1 /* Date manipulation routines.
2 Copyright (C) 2001,2002 The Genes Development Team
3 This file is part of the Gedcom parser library.
4 Contributed by Peter Verthez <Peter.Verthez@advalvas.be>, 2001.
6 The Gedcom parser library is free software; you can redistribute it
7 and/or modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 The Gedcom parser library is distributed in the hope that it will be
12 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with the Gedcom parser library; if not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24 #include "gedcom_internal.h"
31 struct date_value dv_s;
34 struct date_value def_date_val;
37 const char* curr_line_value;
39 void cleanup_date_buffer();
40 struct safe_buffer date_buffer = { NULL, 0, NULL, 0, cleanup_date_buffer };
42 void cleanup_date_buffer()
44 cleanup_buffer(&date_buffer);
47 int max_month[] = { 12, /* CAL_GREGORIAN */
50 13, /* CAL_FRENCH_REV */
54 char* month_name[][13] =
56 { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
57 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" },
59 { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
60 "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" },
62 { "TSH", "CSH", "KSL", "TVT", "SHV", "ADR", "ADS",
63 "NSN", "IYR", "SVN", "TMZ", "AAV", "ELL" },
65 { "VEND", "BRUM", "FRIM", "NIVO", "PLUV", "VENT", "GERM",
66 "FLOR", "PRAI", "MESS", "THER", "FRUC", "COMP" }
69 typedef long int (*to_sdn_func_type) (int, int, int);
70 typedef void (*from_sdn_func_type) (long int, int*, int*, int*);
72 to_sdn_func_type to_sdn_func[] = {
73 &GregorianToSdn, /* CAL_GREGORIAN */
74 &JulianToSdn, /* CAL_JULIAN */
75 &JewishToSdn, /* CAL_JEWISH */
76 &FrenchToSdn /* CAL_FRENCH_REV */
79 from_sdn_func_type from_sdn_func[] = {
80 &SdnToGregorian, /* CAL_GREGORIAN */
81 &SdnToJulian, /* CAL_JULIAN */
82 &SdnToJewish, /* CAL_JEWISH */
83 &SdnToFrench /* CAL_FRENCH_REV */
86 long int checkedCalToSdn(Calendar_type cal, int year, int month, int day)
89 long int sdn = (*to_sdn_func[cal])(year,month, day);
93 (*from_sdn_func[cal])(sdn, &y, &m, &d);
94 if ((year == y) && (month == m) && (day == d))
101 int checkedSdnToCal(Calendar_type cal, long int sdn,
102 int* year, int* month, int* day)
104 (*from_sdn_func[cal])(sdn, year, month, day);
105 if (*year > 0 && *month > 0 && *day > 0)
111 void copy_date(struct date *to, struct date *from)
113 memcpy(to, from, sizeof(struct date));
116 void init_date(struct date *d)
118 d->cal = CAL_UNKNOWN;
119 d->day_str[0] = '\0';
120 d->month_str[0] = '\0';
121 d->year_str[0] = '\0';
125 d->year_type = YEAR_SINGLE;
126 d->type = DATE_UNRECOGNIZED;
131 struct date_value* make_date_value(Date_value_type t, struct date *d1,
132 struct date *d2, const char* p)
135 copy_date(&dv_s.date1, d1);
136 copy_date(&dv_s.date2, d2);
137 strncpy(dv_s.phrase, p, MAX_PHRASE_LEN + 1);
141 /* PRE: d->cal != CAL_UNKNOWN
142 INPUT: d->day, d->month, d->year
143 OUTPUT: d->type, d->sdn1, d->sdn2
145 int numbers_to_sdn(struct date *d)
148 if (d->cal == CAL_UNKNOWN) {
149 d->type = DATE_UNRECOGNIZED;
150 gedcom_date_error(_("Cannot compute SDN for unknown calendar type"));
154 struct date begin_date;
155 struct date end_date;
156 copy_date(&begin_date, d);
157 if (d->day == -1 || d->month == -1 || d->year == -1) {
158 d->type = DATE_BOUNDED;
159 copy_date(&end_date, d);
160 if (begin_date.month == -1) {
161 begin_date.month = 1; end_date.month = 1;
162 begin_date.day = 1; end_date.day = 1;
165 else if (begin_date.day == -1) {
166 begin_date.day = 1; end_date.day = 1;
168 if (end_date.month > max_month[d->cal]) {
169 end_date.month -= max_month[d->cal];
174 gedcom_date_error(_("Year has to be given in bounded date"));
179 d->type = DATE_EXACT;
182 d->sdn1 = checkedCalToSdn(d->cal, begin_date.year, begin_date.month,
185 gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
186 begin_date.year, begin_date.month, begin_date.day);
190 if (d->type == DATE_BOUNDED) {
191 d->sdn2 = checkedCalToSdn(d->cal, end_date.year, end_date.month,
194 gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
195 end_date.year, end_date.month, end_date.day);
205 /* PRE: d->cal != CAL_UNKNOWN
206 INPUT: d->type, d->sdn1, d->sdn2
207 OUTPUT: d->day, d->month, d->year
209 int sdn_to_numbers(struct date *d)
212 if (d->cal == CAL_UNKNOWN) {
213 gedcom_date_error(_("Cannot compute from SDN for unknown calendar type"));
217 struct date begin_date;
218 struct date end_date;
221 gedcom_date_error(_("SDN 1 should be bigger than zero"));
225 copy_date(&begin_date, d);
226 if (!checkedSdnToCal(d->cal, d->sdn1, &begin_date.year,
227 &begin_date.month, &begin_date.day)) {
228 gedcom_date_error(_("SDN 1 isn't a valid date in the given calendar"));
235 gedcom_date_error(_("SDN 2 should be -1 for exact dates"));
241 gedcom_date_error(_("SDN 2 should be bigger than zero"));
244 else if (d->sdn2 <= d->sdn1) {
245 gedcom_date_error(_("SDN 2 should be bigger than SDN 1"));
249 copy_date(&end_date, d);
250 if (!checkedSdnToCal(d->cal, d->sdn2, &end_date.year,
251 &end_date.month, &end_date.day)) {
252 gedcom_date_error(_("SDN 2 isn't a valid date in the given calendar"));
256 if (begin_date.year == end_date.year) {
257 if (begin_date.month == end_date.month) {
258 if (begin_date.day == end_date.day) {
259 /* year, month and day are relevant */
262 /* year and month are relevant */
267 /* only year is relevant */
268 begin_date.month = -1;
273 gedcom_date_error(_("SDN1/SDN2 isn't a bounded date"));
283 d->year = begin_date.year;
284 d->month = begin_date.month;
285 d->day = begin_date.day;
291 /* PRE: d->cal != CAL_UNKNOWN
292 INPUT: d->day_str, d->month_str, d->year_str
293 OUTPUT: d->day, d->month, d->year, d->year_type
295 int strings_to_numbers(struct date *d)
298 if (d->cal == CAL_UNKNOWN) {
299 gedcom_date_error(_("Cannot compute months for unknown calendar type"));
304 d->day = get_day_num(d->day_str);
305 if (d->day == -1) result = 1;
310 if (d->month_str[0]) {
311 d->month = get_month_num(d->cal, d->month_str);
312 if (d->month == -1) result = 1;
317 d->year = get_year_num(d->year_str, &d->year_type);
318 if (d->year == -1) result = 1;
324 /* PRE: d->cal != CAL_UNKNOWN
325 INPUT: d->day, d->month, d->year, d->year_type
326 OUTPUT: d->day_str, d->month_str, d->year_str
328 int numbers_to_strings(struct date *d)
331 if (d->cal == CAL_UNKNOWN) {
332 gedcom_date_error(_("Cannot compute month names for unknown calendar type"));
337 sprintf(d->day_str, "%d", d->day);
339 if (d->month > 0 && d->month <= max_month[d->cal])
340 strcpy(d->month_str, month_name[d->cal][d->month - 1]);
342 if (d->year_type == YEAR_SINGLE)
343 sprintf(d->year_str, "%d", d->year);
345 sprintf(d->year_str, "%d/%d", d->year - 1, (d->year % 100));
350 /** This function can be called to ensure that an updated date_value is
351 consistent, i.e. all its struct fields are consistent with each other.
352 Depending on which fields you have updated, you should give the correct
353 \c compute_from field.
355 The following table gives an overview of the input and output parameters
356 (the calendar type \c cal is always an input parameter, and should not be
358 <table border="1" width="100%">
360 <th><b>compute_from</b></th>
361 <th><b>input parameters</b></th>
362 <th><b>output parameters</b></th>
365 <td><code>DI_FROM_STRINGS</code></td>
366 <td><code>day_str, month_str, year_str</code></td>
367 <td><code>day, month, year, year_type<br>
368 type, sdn1, sdn2</code></td>
371 <td><code>DI_FROM_NUMBERS</code></td>
372 <td><code>day, month, year, year_type</code></td>
373 <td><code>day_str, month_str, year_str<br>
374 type, sdn1, sdn2</code></td>
377 <td><code>DI_FROM_SDN</code></td>
378 <td><code>type, sdn1, sdn2</code></td>
379 <td><code>day, month, year<br>
380 day_str, month_str, year_str</code></td>
384 If the type in the date_value is \c DV_PHRASE, no conversions take place,
385 otherwise one or both of the date structs are processed according to the
386 table above, depending on the type.
388 This function could also be used to convert a date from one calendar to
389 another, because the serial day number is calendar independent (error
390 handling is ignored in this example):
393 struct date_value* dv = gedcom_new_date_value(NULL);
394 dv->date1.cal = CAL_GREGORIAN;
397 dv->date1.year = 1799;
398 dv->date1.year_type = YEAR_SINGLE;
399 gedcom_normalize_date(DI_FROM_NUMBERS, dv);
401 dv->date1.cal = CAL_FRENCH_REV;
402 gedcom_normalize_date(DI_FROM_SDN, dv);
405 At the end of this piece of code, the day, month and year are filled in
406 according to the French Revolution calendar.
408 \param compute_from Determines which fields will be taken as input to
409 compute the other fields.
411 \param val The struct date_value to update (it will be updated in place)
414 \retval >0 on failure
416 int gedcom_normalize_date(Date_input compute_from, struct date_value *val)
419 if (val->type != DV_PHRASE) {
420 switch (compute_from) {
421 case DI_FROM_STRINGS:
422 result |= strings_to_numbers(&val->date1);
423 result |= numbers_to_sdn(&val->date1);
424 if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
425 result |= strings_to_numbers(&val->date2);
426 result |= numbers_to_sdn(&val->date2);
429 case DI_FROM_NUMBERS:
430 result |= numbers_to_strings(&val->date1);
431 result |= numbers_to_sdn(&val->date1);
432 if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
433 result |= numbers_to_strings(&val->date2);
434 result |= numbers_to_sdn(&val->date2);
438 result |= sdn_to_numbers(&val->date1);
439 result |= numbers_to_strings(&val->date1);
440 if (val->type == DV_BETWEEN || val->type == DV_FROM_TO) {
441 result |= sdn_to_numbers(&val->date2);
442 result |= numbers_to_strings(&val->date2);
452 /** This function creates a new date_value struct and initializes it properly,
453 or copies an existing date value.
455 \param copy_from A given struct date_value to copy (or \c NULL).
457 \return If the parameter \c copy_from is NULL, a new value is created and
458 given initial values. If it is non-NULL, the given value is copied into
459 a new date value. In both cases, the new value is returned.
461 struct date_value* gedcom_new_date_value(const struct date_value* copy_from)
463 struct date_value* dv_ptr;
464 dv_ptr = (struct date_value*) malloc(sizeof(struct date_value));
469 memcpy(dv_ptr, copy_from, sizeof(struct date_value));
471 dv_ptr->type = DV_NO_MODIFIER;
472 init_date(&dv_ptr->date1);
473 init_date(&dv_ptr->date2);
474 dv_ptr->phrase[0] = '\0';
480 /** This function allows to convert the given \c line_value into a struct
483 \param line_value A string containing the date to parse
485 \return The parsed date; note that this return value is statically
486 allocated, and is thus overwritten on each call.
488 struct date_value gedcom_parse_date(const char* line_value)
491 init_date(&dv_s.date1);
492 init_date(&dv_s.date2);
494 init_date(&def_date);
495 curr_line_value = line_value;
496 if (compat_mode(C_NO_REQUIRED_VALUES)
497 && !strncmp(curr_line_value, VALUE_IF_MISSING, 2)) {
498 gedcom_date_error(_("Empty value changed to '%s'"), VALUE_IF_MISSING);
503 init_gedcom_date_lex(line_value);
505 close_gedcom_date_lex();
506 if (compat_date_check(&dv_s, &curr_line_value)) {
507 init_gedcom_date_lex(curr_line_value);
509 close_gedcom_date_lex();
511 if (dv_s.date1.cal != CAL_UNKNOWN)
512 result |= numbers_to_sdn(&dv_s.date1);
513 if (dv_s.date2.cal != CAL_UNKNOWN)
514 result |= numbers_to_sdn(&dv_s.date2);
517 gedcom_date_error(_("Putting date '%s' in 'phrase' member"),
519 make_date_value(DV_PHRASE, &dv_s.date1, &dv_s.date2, curr_line_value);
524 void write_date(const struct date* d)
526 if (! d->year_str[0] || d->year <= 0 || d->sdn1 <= 0)
527 gedcom_error(_("Date is not normalized: some fields are invalid"));
530 case CAL_GREGORIAN: break;
532 safe_buf_append(&date_buffer, "@#DJULIAN@ "); break;
534 safe_buf_append(&date_buffer, "@#DHEBREW@ "); break;
536 safe_buf_append(&date_buffer, "@#DFRENCH R@ "); break;
538 safe_buf_append(&date_buffer, "@#DUNKNOWN@ "); break;
543 safe_buf_append(&date_buffer, "%s ", d->day_str);
545 safe_buf_append(&date_buffer, "%s ", d->month_str);
546 safe_buf_append(&date_buffer, "%s", d->year_str);
550 /** This function converts the given struct date_value into its string
553 \param val The given parsed date
555 \return The string representation of the parsed date; note that this value
556 is statically allocated, and is thus overwritten on each call
558 char* gedcom_date_to_string(const struct date_value* val)
560 init_buffer(&date_buffer);
561 reset_buffer(&date_buffer);
565 write_date(&val->date1); break;
567 safe_buf_append(&date_buffer, "BEF ");
568 write_date(&val->date1); break;
570 safe_buf_append(&date_buffer, "AFT ");
571 write_date(&val->date1); break;
573 safe_buf_append(&date_buffer, "BET ");
574 write_date(&val->date1);
575 safe_buf_append(&date_buffer, " AND ");
576 write_date(&val->date2); break;
578 safe_buf_append(&date_buffer, "FROM ");
579 write_date(&val->date1); break;
581 safe_buf_append(&date_buffer, "TO ");
582 write_date(&val->date1); break;
584 safe_buf_append(&date_buffer, "FROM ");
585 write_date(&val->date1);
586 safe_buf_append(&date_buffer, " TO ");
587 write_date(&val->date2); break;
589 safe_buf_append(&date_buffer, "ABT ");
590 write_date(&val->date1); break;
592 safe_buf_append(&date_buffer, "CAL ");
593 write_date(&val->date1); break;
595 safe_buf_append(&date_buffer, "EST ");
596 write_date(&val->date1); break;
598 safe_buf_append(&date_buffer, "INT ");
599 write_date(&val->date1);
600 safe_buf_append(&date_buffer, " (%s)", val->phrase); break;
602 safe_buf_append(&date_buffer, "(%s)", val->phrase); break;
607 return get_buf_string(&date_buffer);