QOF 0.8.4
qofdate.c
00001 /********************************************************************
00002  *       qofdate.c - QofDate, 64bit UTC date handling.
00003  *       Rewritten from scratch for QOF 0.7.0
00004  *
00005  *  Fri May  5 15:05:24 2006
00006  *  Copyright (C) 1991, 1993, 1997, 1998, 2002, 2006
00007  *  Free Software Foundation, Inc.
00008  *  This file contains routines modified from the GNU C Library.
00009  ********************************************************************/
00010 /*
00011  *  This program is free software; you can redistribute it and/or modify
00012  *  it under the terms of the GNU General Public License as published by
00013  *  the Free Software Foundation; either version 2 of the License, or
00014  *  (at your option) any later version.
00015  *
00016  *  This program is distributed in the hope that it will be useful,
00017  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  *  GNU General Public License for more details.
00020  *
00021  *  You should have received a copy of the GNU General Public License
00022  *  along with this program; if not, write to the Free Software
00023  *  Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA  02110-1301,  USA
00024  */
00025 
00026 #include "config.h"
00027 #include <glib.h>
00028 #include <glib/gprintf.h>
00029 #include <stdlib.h>
00030 #include <time.h>
00031 #include "qof.h"
00032 #include "qofdate-p.h"
00033 
00034 /* from gnu libc */
00035 #define DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
00036 #define LEAPS_THRU_END_OF(y) (DIV (y, 4) - DIV (y, 100) + DIV (y, 400))
00037 
00038 static GHashTable *DateFormatTable = NULL;
00039 static gboolean QofDateInit = FALSE;
00040 static QofLogModule log_module = QOF_MOD_DATE;
00041 static gchar locale_separator = '\0';
00042 static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
00043 
00044 /* copied from glib */
00045 static const guint16 days_in_year[2][14] = 
00046 {  /* 0, jan feb mar apr may  jun  jul  aug  sep  oct  nov  dec */
00047   {  0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 
00048   {  0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
00049 };
00050 static const guint8 days_in_months[2][13] =
00051 {  /* error, jan feb mar apr may jun jul aug sep oct nov dec */
00052   {  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
00053   {  0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } /* leap year */
00054 };
00055 
00056 /* A single Date Format Entry. */
00057 typedef struct QofDateEntry_s
00058 {
00059     const gchar *format;
00060     const gchar *name;
00061     gchar separator;
00062     QofDateFormat df;
00063     gboolean locale_specific;
00064 } QofDateEntry;
00065 
00066 void
00067 qof_date_init (void)
00068 {
00069     if (!QofDateInit)
00070     {
00071         DateFormatTable = g_hash_table_new (g_direct_hash, g_direct_equal);
00072     }
00073     {
00074         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00075         d->format = "%m/%d/%Y";
00076         d->name = "us";
00077         d->separator = '/';
00078         d->df = QOF_DATE_FORMAT_US;
00079         d->locale_specific = FALSE;
00080         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00081     }
00082     {
00083         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00084         d->format = "%d/%m/%Y";
00085         d->name = "uk";
00086         d->separator = '/';
00087         d->df = QOF_DATE_FORMAT_UK;
00088         d->locale_specific = FALSE;
00089         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00090     }
00091     {
00092         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00093         d->format = "%d.%m.%Y";
00094         d->name = "ce";
00095         d->separator = '.';
00096         d->df = QOF_DATE_FORMAT_CE;
00097         d->locale_specific = FALSE;
00098         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00099     }
00100     {
00101         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00102         d->format = "%F";
00103         d->name = "iso";
00104         d->separator = '-';
00105         d->df = QOF_DATE_FORMAT_ISO;
00106         d->locale_specific = FALSE;
00107         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00108     }
00109     {
00110         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00111         d->format = QOF_UTC_DATE_FORMAT;
00112         d->name = "utc";
00113         d->separator = '-';
00114         d->df = QOF_DATE_FORMAT_UTC;
00115         d->locale_specific = FALSE;
00116         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00117     }
00118     {
00119         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00120         d->format = "%x";
00121         d->name = "locale";
00122         d->separator = locale_separator;
00123         d->df = QOF_DATE_FORMAT_LOCALE;
00124         d->locale_specific = TRUE;
00125         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00126     }
00127     {
00128         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00129         d->format = "%c";
00130         d->name = "custom";
00131         d->separator = locale_separator;
00132         d->df = QOF_DATE_FORMAT_CUSTOM;
00133         d->locale_specific = TRUE;
00134         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00135     }
00136     {
00137         QofDateEntry *d = g_new0(QofDateEntry,1);
00138         d->format = "%Y-%m-%d %H:%M:%S.%N %z";
00139         d->name = "iso8601";
00140         d->separator = '-';
00141         d->df = QOF_DATE_FORMAT_ISO8601;
00142         d->locale_specific = FALSE;
00143         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER(d->df), d);
00144     }
00145     QofDateInit = TRUE;
00146 }
00147 
00148 static void
00149 hash_value_free (gpointer key __attribute__ ((unused)), gpointer value, 
00150     gpointer data __attribute__ ((unused)))
00151 {
00152     g_free (value);
00153 }
00154 
00155 void
00156 qof_date_close (void)
00157 {
00158     if (QofDateInit)
00159     {
00160         g_hash_table_foreach (DateFormatTable, hash_value_free, NULL);
00161         g_hash_table_destroy (DateFormatTable);
00162     }
00163     QofDateInit = FALSE;
00164 }
00165 
00166 guint16
00167 qof_date_get_yday (gint mday, gint month, gint64 year)
00168 {
00169     guint8 leap;
00170 
00171     g_return_val_if_fail (mday  != 0, 0);
00172     g_return_val_if_fail (month != 0, 0);
00173     g_return_val_if_fail (month <= 12, 0);
00174     g_return_val_if_fail (month >= 1, 0);
00175     g_return_val_if_fail (year  != 0, 0);
00176     leap = qof_date_isleap (year);
00177     g_return_val_if_fail (mday <= 
00178         qof_date_get_mday (month, year), 0);
00179     return days_in_year[leap][month] + mday;
00180 }
00181 
00182 guint8
00183 qof_date_get_mday (gint month, gint64 year)
00184 {
00185     g_return_val_if_fail (month !=  0, 0);
00186     g_return_val_if_fail (month <= 12, 0);
00187     g_return_val_if_fail (month >=  1, 0);
00188     g_return_val_if_fail (year  !=  0, 0);
00189     return days_in_months[qof_date_isleap (year)][month];
00190 }
00191 
00192 gboolean
00193 qof_date_is_last_mday (const QofDate *qd)
00194 {
00195     g_return_val_if_fail (qd, FALSE);
00196     g_return_val_if_fail (qd->qd_valid, FALSE);
00197     return (qd->qd_mday == 
00198         qof_date_get_mday (qd->qd_mon, qd->qd_year));
00199 }
00200 
00201 gboolean
00202 qof_date_format_add (const gchar * str, QofDateFormat * identifier)
00203 {
00204     struct tm check;
00205     gint len;
00206     time_t now;
00207     gchar test[MAX_DATE_BUFFER];
00208 
00210     g_return_val_if_fail (QofDateInit, FALSE);
00211     g_return_val_if_fail (str, FALSE);
00212     g_return_val_if_fail (strlen (str) != 0, FALSE);
00213     /* prevent really long strings being passed */
00214     ENTER (" str=%s", str);
00215     if (strlen (str) > MAX_DATE_LENGTH)
00216     {
00217         LEAVE (" '%s' is too long! Max=%d str_len=%d",
00218             str, MAX_DATE_LENGTH, (gint) strlen (str));
00219         return FALSE;
00220     }
00221     /* test the incoming string using the current time. */
00222     now = time (NULL);
00223     test[0] = '\1';
00224     check = *gmtime_r (&now, &check);
00225     /* need to allow time related formats - 
00226     don't use g_date_strftime here. */
00227     len = strftime (test, (MAX_DATE_BUFFER - 1), str, &check);
00228     if (len == 0 && test[0] != '\0')
00229     {
00230         LEAVE (" strftime could not understand '%s'", str);
00231         return FALSE;
00232     }
00233     len = strlen (test);
00234     if (len > MAX_DATE_LENGTH)
00235     {
00236         LEAVE (" %s creates a string '%s' that is too long!"
00237             " Max=%d str_len=%d", str, test, MAX_DATE_LENGTH, len);
00238         return FALSE;
00239     }
00240     *identifier = g_hash_table_size (DateFormatTable) + 1;
00241     {
00242         QofDateEntry *d = g_new0 (QofDateEntry, 1);
00243         d->format = str;
00244         d->name = str;
00245         d->separator = locale_separator;
00246         d->df = *identifier;
00247         g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (d->df), d);
00248     }
00249     LEAVE (" successful");
00250     return TRUE;
00251 }
00252 
00253 const gchar *
00254 qof_date_format_to_name (QofDateFormat format)
00255 {
00256     QofDateEntry *d;
00257 
00258     g_return_val_if_fail (QofDateInit, NULL);
00259     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (format));
00260     if (!d)
00261     {
00262         PERR (" unknown format: '%d'", format);
00263         return NULL;
00264     }
00265     return d->name;
00266 }
00267 
00268 gboolean
00269 qof_date_format_set_name (const gchar * name, QofDateFormat format)
00270 {
00271     QofDateEntry *d;
00272 
00273     g_return_val_if_fail (QofDateInit, FALSE);
00274     if (format <= DATE_FORMAT_LAST)
00275         return FALSE;
00276     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (format));
00277     if (!d)
00278     {
00279         PERR (" unknown format: '%d'", format);
00280         return FALSE;
00281     }
00282     d->name = name;
00283     g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (format), d);
00284     return TRUE;
00285 }
00286 
00287 QofDateFormat
00288 qof_date_format_get_current (void)
00289 {
00290     return dateFormat;
00291 }
00292 
00293 gboolean
00294 qof_date_format_set_current (QofDateFormat df)
00295 {
00296     QofDateEntry *d;
00297 
00298     g_return_val_if_fail (QofDateInit, FALSE);
00299     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
00300     if (!d)
00301     {
00302         PERR (" unknown format: '%d'", df);
00303         return FALSE;
00304     }
00305     dateFormat = d->df;
00306     return TRUE;
00307 }
00308 
00309 const gchar *
00310 qof_date_format_get_format (QofDateFormat df)
00311 {
00312     QofDateEntry *d;
00313 
00314     g_return_val_if_fail (QofDateInit, NULL);
00315     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
00316     if (!d)
00317     {
00318         PERR (" unknown format: '%d'", df);
00319         return NULL;
00320     }
00321     return d->format;
00322 }
00323 
00324 gchar
00325 qof_date_format_get_date_separator (QofDateFormat df)
00326 {
00327     QofDateEntry *d;
00328 
00329     g_return_val_if_fail (QofDateInit, locale_separator);
00330     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
00331     if (!d)
00332     {
00333         PERR (" unknown format: '%d'", df);
00334         return locale_separator;
00335     }
00336     return d->separator;
00337 }
00338 
00339 gboolean
00340 qof_date_format_set_date_separator (const gchar sep, QofDateFormat df)
00341 {
00342     QofDateEntry *d;
00343 
00344     g_return_val_if_fail (QofDateInit, FALSE);
00345     if (df < DATE_FORMAT_LAST)
00346     {
00347         DEBUG (" Prevented attempt to override a default format");
00348         return FALSE;
00349     }
00350     if (g_ascii_isdigit (sep))
00351         return FALSE;
00352     d = g_hash_table_lookup (DateFormatTable, GINT_TO_POINTER (df));
00353     if (!d)
00354     {
00355         PERR (" unknown format: '%d'", df);
00356         return FALSE;
00357     }
00358     d->separator = sep;
00359     g_hash_table_insert (DateFormatTable, GINT_TO_POINTER (df), d);
00360     return TRUE;
00361 }
00362 
00363 struct iter
00364 {
00365     const gchar *name;
00366     QofDateFormat df;
00367 };
00368 
00369 static void
00370 lookup_name (gpointer key __attribute__ ((unused)), gpointer value, 
00371     gpointer data)
00372 {
00373     struct iter *i;
00374     QofDateEntry *d;
00375 
00376     i = (struct iter *) data;
00377     d = (QofDateEntry *) value;
00378     if (0 == safe_strcmp (d->name, i->name))
00379     {
00380         i->df = d->df;
00381     }
00382 }
00383 
00384 QofDateFormat
00385 qof_date_format_from_name (const gchar * name)
00386 {
00387     struct iter i;
00388 
00389     if (!name)
00390         return -1;
00391     if (0 == safe_strcmp (name, "us"))
00392         return QOF_DATE_FORMAT_US;
00393     if (0 == safe_strcmp (name, "uk"))
00394         return QOF_DATE_FORMAT_UK;
00395     if (0 == safe_strcmp (name, "ce"))
00396         return QOF_DATE_FORMAT_CE;
00397     if (0 == safe_strcmp (name, "utc"))
00398         return QOF_DATE_FORMAT_UTC;
00399     if (0 == safe_strcmp (name, "iso"))
00400         return QOF_DATE_FORMAT_ISO;
00401     if (0 == safe_strcmp (name, "locale"))
00402         return QOF_DATE_FORMAT_LOCALE;
00403     if (0 == safe_strcmp (name, "custom"))
00404         return QOF_DATE_FORMAT_CUSTOM;
00405     i.name = name;
00406     i.df = -1;
00407     g_hash_table_foreach (DateFormatTable, lookup_name, &i);
00408     return i.df;
00409 }
00410 
00411 static QofDate*
00412 date_normalise (QofDate * date)
00413 {
00414     gint days, leap;
00415 
00416     g_return_val_if_fail (date, NULL);
00417 
00418     date->qd_sec -= date->qd_gmt_off;
00419     /* if value is negative, just add */
00420     if ((date->qd_nanosecs >= QOF_NSECS) || 
00421         (date->qd_nanosecs <= -QOF_NSECS))
00422     {
00423         date->qd_sec += date->qd_nanosecs / QOF_NSECS;
00424         date->qd_nanosecs = date->qd_nanosecs % QOF_NSECS;
00425         if (date->qd_nanosecs < 0)
00426         {
00427             date->qd_nanosecs += QOF_NSECS;
00428             date->qd_sec--;
00429         }
00430     }
00431     if ((date->qd_sec >= 60) || (date->qd_sec <= -60))
00432     {
00433         date->qd_min += date->qd_sec / 60;
00434         date->qd_sec  = date->qd_sec % 60;
00435         if (date->qd_sec < 0)
00436         {
00437             date->qd_sec += 60;
00438             date->qd_min--;
00439         }
00440     }
00441     if ((date->qd_min >= 60) || (date->qd_min <= -60))
00442     {
00443         date->qd_hour += date->qd_min / 60;
00444         date->qd_min   = date->qd_min % 60;
00445         if (date->qd_min < 0)
00446         {
00447             date->qd_min += 60;
00448             date->qd_hour--;
00449         }
00450     }
00451     /* Year Zero does not exist, 1BC is immediately followed by 1AD. */
00452     if (date->qd_year == 0)
00453         date->qd_year = -1;
00454     /* qd_mon starts at 1, not zero */
00455     if (date->qd_mon == 0)
00456         date->qd_mon = 1;
00457     /* qd_mday starts at 1, not zero */
00458     if (date->qd_mday == 0)
00459         date->qd_mday = 1;
00460     if ((date->qd_hour >= 24) || (date->qd_hour <= -24))
00461     {
00462         date->qd_mday += date->qd_hour / 24;
00463         date->qd_hour  = date->qd_hour % 24;
00464         if (date->qd_hour < 0)
00465         {
00466             date->qd_hour += 24;
00467             date->qd_mday--;
00468         }
00469     }
00470     /* yes, [13] is correct == total at end of month_12 */
00471     leap = days_in_year[qof_date_isleap(date->qd_year)][13];
00472     while (date->qd_mday > leap)
00473     {
00474         date->qd_year++;
00475         leap = days_in_year[qof_date_isleap(date->qd_year)][13];
00476         date->qd_mday -= leap;
00477     }
00478     while (date->qd_mday < (leap*-1))
00479     {
00480         date->qd_year--;
00481         leap = days_in_year[qof_date_isleap(date->qd_year)][13];
00482         date->qd_mday += leap;
00483     }
00484     if ((date->qd_mon > 12) || (date->qd_mon < -12))
00485     {
00486         gint leap =  days_in_year[qof_date_isleap(date->qd_year)][13];
00487         date->qd_year += date->qd_mon / 12;
00488         date->qd_mon   = date->qd_mon % 12;
00489         if (date->qd_mday > leap)
00490             date->qd_mday -= leap;
00491         if (date->qd_mday < (leap*-1))
00492             date->qd_mday += leap;
00493         if (date->qd_mon < 0)
00494         {
00495             /* -1 == Dec, -4 == Sep */
00496             date->qd_mon += 12 + 1;
00497             if (date->qd_year < 0)
00498             {
00499                 date->qd_year++;
00500             }
00501             else
00502             {
00503                 date->qd_year--;
00504             }
00505             /*date->qd_year = (date->qd_year < 0) ? 
00506                 date->qd_year++ : date->qd_year--;*/
00507         }
00508     }
00509     days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
00510     while (date->qd_mday < 0)
00511     {
00512         date->qd_mday += days;
00513         date->qd_mon--;
00514         if (date->qd_mon < 1)
00515         {
00516             date->qd_year -= date->qd_mon / 12;
00517             date->qd_mon   = date->qd_mon % 12;
00518             /* if year was AD and is now zero, reset to BC. */
00519             if ((date->qd_year == 0) && (date->qd_mon < 0))
00520                 date->qd_year = -1;
00521         }
00522         days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
00523     }
00524     while (date->qd_mday > days)
00525     {
00526         date->qd_mday -= days;
00527         date->qd_mon++;
00528         if (date->qd_mon > 11)
00529         {
00530             date->qd_year += date->qd_mon / 12;
00531             date->qd_mon   = date->qd_mon % 12;
00532             /* if year was BC and is now zero, reset to AD. */
00533             if ((date->qd_year == 0) && (date->qd_mon > 0))
00534                 date->qd_year = +1;
00535         }
00536         days = days_in_months[qof_date_isleap(date->qd_year)][date->qd_mon];
00537     }
00538     /* use sensible defaults */
00539     if (date->qd_mday == 0)
00540         date->qd_mday = 1;
00541     if (date->qd_mon == 0)
00542         date->qd_mon = 1;
00543     /* use days_in_year to set yday */
00544     date->qd_yday = (date->qd_mday - 1) + 
00545         days_in_year[qof_date_isleap(date->qd_year)][date->qd_mon];
00546     set_day_of_the_week (date);
00547 
00548     /* qd_year has no realistic limits */
00549     date->qd_valid = TRUE;
00550     date->qd_zone = "GMT";
00551     date->qd_is_dst = 0;
00552     date->qd_gmt_off = 0L;
00553     return date;
00554 }
00555 
00556 QofDate *
00557 qof_date_parse (const gchar * str, QofDateFormat df)
00558 {
00559     const gchar *format;
00560     QofDateError error;
00561     QofDate *date;
00562     gchar * G_GNUC_UNUSED check;
00563 
00564     check = NULL;
00565     error = ERR_NO_ERROR;
00566     date = qof_date_new ();
00567     format = qof_date_format_get_format (df);
00568     check = strptime_internal (str, format, date, &error);
00569     check = NULL;
00570     if (error != ERR_NO_ERROR)
00571     {
00572         qof_date_free (date);
00573         return NULL;
00574     }
00575 
00576     date = date_normalise (date);
00577     return date;
00578 }
00579 
00580 gchar *
00581 qof_date_print (const QofDate * date, QofDateFormat df)
00582 {
00583     size_t result;
00584     gchar temp[MAX_DATE_BUFFER];
00585     QofDateEntry *d;
00586 
00587     g_return_val_if_fail (QofDateInit, NULL);
00588     g_return_val_if_fail (date, NULL);
00589     g_return_val_if_fail (date->qd_valid, NULL);
00590     d = g_hash_table_lookup (DateFormatTable, 
00591         GINT_TO_POINTER (df));
00592     g_return_val_if_fail (d, NULL);
00593     temp[0] = '\1';
00594     result = strftime_case (FALSE, temp, MAX_DATE_BUFFER, 
00595         d->format, date, 1, date->qd_nanosecs);
00596     if (result == 0 && temp[0] != '\0')
00597     {
00598         PERR (" qof extended strftime failed");
00599         return NULL;
00600     }
00601     return g_strndup(temp, result);
00602 }
00603 
00604 /* QofDate handlers */
00605 
00606 QofDate *
00607 qof_date_new (void)
00608 {
00609     QofDate *d;
00610 
00611     d = g_new0 (QofDate, 1);
00612     return d;
00613 }
00614 
00615 QofDate *
00616 qof_date_get_current (void)
00617 {
00618     QofTime *qt;
00619     QofDate *qd;
00620 
00621     qt = qof_time_get_current ();
00622     qd = qof_date_from_qtime (qt);
00623     qof_time_free (qt);
00624     return qd;
00625 }
00626 
00627 QofDate *
00628 qof_date_new_dmy (gint day, gint month, gint64 year)
00629 {
00630     QofDate *qd;
00631 
00632     qd = g_new0 (QofDate, 1);
00633     qd->qd_mday = day;
00634     qd->qd_mon  = month;
00635     qd->qd_year = year;
00636     if(!qof_date_valid (qd))
00637         return NULL;
00638     return qd;
00639 }
00640 
00641 void
00642 qof_date_free (QofDate * date)
00643 {
00644     g_return_if_fail (date);
00645     g_free (date);
00646     date = NULL;
00647 }
00648 
00649 gboolean
00650 qof_date_valid (QofDate *date)
00651 {
00652     g_return_val_if_fail (date, FALSE);
00653     date = date_normalise (date);
00654     if (date->qd_valid == FALSE)
00655     {
00656         PERR (" unknown QofDate error");
00657         return FALSE;
00658     }
00659     return TRUE;
00660 }
00661 
00662 gboolean
00663 qof_date_equal (const QofDate *d1, const QofDate *d2)
00664 {
00665     if (0 == qof_date_compare (d1, d2))
00666         return TRUE;
00667     return FALSE;
00668 }
00669 
00670 gint
00671 qof_date_compare (const QofDate * d1, const QofDate * d2)
00672 {
00673     if ((!d1) && (!d2))
00674         return 0;
00675     if (d1 == d2)
00676         return 0;
00677     if (!d1)
00678         return -1;
00679     if (!d2)
00680         return 1;
00681     if (d1->qd_year < d2->qd_year)
00682         return -1;
00683     if (d1->qd_year > d2->qd_year)
00684         return 1;
00685     if (d1->qd_mon < d2->qd_mon)
00686         return -1;
00687     if (d1->qd_mon > d2->qd_mon)
00688         return 1;
00689     if (d1->qd_mday < d2->qd_mday)
00690         return -1;
00691     if (d1->qd_mday > d2->qd_mday)
00692         return 1;
00693     if (d1->qd_hour < d2->qd_hour)
00694         return -1;
00695     if (d1->qd_hour > d2->qd_hour)
00696         return 1;
00697     if (d1->qd_min < d2->qd_min)
00698         return -1;
00699     if (d1->qd_min > d2->qd_min)
00700         return 1;
00701     if (d1->qd_sec < d2->qd_sec)
00702         return -1;
00703     if (d1->qd_sec > d2->qd_sec)
00704         return 1;
00705     if (d1->qd_nanosecs < d2->qd_nanosecs)
00706         return -1;
00707     if (d1->qd_nanosecs > d2->qd_nanosecs)
00708         return 1;
00709     return 0;
00710 }
00711 
00712 QofDate *
00713 qof_date_from_struct_tm (const struct tm *stm)
00714 {
00715     QofDate *d;
00716 
00717     g_return_val_if_fail (stm, NULL);
00718     d = g_new0 (QofDate, 1);
00719     d->qd_sec  = stm->tm_sec;
00720     d->qd_min  = stm->tm_min;
00721     d->qd_hour = stm->tm_hour;
00722     d->qd_mday = stm->tm_mday;
00723     d->qd_mon  = stm->tm_mon + 1;
00724     d->qd_year = stm->tm_year + 1900;
00725     d->qd_wday = stm->tm_wday;
00726     d->qd_yday = stm->tm_yday;
00727     d->qd_is_dst = stm->tm_isdst;
00728     d->qd_gmt_off = stm->tm_gmtoff;
00729     d->qd_zone = stm->tm_zone;
00730     d->qd_valid = TRUE;
00731     d = date_normalise(d);
00732     return d;
00733 }
00734 
00735 gboolean
00736 qof_date_to_struct_tm (const QofDate * qd, struct tm * stm, 
00737                        glong *nanosecs)
00738 {
00739     g_return_val_if_fail (qd, FALSE);
00740     g_return_val_if_fail (stm, FALSE);
00741     g_return_val_if_fail (qd->qd_valid, FALSE);
00742     if ((qd->qd_year > G_MAXINT) || (qd->qd_year < 1900))
00743     {
00744         PERR (" date too large for struct tm");
00745         return FALSE;
00746     }
00747     stm->tm_sec  = qd->qd_sec;
00748     stm->tm_min  = qd->qd_min;
00749     stm->tm_hour = qd->qd_hour;
00750     stm->tm_mday = qd->qd_mday;
00751     stm->tm_mon  = qd->qd_mon - 1;
00752     stm->tm_year = qd->qd_year - 1900;
00753     stm->tm_wday = qd->qd_wday;
00754     stm->tm_yday = qd->qd_yday;
00755     stm->tm_isdst = qd->qd_is_dst;
00756     stm->tm_gmtoff = qd->qd_gmt_off;
00757     stm->tm_zone = qd->qd_zone;
00758     if (nanosecs != NULL)
00759         *nanosecs = qd->qd_nanosecs;
00760     return TRUE;
00761 }
00762 
00763 gboolean
00764 qof_date_to_gdate (const QofDate *qd, GDate *gd)
00765 {
00766     g_return_val_if_fail (qd, FALSE);
00767     g_return_val_if_fail (gd, FALSE);
00768     g_return_val_if_fail (qd->qd_valid, FALSE);
00769     if (qd->qd_year >= G_MAXUINT16)
00770     {
00771         PERR (" QofDate out of range of GDate");
00772         return FALSE;
00773     }
00774     if (!g_date_valid_dmy (qd->qd_mday, qd->qd_mon, qd->qd_year))
00775     {
00776         PERR (" GDate failed to allow day, month and/or year");
00777         return FALSE;
00778     }
00779     g_date_set_dmy (gd, qd->qd_mday, qd->qd_mon, qd->qd_year);
00780     return TRUE;
00781 }
00782 
00783 QofDate *
00784 qof_date_from_gdate (const GDate *date)
00785 {
00786     QofDate * qd;
00787 
00788     g_return_val_if_fail (g_date_valid (date), NULL);
00789     qd = qof_date_new ();
00790     qd->qd_year = g_date_get_year (date);
00791     qd->qd_mon  = g_date_get_month (date);
00792     qd->qd_mday = g_date_get_day (date);
00793     qd = date_normalise (qd);
00794     return qd;
00795 }
00796 
00797 static void
00798 qof_date_offset (const QofTime *time, glong offset, QofDate *qd)
00799 {
00800     glong days;
00801     gint64 rem, y, yg;
00802     const guint16 *ip;
00803     QofTimeSecs t;
00804 
00805     g_return_if_fail (qd);
00806     g_return_if_fail (time);
00807     t = qof_time_get_secs ((QofTime*)time);
00808     days = t / SECS_PER_DAY;
00809     rem = t % SECS_PER_DAY;
00810     rem += offset;
00811     while (rem < 0)
00812     {
00813         rem += SECS_PER_DAY;
00814         --days;
00815     }
00816     while (rem >= SECS_PER_DAY)
00817     {
00818         rem -= SECS_PER_DAY;
00819         ++days;
00820     }
00821     qd->qd_hour = rem / SECS_PER_HOUR;
00822     rem %= SECS_PER_HOUR;
00823     qd->qd_min = rem / 60;
00824     qd->qd_sec = rem % 60;
00825     /* January 1, 1970 was a Thursday.  */
00826     qd->qd_wday = (4 + days) % 7;
00827     if (qd->qd_wday < 0)
00828         qd->qd_wday += 7;
00829     y = 1970;
00830     while (days < 0 || days >= (__isleap (y) ? 366 : 365))
00831     {
00832         /* Guess a corrected year, assuming 365 days per year.  */
00833         yg = y + days / 365 - (days % 365 < 0);
00834         /* Adjust DAYS and Y to match the guessed year.  */
00835         days -= ((yg - y) * 365
00836             + LEAPS_THRU_END_OF (yg - 1)
00837             - LEAPS_THRU_END_OF (y - 1));
00838         y = yg;
00839     }
00840     qd->qd_year = y;
00841     qd->qd_yday = days;
00842     ip = days_in_year[qof_date_isleap(y)];
00843     for (y = 12; days < (glong) ip[y]; --y)
00844         continue;
00845     days -= ip[y];
00846     qd->qd_mon = y;
00847     qd->qd_mday = days + 1;
00848 }
00849 
00850 /* safe to use time_t here because only values
00851 within the range of a time_t have any leapseconds. */
00852 static gint
00853 count_leapseconds (time_t interval)
00854 {
00855     time_t altered;
00856     struct tm utc;
00857 
00858     altered = interval;
00859     utc = *gmtime_r (&interval, &utc);
00860     altered = mktime (&utc);
00861     return altered - interval;
00862 }
00863 
00864 /*static inline gint*/
00865 static gint
00866 extract_interval (const QofTime *qt)
00867 {
00868     gint leap_seconds;
00869     QofTimeSecs t, l;
00870     const QofTime *now;
00871 
00872     leap_seconds = 0;
00873     t = qof_time_get_secs (qt);
00874     now = qof_time_get_current ();
00875     l = (qof_time_get_secs (now) > G_MAXINT32) ? 
00876         G_MAXINT32 : qof_time_get_secs (now);
00877     leap_seconds = ((t > l) || (t < 0)) ? 
00878         count_leapseconds (l) :
00879         count_leapseconds (t);
00880     return leap_seconds;
00881 }
00882 
00883 QofDate *
00884 qof_date_from_qtime (const QofTime *qt)
00885 {
00886     QofDate *qd;
00887     gint leap_extra_secs;
00888 
00889     /* may not want to create a new time or date - it
00890     complicates memory management. */
00891     g_return_val_if_fail (qt, NULL);
00892     g_return_val_if_fail (qof_time_is_valid (qt), NULL);
00893     qd = qof_date_new ();
00894     leap_extra_secs = 0;
00895     setenv ("TZ", "GMT", 1);
00896     tzset();
00897     leap_extra_secs = extract_interval (qt);
00898     qof_date_offset (qt, leap_extra_secs, qd);
00899     qd->qd_nanosecs = qof_time_get_nanosecs (qt);
00900     qd->qd_is_dst = 0;
00901     qd->qd_zone = "GMT";
00902     qd->qd_gmt_off = 0L;
00903     if (!qof_date_valid(qd))
00904         return NULL;
00905     return qd;
00906 }
00907 
00908 gint64
00909 days_between (gint64 year1, gint64 year2)
00910 {
00911     gint64 i, start, end, l;
00912 
00913     l = 0;
00914     if (year1 == year2)
00915         return l;
00916     start = (year1 < year2) ? year1 : year2;
00917     end = (year2 < year1) ? year1: year2;
00918     for (i = start; i < end; i++)
00919     {
00920         l += (qof_date_isleap(i)) ? 366 : 365;
00921     }
00922     return l;
00923 }
00924 
00925 QofTime*
00926 qof_date_to_qtime (const QofDate *qd)
00927 {
00928     QofTime *qt;
00929     QofTimeSecs c;
00930 
00931     g_return_val_if_fail (qd, NULL);
00932     g_return_val_if_fail (qd->qd_valid, NULL);
00933     c = 0;
00934     qt = NULL;
00935     if (qd->qd_year < 1970)
00936     {
00937         c = qd->qd_sec;
00938         c += QOF_MIN_TO_SEC(qd->qd_min);
00939         c += QOF_HOUR_TO_SEC(qd->qd_hour);
00940         c += QOF_DAYS_TO_SEC(qd->qd_yday);
00941         c -= QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
00942         c -= qd->qd_gmt_off;
00943         qt = qof_time_set (c, qd->qd_nanosecs);
00944     }
00945     if (qd->qd_year >= 1970)
00946     {
00947         c = qd->qd_sec;
00948         c += QOF_MIN_TO_SEC(qd->qd_min);
00949         c += QOF_HOUR_TO_SEC(qd->qd_hour);
00950         c += QOF_DAYS_TO_SEC(qd->qd_yday);
00951         c += QOF_DAYS_TO_SEC(days_between (1970, qd->qd_year));
00952         c -= qd->qd_gmt_off;
00953         qt = qof_time_set (c, qd->qd_nanosecs);
00954     }
00955     return qt;
00956 }
00957 
00958 QofTime *
00959 qof_date_time_difference (const QofDate * date1, 
00960     const QofDate * date2)
00961 {
00962     gint64 days;
00963     QofTime *secs;
00964 
00965     secs = qof_time_new ();
00966     days = days_between (date1->qd_year, date2->qd_year);
00967     qof_time_add_secs(secs, QOF_DAYS_TO_SEC(days));
00968     if (days >= 0)
00969     {
00970         /* positive value, add date2 secs, subtract date1 */
00971         qof_time_add_secs(secs, -1 *
00972                 (QOF_HOUR_TO_SEC(date1->qd_hour) -
00973                 QOF_MIN_TO_SEC(date1->qd_min) -
00974                 (date1->qd_sec)));
00975         qof_time_add_secs(secs,
00976                 QOF_HOUR_TO_SEC(date2->qd_hour) +
00977                 QOF_MIN_TO_SEC(date2->qd_min) +
00978                 (date2->qd_sec));
00979         qof_time_set_nanosecs(secs, 
00980             (date1->qd_nanosecs - date2->qd_nanosecs));
00981     }
00982     if (days < 0)
00983     {
00984         /* negative value*/
00985         qof_time_add_secs (secs, 
00986                 QOF_HOUR_TO_SEC(date1->qd_hour) -
00987                 QOF_MIN_TO_SEC(date1->qd_min) -
00988                 (date1->qd_sec));
00989         qof_time_add_secs (secs, -1 * 
00990                 (QOF_HOUR_TO_SEC(date2->qd_hour) +
00991                 QOF_MIN_TO_SEC(date2->qd_min) +
00992                 (date2->qd_sec)));
00993         qof_time_set_nanosecs(secs, 
00994             (date2->qd_nanosecs - date1->qd_nanosecs));
00995     }
00996     return secs;
00997 }
00998 
00999 gboolean
01000 qof_date_adddays (QofDate * qd, gint days)
01001 {
01002     g_return_val_if_fail (qd, FALSE);
01003     g_return_val_if_fail (qof_date_valid (qd), FALSE);
01004     qd->qd_mday += days;
01005     return qof_date_valid (qd);
01006 }
01007 
01008 gboolean
01009 qof_date_addmonths (QofDate * qd, gint months,
01010     gboolean track_last_day)
01011 {
01012     g_return_val_if_fail (qd, FALSE);
01013     g_return_val_if_fail (qof_date_valid (qd), FALSE);
01014     qd->qd_mon += months % 12;
01015     qd->qd_year += months / 12;
01016     g_return_val_if_fail (qof_date_valid (qd), FALSE);
01017     if (track_last_day && qof_date_is_last_mday (qd))
01018     {
01019         qd->qd_mday = qof_date_get_mday (qd->qd_mon,
01020             qd->qd_year);
01021     }
01022     return TRUE;
01023 }
01024 
01025 inline gboolean
01026 qof_date_set_day_end (QofDate * qd)
01027 {
01028     qd->qd_hour = 23;
01029     qd->qd_min  = 59;
01030     qd->qd_sec  = 59;
01031     qd->qd_nanosecs = (QOF_NSECS - 1);
01032     return qof_date_valid (qd);
01033 }
01034 
01035 inline gboolean
01036 qof_date_set_day_start (QofDate * qd)
01037 {
01038     g_return_val_if_fail (qd, FALSE);
01039     qd->qd_hour = 0;
01040     qd->qd_min  = 0;
01041     qd->qd_sec  = 0;
01042     qd->qd_nanosecs = G_GINT64_CONSTANT(0);
01043     return qof_date_valid (qd);
01044 }
01045 
01046 inline gboolean
01047 qof_date_set_day_middle (QofDate * qd)
01048 {
01049     g_return_val_if_fail (qd, FALSE);
01050     qd->qd_hour = 12;
01051     qd->qd_min  = 0;
01052     qd->qd_sec = 0;
01053     qd->qd_nanosecs = G_GINT64_CONSTANT(0);
01054     return qof_date_valid (qd);
01055 }
01056 
01057 /******************** END OF FILE *************************/