+ d->sdn1 = checkedCalToSdn(d->cal, begin_date.year, begin_date.month,
+ begin_date.day);
+ if (d->sdn1 == -1) {
+ gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
+ begin_date.year, begin_date.month, begin_date.day);
+ result = 1;
+ }
+ else
+ if (d->type == DATE_BOUNDED) {
+ d->sdn2 = checkedCalToSdn(d->cal, end_date.year, end_date.month,
+ end_date.day);
+ if (d->sdn2 == -1) {
+ gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
+ end_date.year, end_date.month, end_date.day);
+ result = 1;
+ }
+ else
+ d->sdn2 -= 1;
+ }
+ }
+ return result;
+}
+
+/* PRE: d->cal != CAL_UNKNOWN
+ INPUT: d->type, d->sdn1, d->sdn2
+ OUTPUT: d->day, d->month, d->year
+*/
+int sdn_to_numbers(struct date *d)
+{
+ int result = 0;
+ if (d->cal == CAL_UNKNOWN) {
+ gedcom_date_error(_("Cannot compute from SDN for unknown calendar type"));
+ result = 1;
+ }
+ else {
+ struct date begin_date;
+ struct date end_date;
+
+ if (d->sdn1 <= 0) {
+ gedcom_date_error(_("SDN 1 should be bigger than zero"));
+ result = 1;
+ }
+ else {
+ copy_date(&begin_date, d);
+ if (!checkedSdnToCal(d->cal, d->sdn1, &begin_date.year,
+ &begin_date.month, &begin_date.day)) {
+ gedcom_date_error(_("SDN 1 isn't a valid date in the given calendar"));
+ result = 1;
+ }
+ else {
+ switch (d->type) {
+ case DATE_EXACT:
+ if (d->sdn2 != -1) {
+ gedcom_date_error(_("SDN 2 should be -1 for exact dates"));
+ result = 1;
+ }
+ break;
+ case DATE_BOUNDED:
+ if (d->sdn2 <= 0) {
+ gedcom_date_error(_("SDN 2 should be bigger than zero"));
+ result = 1;
+ }
+ else if (d->sdn2 <= d->sdn1) {
+ gedcom_date_error(_("SDN 2 should be bigger than SDN 1"));
+ result = 1;
+ }
+ else {
+ copy_date(&end_date, d);
+ if (!checkedSdnToCal(d->cal, d->sdn2, &end_date.year,
+ &end_date.month, &end_date.day)) {
+ gedcom_date_error(_("SDN 2 isn't a valid date in the given calendar"));
+ result = 1;
+ }
+ else {
+ if (begin_date.year == end_date.year) {
+ if (begin_date.month == end_date.month) {
+ if (begin_date.day == end_date.day) {
+ /* year, month and day are relevant */
+ }
+ else {
+ /* year and month are relevant */
+ begin_date.day = -1;
+ }
+ }
+ else {
+ /* only year is relevant */
+ begin_date.month = -1;
+ begin_date.day = -1;
+ }
+ }
+ else {
+ gedcom_date_error(_("SDN1/SDN2 isn't a bounded date"));
+ result = 1;
+ }
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ d->year = begin_date.year;
+ d->month = begin_date.month;
+ d->day = begin_date.day;
+ }
+ }
+ return result;
+}
+
+/* PRE: d->cal != CAL_UNKNOWN
+ INPUT: d->day_str, d->month_str, d->year_str
+ OUTPUT: d->day, d->month, d->year, d->year_type
+*/
+int strings_to_numbers(struct date *d)
+{
+ int result = 0;
+ if (d->cal == CAL_UNKNOWN) {
+ gedcom_date_error(_("Cannot compute months for unknown calendar type"));
+ result = 1;
+ }
+ else {
+ if (d->day_str[0]) {
+ d->day = get_day_num(d->day_str);
+ if (d->day == -1) result = 1;
+ }
+ else
+ d->day = -1;
+
+ if (d->month_str[0]) {
+ d->month = get_month_num(d->cal, d->month_str);
+ if (d->month == -1) result = 1;
+ }
+ else
+ d->month = -1;
+
+ d->year = get_year_num(d->year_str, &d->year_type);
+ if (d->year == -1) result = 1;
+ }
+
+ return result;
+}
+
+/* PRE: d->cal != CAL_UNKNOWN
+ INPUT: d->day, d->month, d->year, d->year_type
+ OUTPUT: d->day_str, d->month_str, d->year_str
+*/
+int numbers_to_strings(struct date *d)
+{
+ int result = 0;
+ if (d->cal == CAL_UNKNOWN) {
+ gedcom_date_error(_("Cannot compute month names for unknown calendar type"));
+ result = 1;
+ }
+ else {
+ if (d->day != -1)
+ sprintf(d->day_str, "%d", d->day);
+
+ if (d->month > 0 && d->month <= max_month[d->cal])
+ strcpy(d->month_str, month_name[d->cal][d->month - 1]);
+
+ if (d->year_type == YEAR_SINGLE)
+ sprintf(d->year_str, "%d", d->year);
+ else
+ sprintf(d->year_str, "%d/%d", d->year - 1, (d->year % 100));
+ }
+ return result;
+}
+
+/** This function can be called to ensure that an updated date_value is
+ consistent, i.e. all its struct fields are consistent with each other.
+ Depending on which fields you have updated, you should give the correct
+ \c compute_from field.
+
+ The following table gives an overview of the input and output parameters
+ (the calendar type \c cal is always an input parameter, and should not be
+ \c CAL_UNKNOWN):
+ <table border="1" width="100%">
+ <tr>
+ <th><b>compute_from</b></th>
+ <th><b>input parameters</b></th>
+ <th><b>output parameters</b></th>
+ </tr>
+ <tr>
+ <td><code>DI_FROM_STRINGS</code></td>
+ <td><code>day_str, month_str, year_str</code></td>
+ <td><code>day, month, year, year_type<br>
+ type, sdn1, sdn2</code></td>
+ </tr>
+ <tr>
+ <td><code>DI_FROM_NUMBERS</code></td>
+ <td><code>day, month, year, year_type</code></td>
+ <td><code>day_str, month_str, year_str<br>
+ type, sdn1, sdn2</code></td>
+ </tr>
+ <tr>
+ <td><code>DI_FROM_SDN</code></td>
+ <td><code>type, sdn1, sdn2</code></td>
+ <td><code>day, month, year<br>
+ day_str, month_str, year_str</code></td>
+ </tr>
+ </table>
+
+ If the type in the date_value is \c DV_PHRASE, no conversions take place,
+ otherwise one or both of the date structs are processed according to the
+ table above, depending on the type.
+
+ This function could also be used to convert a date from one calendar to
+ another, because the serial day number is calendar independent (error
+ handling is ignored in this example):
+
+ \code
+ struct date_value* dv = gedcom_new_date_value(NULL);
+ dv->date1.cal = CAL_GREGORIAN;
+ dv->date1.day = 4;
+ dv->date1.month = 2;
+ dv->date1.year = 1799;
+ dv->date1.year_type = YEAR_SINGLE;
+ gedcom_normalize_date(DI_FROM_NUMBERS, dv);
+
+ dv->date1.cal = CAL_FRENCH_REV;
+ gedcom_normalize_date(DI_FROM_SDN, dv);
+ \endcode
+
+ At the end of this piece of code, the day, month and year are filled in
+ according to the French Revolution calendar.
+
+ \param compute_from Determines which fields will be taken as input to
+ compute the other fields.
+
+ \param val The struct date_value to update (it will be updated in place)
+
+ \retval 0 on success
+ \retval >0 on failure
+*/
+int gedcom_normalize_date(Date_input compute_from, struct date_value *val)
+{
+ int result = 0;
+ if (val->type != DV_PHRASE) {
+ switch (compute_from) {
+ case DI_FROM_STRINGS:
+ result |= strings_to_numbers(&val->date1);
+ result |= numbers_to_sdn(&val->date1);
+ if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
+ result |= strings_to_numbers(&val->date2);
+ result |= numbers_to_sdn(&val->date2);
+ }
+ break;
+ case DI_FROM_NUMBERS:
+ result |= numbers_to_strings(&val->date1);
+ result |= numbers_to_sdn(&val->date1);
+ if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
+ result |= numbers_to_strings(&val->date2);
+ result |= numbers_to_sdn(&val->date2);
+ }
+ break;
+ case DI_FROM_SDN:
+ result |= sdn_to_numbers(&val->date1);
+ result |= numbers_to_strings(&val->date1);
+ if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
+ result |= sdn_to_numbers(&val->date2);
+ result |= numbers_to_strings(&val->date2);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return result;
+}
+
+/** This function creates a new date_value struct and initializes it properly,
+ or copies an existing date value.
+
+ \param copy_from A given struct date_value to copy (or \c NULL).
+
+ \return If the parameter \c copy_from is NULL, a new value is created and
+ given initial values. If it is non-NULL, the given value is copied into
+ a new date value. In both cases, the new value is returned.
+*/
+struct date_value* gedcom_new_date_value(const struct date_value* copy_from)
+{
+ struct date_value* dv_ptr;
+ dv_ptr = (struct date_value*) malloc(sizeof(struct date_value));
+ if (!dv_ptr)
+ MEMORY_ERROR;
+ else {
+ if (copy_from)
+ memcpy(dv_ptr, copy_from, sizeof(struct date_value));
+ else {
+ dv_ptr->type = DV_NO_MODIFIER;
+ init_date(&dv_ptr->date1);
+ init_date(&dv_ptr->date2);
+ dv_ptr->phrase[0] = '\0';