renamed the package to libgedcom-dev
[gedcom-parse.git] / gedcom / date.c
index 5deab785aa17884439e64bf1ffa59849bd680a89..23a689e21abd7b02848a4c1a845397ac099161ab 100644 (file)
@@ -24,6 +24,8 @@
 #include "gedcom_internal.h"
 #include "sdncal.h"
 #include "buffer.h"
+#include "compat.h"
+#include <string.h>
 #include "date.h"
 
 struct date_value dv_s;
@@ -106,9 +108,9 @@ int checkedSdnToCal(Calendar_type cal, long int sdn,
     return 0;
 }
 
-void copy_date(struct date *to, struct date from)
+void copy_date(struct date *to, struct date *from)
 {
-  memcpy(to, &from, sizeof(struct date));
+  memcpy(to, from, sizeof(struct date));
 }
 
 void init_date(struct date *d)
@@ -126,14 +128,14 @@ void init_date(struct date *d)
   d->sdn2 = -1;
 }
 
-struct date_value make_date_value(Date_value_type t, struct date d1,
-                                 struct date d2, const char* p)
+struct date_value* make_date_value(Date_value_type t, struct date *d1,
+                                 struct date *d2, const char* p)
 {
   dv_s.type = t;
   copy_date(&dv_s.date1, d1);
   copy_date(&dv_s.date2, d2);
   strncpy(dv_s.phrase, p, MAX_PHRASE_LEN + 1);
-  return dv_s;
+  return &dv_s;
 }
 
 /* PRE:     d->cal != CAL_UNKNOWN
@@ -145,16 +147,16 @@ int numbers_to_sdn(struct date *d)
   int result = 0;
   if (d->cal == CAL_UNKNOWN) {
     d->type = DATE_UNRECOGNIZED;
-    gedcom_error(_("Cannot compute SDN for unknown calendar type"));
+    gedcom_date_error(_("Cannot compute SDN for unknown calendar type"));
     result = 1;
   }
   else {
     struct date begin_date;
     struct date end_date;
-    copy_date(&begin_date, *d);
+    copy_date(&begin_date, d);
     if (d->day == -1 || d->month == -1 || d->year == -1) {
       d->type = DATE_BOUNDED;
-      copy_date(&end_date, *d);
+      copy_date(&end_date, d);
       if (begin_date.month == -1) {
        begin_date.month = 1; end_date.month = 1;
        begin_date.day   = 1; end_date.day   = 1;
@@ -169,7 +171,7 @@ int numbers_to_sdn(struct date *d)
        }
       }
       else {
-       gedcom_error(_("Year has to be given in bounded date"));
+       gedcom_date_error(_("Year has to be given in bounded date"));
        result = 1;
       }
     }
@@ -180,7 +182,8 @@ int numbers_to_sdn(struct date *d)
     d->sdn1 = checkedCalToSdn(d->cal, begin_date.year, begin_date.month,
                              begin_date.day);
     if (d->sdn1 == -1) {
-      gedcom_error(_("Error converting date"));
+      gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
+                  begin_date.year, begin_date.month, begin_date.day);
       result = 1;
     }
     else
@@ -188,7 +191,8 @@ int numbers_to_sdn(struct date *d)
        d->sdn2 = checkedCalToSdn(d->cal, end_date.year, end_date.month,
                                  end_date.day);
        if (d->sdn2 == -1) {
-         gedcom_error(_("Error converting date"));
+         gedcom_date_error(_("Error converting date: year %d, month %d, day %d"),
+                      end_date.year, end_date.month, end_date.day);
          result = 1;
        }
        else
@@ -206,7 +210,7 @@ int sdn_to_numbers(struct date *d)
 {
   int result = 0;
   if (d->cal == CAL_UNKNOWN) {
-    gedcom_error(_("Cannot compute from SDN for unknown calendar type"));
+    gedcom_date_error(_("Cannot compute from SDN for unknown calendar type"));
     result = 1;
   }
   else {
@@ -214,38 +218,38 @@ int sdn_to_numbers(struct date *d)
     struct date end_date;
 
     if (d->sdn1 <= 0) {
-      gedcom_error(_("SDN 1 should be bigger than zero"));
+      gedcom_date_error(_("SDN 1 should be bigger than zero"));
       result = 1;
     }
     else {
-      copy_date(&begin_date, *d);
+      copy_date(&begin_date, d);
       if (!checkedSdnToCal(d->cal, d->sdn1, &begin_date.year,
                           &begin_date.month, &begin_date.day)) {
-       gedcom_error(_("SDN 1 isn't a valid date in the given calendar"));
+       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_error(_("SDN 2 should be -1 for exact dates"));
+             gedcom_date_error(_("SDN 2 should be -1 for exact dates"));
              result = 1;
            }
            break;
          case DATE_BOUNDED:
            if (d->sdn2 <= 0) {
-             gedcom_error(_("SDN 2 should be bigger than zero"));
+             gedcom_date_error(_("SDN 2 should be bigger than zero"));
              result = 1;
            }
            else if (d->sdn2 <= d->sdn1) {
-             gedcom_error(_("SDN 2 should be bigger than SDN 1"));
+             gedcom_date_error(_("SDN 2 should be bigger than SDN 1"));
              result = 1;
            }
            else {
-             copy_date(&end_date, *d);
+             copy_date(&end_date, d);
              if (!checkedSdnToCal(d->cal, d->sdn2, &end_date.year,
                                   &end_date.month, &end_date.day)) {
-               gedcom_error(_("SDN 2 isn't a valid date in the given calendar"));
+               gedcom_date_error(_("SDN 2 isn't a valid date in the given calendar"));
                result = 1;
              }
              else {
@@ -266,7 +270,7 @@ int sdn_to_numbers(struct date *d)
                  }
                }
                else {
-                 gedcom_error(_("SDN1/SDN2 isn't a bounded date"));
+                 gedcom_date_error(_("SDN1/SDN2 isn't a bounded date"));
                  result = 1;
                }
              }
@@ -292,7 +296,7 @@ int strings_to_numbers(struct date *d)
 {
   int result = 0;
   if (d->cal == CAL_UNKNOWN) {
-    gedcom_error(_("Cannot compute months for unknown calendar type"));
+    gedcom_date_error(_("Cannot compute months for unknown calendar type"));
     result = 1;
   }
   else {
@@ -325,14 +329,14 @@ int numbers_to_strings(struct date *d)
 {
   int result = 0;
   if (d->cal == CAL_UNKNOWN) {
-    gedcom_error(_("Cannot compute month names for unknown calendar type"));
+    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 != -1)
+    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)
@@ -343,11 +347,77 @@ int numbers_to_strings(struct date *d)
   return result;
 }
 
-int gedcom_normalize_date(Date_input input, struct date_value *val)
+/** 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 (input) {
+    switch (compute_from) {
       case DI_FROM_STRINGS:
        result |= strings_to_numbers(&val->date1);
        result |= numbers_to_sdn(&val->date1);
@@ -379,7 +449,16 @@ int gedcom_normalize_date(Date_input input, struct date_value *val)
   return result;
 }
 
-struct date_value* gedcom_new_date_value(struct date_value* copy_from)
+/** 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));
@@ -398,22 +477,51 @@ struct date_value* gedcom_new_date_value(struct date_value* copy_from)
   return dv_ptr;
 }
 
+/** This function allows to convert the given \c line_value into a struct
+    date_value.
+    
+    \param line_value A string containing the date to parse
+
+    \return The parsed date; note that this return value is statically
+    allocated, and is thus overwritten on each call.
+*/
 struct date_value gedcom_parse_date(const char* line_value)
 {
+  int result = 0;
+  init_date(&dv_s.date1);
+  init_date(&dv_s.date2);
   init_date(&date_s);
   init_date(&def_date);
   curr_line_value = line_value;
-  init_gedcom_date_lex(line_value);
-  gedcom_date_parse();
-  close_gedcom_date_lex();
-  if (dv_s.date1.cal != CAL_UNKNOWN)
-    numbers_to_sdn(&dv_s.date1);
-  if (dv_s.date2.cal != CAL_UNKNOWN)
-    numbers_to_sdn(&dv_s.date2);
+  if (compat_mode(C_NO_REQUIRED_VALUES)
+      && !strncmp(curr_line_value, VALUE_IF_MISSING, 2)) {
+    gedcom_date_error(_("Empty value changed to '%s'"), VALUE_IF_MISSING);
+    result = 1;
+  }
+  else {
+    compat_date_start();
+    init_gedcom_date_lex(line_value);
+    gedcom_date_parse();
+    close_gedcom_date_lex();
+    if (compat_date_check(&dv_s, &curr_line_value)) {
+      init_gedcom_date_lex(curr_line_value);
+      gedcom_date_parse();
+      close_gedcom_date_lex();
+    }
+    if (dv_s.date1.cal != CAL_UNKNOWN)
+      result |= numbers_to_sdn(&dv_s.date1);
+    if (dv_s.date2.cal != CAL_UNKNOWN)
+      result |= numbers_to_sdn(&dv_s.date2);
+  }
+  if (result != 0) {
+    gedcom_date_error(_("Putting date '%s' in 'phrase' member"),
+                     curr_line_value);
+    make_date_value(DV_PHRASE, &dv_s.date1, &dv_s.date2, curr_line_value);
+  }
   return dv_s;
 }
 
-void write_date(struct date* d)
+void write_date(const struct date* d)
 {
   if (! d->year_str[0] || d->year <= 0 || d->sdn1 <= 0)
     gedcom_error(_("Date is not normalized: some fields are invalid"));
@@ -439,7 +547,15 @@ void write_date(struct date* d)
   }
 }
 
-char* gedcom_date_to_string(struct date_value* val)
+/** This function converts the given struct date_value into its string
+    representation.
+
+    \param val  The given parsed date
+
+    \return The string representation of the parsed date; note that this value
+    is statically allocated, and is thus overwritten on each call
+*/
+char* gedcom_date_to_string(const struct date_value* val)
 {
   init_buffer(&date_buffer);
   reset_buffer(&date_buffer);