renamed the package to libgedcom-dev
[gedcom-parse.git] / gedcom / date.c
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.
5
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.
10
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.
15
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
19    02111-1307 USA.  */
20
21 /* $Id$ */
22 /* $Name$ */
23
24 #include "gedcom_internal.h"
25 #include "sdncal.h"
26 #include "buffer.h"
27 #include "compat.h"
28 #include <string.h>
29 #include "date.h"
30
31 struct date_value dv_s;
32 struct date date_s;
33
34 struct date_value def_date_val;
35 struct date def_date;
36
37 const char* curr_line_value;
38
39 void cleanup_date_buffer();
40 struct safe_buffer date_buffer = { NULL, 0, NULL, 0, cleanup_date_buffer };
41
42 void cleanup_date_buffer()
43 {
44   cleanup_buffer(&date_buffer);
45 }
46
47 int max_month[] = { 12,  /* CAL_GREGORIAN */
48                     12,  /* CAL_JULIAN */
49                     13,  /* CAL_HEBREW */
50                     13,  /* CAL_FRENCH_REV */
51                     0    /* CAL_UNKNOWN */
52                   };
53
54 char* month_name[][13] =
55 { /* CAL_GREGORIAN */
56   { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
57     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" },
58   /* CAL_JULIAN */
59   { "JAN", "FEB", "MAR", "APR", "MAY", "JUN",
60     "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" },
61   /* CAL_JEWISH */
62   { "TSH", "CSH", "KSL", "TVT", "SHV", "ADR", "ADS",
63     "NSN", "IYR", "SVN", "TMZ", "AAV", "ELL" },
64   /* CAL_FRENCH_REF */
65   { "VEND", "BRUM", "FRIM", "NIVO", "PLUV", "VENT", "GERM",
66     "FLOR", "PRAI", "MESS", "THER", "FRUC", "COMP" }
67 };
68
69 typedef long int (*to_sdn_func_type) (int, int, int);
70 typedef void (*from_sdn_func_type) (long int, int*, int*, int*);
71
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 */
77 };
78
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 */
84 };
85
86 long int checkedCalToSdn(Calendar_type cal, int year, int month, int day)
87 {
88   int y, m, d;
89   long int sdn = (*to_sdn_func[cal])(year,month, day);
90   if (sdn <= 0)
91     return -1;
92   else {
93     (*from_sdn_func[cal])(sdn, &y, &m, &d);
94     if ((year == y) && (month == m) && (day == d))
95       return sdn;
96     else
97       return -1;
98   }
99 }
100
101 int checkedSdnToCal(Calendar_type cal, long int sdn,
102                     int* year, int* month, int* day)
103 {
104   (*from_sdn_func[cal])(sdn, year, month, day);
105   if (*year > 0 && *month > 0 && *day > 0)
106     return 1;
107   else
108     return 0;
109 }
110
111 void copy_date(struct date *to, struct date *from)
112 {
113   memcpy(to, from, sizeof(struct date));
114 }
115
116 void init_date(struct date *d)
117 {
118   d->cal = CAL_UNKNOWN;
119   d->day_str[0] = '\0';
120   d->month_str[0] = '\0';
121   d->year_str[0] = '\0';
122   d->day = -1;
123   d->month = -1;
124   d->year = -1;
125   d->year_type = YEAR_SINGLE;
126   d->type = DATE_UNRECOGNIZED;
127   d->sdn1 = -1;
128   d->sdn2 = -1;
129 }
130
131 struct date_value* make_date_value(Date_value_type t, struct date *d1,
132                                   struct date *d2, const char* p)
133 {
134   dv_s.type = t;
135   copy_date(&dv_s.date1, d1);
136   copy_date(&dv_s.date2, d2);
137   strncpy(dv_s.phrase, p, MAX_PHRASE_LEN + 1);
138   return &dv_s;
139 }
140
141 /* PRE:     d->cal != CAL_UNKNOWN
142    INPUT:   d->day, d->month, d->year
143    OUTPUT:  d->type, d->sdn1, d->sdn2
144 */
145 int numbers_to_sdn(struct date *d)
146 {
147   int result = 0;
148   if (d->cal == CAL_UNKNOWN) {
149     d->type = DATE_UNRECOGNIZED;
150     gedcom_date_error(_("Cannot compute SDN for unknown calendar type"));
151     result = 1;
152   }
153   else {
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;
163         end_date.year += 1;
164       }
165       else if (begin_date.day == -1) {
166         begin_date.day   = 1; end_date.day   = 1;
167         end_date.month += 1;
168         if (end_date.month > max_month[d->cal]) {
169           end_date.month -= max_month[d->cal];
170           end_date.year  += 1;
171         }
172       }
173       else {
174         gedcom_date_error(_("Year has to be given in bounded date"));
175         result = 1;
176       }
177     }
178     else {
179       d->type = DATE_EXACT;
180     }
181
182     d->sdn1 = checkedCalToSdn(d->cal, begin_date.year, begin_date.month,
183                               begin_date.day);
184     if (d->sdn1 == -1) {
185       gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
186                    begin_date.year, begin_date.month, begin_date.day);
187       result = 1;
188     }
189     else
190       if (d->type == DATE_BOUNDED) {
191         d->sdn2 = checkedCalToSdn(d->cal, end_date.year, end_date.month,
192                                   end_date.day);
193         if (d->sdn2 == -1) {
194           gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
195                        end_date.year, end_date.month, end_date.day);
196           result = 1;
197         }
198         else
199           d->sdn2 -= 1;
200       }
201   }
202   return result;
203 }
204
205 /* PRE:     d->cal != CAL_UNKNOWN
206    INPUT:   d->type, d->sdn1, d->sdn2
207    OUTPUT:  d->day, d->month, d->year
208 */
209 int sdn_to_numbers(struct date *d)
210 {
211   int result = 0;
212   if (d->cal == CAL_UNKNOWN) {
213     gedcom_date_error(_("Cannot compute from SDN for unknown calendar type"));
214     result = 1;
215   }
216   else {
217     struct date begin_date;
218     struct date end_date;
219
220     if (d->sdn1 <= 0) {
221       gedcom_date_error(_("SDN 1 should be bigger than zero"));
222       result = 1;
223     }
224     else {
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"));
229         result = 1;
230       }
231       else {
232         switch (d->type) {
233           case DATE_EXACT:
234             if (d->sdn2 != -1) {
235               gedcom_date_error(_("SDN 2 should be -1 for exact dates"));
236               result = 1;
237             }
238             break;
239           case DATE_BOUNDED:
240             if (d->sdn2 <= 0) {
241               gedcom_date_error(_("SDN 2 should be bigger than zero"));
242               result = 1;
243             }
244             else if (d->sdn2 <= d->sdn1) {
245               gedcom_date_error(_("SDN 2 should be bigger than SDN 1"));
246               result = 1;
247             }
248             else {
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"));
253                 result = 1;
254               }
255               else {
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 */
260                     }
261                     else {
262                       /* year and month are relevant */
263                       begin_date.day = -1;
264                     }
265                   }
266                   else {
267                     /* only year is relevant */
268                     begin_date.month = -1;
269                     begin_date.day   = -1;
270                   }
271                 }
272                 else {
273                   gedcom_date_error(_("SDN1/SDN2 isn't a bounded date"));
274                   result = 1;
275                 }
276               }
277             }
278             break;
279           default:
280             break;
281         }
282       }
283       d->year  = begin_date.year;
284       d->month = begin_date.month;
285       d->day   = begin_date.day;
286     }
287   }
288   return result;
289 }
290
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
294 */
295 int strings_to_numbers(struct date *d)
296 {
297   int result = 0;
298   if (d->cal == CAL_UNKNOWN) {
299     gedcom_date_error(_("Cannot compute months for unknown calendar type"));
300     result = 1;
301   }
302   else {
303     if (d->day_str[0]) {
304       d->day = get_day_num(d->day_str);
305       if (d->day == -1) result = 1;
306     }
307     else
308       d->day = -1;
309     
310     if (d->month_str[0]) {
311       d->month = get_month_num(d->cal, d->month_str);
312       if (d->month == -1) result = 1;
313     }
314     else
315       d->month = -1;
316     
317     d->year = get_year_num(d->year_str, &d->year_type);
318     if (d->year == -1) result = 1;
319   }
320   
321   return result;
322 }
323
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
327 */
328 int numbers_to_strings(struct date *d)
329 {
330   int result = 0;
331   if (d->cal == CAL_UNKNOWN) {
332     gedcom_date_error(_("Cannot compute month names for unknown calendar type"));
333     result = 1;
334   }
335   else {
336     if (d->day != -1)
337       sprintf(d->day_str, "%d", d->day);
338     
339     if (d->month > 0 && d->month <= max_month[d->cal])
340       strcpy(d->month_str, month_name[d->cal][d->month - 1]);
341     
342     if (d->year_type == YEAR_SINGLE)
343       sprintf(d->year_str, "%d", d->year);
344     else
345       sprintf(d->year_str, "%d/%d", d->year - 1, (d->year % 100));
346   }
347   return result;
348 }
349
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.
354
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
357     \c CAL_UNKNOWN):
358      <table border="1" width="100%">
359        <tr>
360          <th><b>compute_from</b></th>
361          <th><b>input parameters</b></th>
362          <th><b>output parameters</b></th>
363        </tr>
364        <tr>
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>
369        </tr>
370        <tr>
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>
375        </tr>
376        <tr>
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>
381        </tr>
382      </table>
383
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.
387
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):
391     
392     \code
393       struct date_value* dv = gedcom_new_date_value(NULL);
394       dv->date1.cal = CAL_GREGORIAN;
395       dv->date1.day   = 4;
396       dv->date1.month = 2;
397       dv->date1.year  = 1799;
398       dv->date1.year_type = YEAR_SINGLE;
399       gedcom_normalize_date(DI_FROM_NUMBERS, dv);
400
401       dv->date1.cal = CAL_FRENCH_REV;
402       gedcom_normalize_date(DI_FROM_SDN, dv);
403     \endcode
404
405     At the end of this piece of code, the day, month and year are filled in
406     according to the French Revolution calendar.
407
408     \param compute_from Determines which fields will be taken as input to
409     compute the other fields.
410      
411     \param val The struct date_value to update (it will be updated in place)
412
413     \retval 0 on success
414     \retval >0 on failure
415 */
416 int gedcom_normalize_date(Date_input compute_from, struct date_value *val)
417 {
418   int result = 0;
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);
427         }
428         break;
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);
435         }
436         break;
437       case DI_FROM_SDN:
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);
443         }
444         break;
445       default:
446         break;
447     }
448   }
449   return result;
450 }
451
452 /** This function creates a new date_value struct and initializes it properly,
453     or copies an existing date value.
454
455     \param copy_from  A given struct date_value to copy (or \c NULL).
456
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.
460 */
461 struct date_value* gedcom_new_date_value(const struct date_value* copy_from)
462 {
463   struct date_value* dv_ptr;
464   dv_ptr = (struct date_value*) malloc(sizeof(struct date_value));
465   if (!dv_ptr)
466     MEMORY_ERROR;
467   else {
468     if (copy_from)
469       memcpy(dv_ptr, copy_from, sizeof(struct date_value));
470     else {
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';
475     }
476   }
477   return dv_ptr;
478 }
479
480 /** This function allows to convert the given \c line_value into a struct
481     date_value.
482     
483     \param line_value A string containing the date to parse
484
485     \return The parsed date; note that this return value is statically
486     allocated, and is thus overwritten on each call.
487 */
488 struct date_value gedcom_parse_date(const char* line_value)
489 {
490   int result = 0;
491   init_date(&dv_s.date1);
492   init_date(&dv_s.date2);
493   init_date(&date_s);
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);
499     result = 1;
500   }
501   else {
502     compat_date_start();
503     init_gedcom_date_lex(line_value);
504     gedcom_date_parse();
505     close_gedcom_date_lex();
506     if (compat_date_check(&dv_s, &curr_line_value)) {
507       init_gedcom_date_lex(curr_line_value);
508       gedcom_date_parse();
509       close_gedcom_date_lex();
510     }
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);
515   }
516   if (result != 0) {
517     gedcom_date_error(_("Putting date '%s' in 'phrase' member"),
518                       curr_line_value);
519     make_date_value(DV_PHRASE, &dv_s.date1, &dv_s.date2, curr_line_value);
520   }
521   return dv_s;
522 }
523
524 void write_date(const struct date* d)
525 {
526   if (! d->year_str[0] || d->year <= 0 || d->sdn1 <= 0)
527     gedcom_error(_("Date is not normalized: some fields are invalid"));
528   else {
529     switch (d->cal) {
530       case CAL_GREGORIAN: break;
531       case CAL_JULIAN:
532         safe_buf_append(&date_buffer, "@#DJULIAN@ "); break;
533       case CAL_HEBREW:
534         safe_buf_append(&date_buffer, "@#DHEBREW@ "); break;
535       case CAL_FRENCH_REV:
536         safe_buf_append(&date_buffer, "@#DFRENCH R@ "); break;
537       case CAL_UNKNOWN:
538         safe_buf_append(&date_buffer, "@#DUNKNOWN@ "); break;
539       default:
540         break;
541     }
542     if (d->day_str[0])
543       safe_buf_append(&date_buffer, "%s ", d->day_str);
544     if (d->month_str[0])
545       safe_buf_append(&date_buffer, "%s ", d->month_str);
546     safe_buf_append(&date_buffer, "%s", d->year_str);
547   }
548 }
549
550 /** This function converts the given struct date_value into its string
551     representation.
552
553     \param val  The given parsed date
554
555     \return The string representation of the parsed date; note that this value
556     is statically allocated, and is thus overwritten on each call
557 */
558 char* gedcom_date_to_string(const struct date_value* val)
559 {
560   init_buffer(&date_buffer);
561   reset_buffer(&date_buffer);
562   
563   switch (val->type) {
564     case DV_NO_MODIFIER:
565       write_date(&val->date1); break;
566     case DV_BEFORE:
567       safe_buf_append(&date_buffer, "BEF ");
568       write_date(&val->date1); break;
569     case DV_AFTER:
570       safe_buf_append(&date_buffer, "AFT ");
571       write_date(&val->date1); break;
572     case DV_BETWEEN:
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;
577     case DV_FROM:
578       safe_buf_append(&date_buffer, "FROM ");
579       write_date(&val->date1); break;
580     case DV_TO:
581       safe_buf_append(&date_buffer, "TO ");
582       write_date(&val->date1); break;
583     case DV_FROM_TO:
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;
588     case DV_ABOUT:
589       safe_buf_append(&date_buffer, "ABT ");
590       write_date(&val->date1); break;
591     case DV_CALCULATED:
592       safe_buf_append(&date_buffer, "CAL ");
593       write_date(&val->date1); break;
594     case DV_ESTIMATED:
595       safe_buf_append(&date_buffer, "EST ");
596       write_date(&val->date1); break;
597     case DV_INTERPRETED:
598       safe_buf_append(&date_buffer, "INT ");
599       write_date(&val->date1);
600       safe_buf_append(&date_buffer, " (%s)", val->phrase); break;
601     case DV_PHRASE:
602       safe_buf_append(&date_buffer, "(%s)", val->phrase); break;
603     default:
604       break;
605   }
606   
607   return get_buf_string(&date_buffer);
608 }