gnc-date.c

00001 /********************************************************************\
00002  * gnc-date.c -- misc utility functions to handle date and time     * 
00003  *         (to be renamed qofdate.c in libqof2)                     *
00004  *                                                                  *
00005  * Copyright (C) 1997 Robin D. Clark <rclark@cs.hmc.edu>            *
00006  * Copyright (C) 1998-2000, 2003 Linas Vepstas <linas@linas.org>    *
00007  *                                                                  *
00008  * This program is free software; you can redistribute it and/or    *
00009  * modify it under the terms of the GNU General Public License as   *
00010  * published by the Free Software Foundation; either version 2 of   *
00011  * the License, or (at your option) any later version.              *
00012  *                                                                  *
00013  * This program is distributed in the hope that it will be useful,  *
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00016  * GNU General Public License for more details.                     *
00017  *                                                                  *
00018  * You should have received a copy of the GNU General Public License*
00019  * along with this program; if not, contact:                        *
00020  *                                                                  *
00021  * Free Software Foundation           Voice:  +1-617-542-5942       *
00022  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00023  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00024  *                                                                  *
00025 \********************************************************************/
00026 
00027 #define _GNU_SOURCE
00028 #define __EXTENSIONS__
00029 
00030 #include "config.h"
00031 #include <glib.h>
00032 /* to be renamed qofdate.c */
00033 #include <ctype.h>
00034 
00035 #ifdef HAVE_LANGINFO_H
00036 #define HAVE_LANGINFO_D_FMT 1
00037 #endif
00038 
00039 #ifdef HAVE_LANGINFO_D_FMT
00040 #include <langinfo.h>
00041 #endif
00042 
00043 #include <stdio.h>
00044 #include <stdlib.h>
00045 #include <string.h>
00046 #include <time.h>
00047 
00048 #include <glib.h>
00049 
00050 #include "gnc-date.h"
00051 #include "qof.h"
00052 
00053 #ifndef HAVE_STRPTIME
00054 #include "strptime.h"
00055 #endif
00056 #ifndef HAVE_LOCALTIME_R
00057 #include "localtime_r.h"
00058 #endif
00059 
00060 #define NANOS_PER_SECOND 1000000000
00061 
00062 #ifdef HAVE_LANGINFO_D_FMT
00063 #  define GNC_D_FMT (nl_langinfo (D_FMT))
00064 #  define GNC_D_T_FMT (nl_langinfo (D_T_FMT))
00065 #  define GNC_T_FMT (nl_langinfo (T_FMT))
00066 #else
00067 #  define GNC_D_FMT "%Y-%m-%d"
00068 #  define GNC_D_T_FMT "%Y-%m-%d %r"
00069 #  define GNC_T_FMT "%r"
00070 #endif
00071 
00072 
00073 /* This is now user configured through the gnome options system() */
00074 static QofDateFormat dateFormat = QOF_DATE_FORMAT_LOCALE;
00075 static QofDateFormat prevQofDateFormat = QOF_DATE_FORMAT_LOCALE;
00076 
00077 /* This static indicates the debugging module that this .o belongs to. */
00078 static QofLogModule log_module = QOF_MOD_ENGINE;
00079 
00080 /********************************************************************\
00081 \********************************************************************/
00082 
00083 const char*
00084 gnc_date_dateformat_to_string(QofDateFormat format)
00085 {
00086   switch (format) {
00087   case QOF_DATE_FORMAT_US:
00088     return "us";
00089   case QOF_DATE_FORMAT_UK:
00090     return "uk";
00091   case QOF_DATE_FORMAT_CE:
00092     return "ce";
00093   case QOF_DATE_FORMAT_ISO:
00094     return "iso";
00095   case QOF_DATE_FORMAT_UTC:
00096    return "utc";
00097   case QOF_DATE_FORMAT_LOCALE:
00098     return "locale";
00099   case QOF_DATE_FORMAT_CUSTOM:
00100     return "custom";
00101   default:
00102     return NULL;    
00103   }
00104 }
00105 
00106 gboolean
00107 gnc_date_string_to_dateformat(const char* fmt_str, QofDateFormat *format)
00108 {
00109   if (!fmt_str)
00110     return TRUE;
00111 
00112   if (!strcmp(fmt_str, "us"))
00113     *format = QOF_DATE_FORMAT_US;
00114   else if (!strcmp(fmt_str, "uk"))
00115     *format = QOF_DATE_FORMAT_UK;
00116   else if (!strcmp(fmt_str, "ce"))
00117     *format = QOF_DATE_FORMAT_CE;
00118   else if (!strcmp(fmt_str, "utc"))
00119     *format = QOF_DATE_FORMAT_UTC;
00120   else if (!strcmp(fmt_str, "iso"))
00121     *format = QOF_DATE_FORMAT_ISO;
00122   else if (!strcmp(fmt_str, "locale"))
00123     *format = QOF_DATE_FORMAT_LOCALE;
00124   else if (!strcmp(fmt_str, "custom"))
00125     *format = QOF_DATE_FORMAT_CUSTOM;
00126   else
00127     return TRUE;
00128 
00129   return FALSE;
00130 }
00131 
00132 
00133 const char*
00134 gnc_date_monthformat_to_string(GNCDateMonthFormat format)
00135 {
00136   switch (format) {
00137   case GNCDATE_MONTH_NUMBER:
00138     return "number";
00139   case GNCDATE_MONTH_ABBREV:
00140     return "abbrev";
00141   case GNCDATE_MONTH_NAME:
00142     return "name";
00143   default:
00144     return NULL;
00145   }
00146 }
00147 
00148 gboolean
00149 gnc_date_string_to_monthformat(const char *fmt_str, GNCDateMonthFormat *format)
00150 {
00151   if (!fmt_str)
00152     return TRUE;
00153 
00154   if (!strcmp(fmt_str, "number"))
00155     *format = GNCDATE_MONTH_NUMBER;
00156   else if (!strcmp(fmt_str, "abbrev"))
00157     *format = GNCDATE_MONTH_ABBREV;
00158   else if (!strcmp(fmt_str, "name"))
00159     *format = GNCDATE_MONTH_NAME;
00160   else
00161     return TRUE;
00162 
00163   return FALSE;
00164 }
00165 
00166 /********************************************************************\
00167 \********************************************************************/
00168 
00169 static void
00170 timespec_normalize(Timespec *t)
00171 {
00172   if(t->tv_nsec > NANOS_PER_SECOND)
00173   {
00174     t->tv_sec+= (t->tv_nsec / NANOS_PER_SECOND);
00175     t->tv_nsec= t->tv_nsec % NANOS_PER_SECOND;
00176   }
00177 
00178   if(t->tv_nsec < - NANOS_PER_SECOND)
00179   {
00180     t->tv_sec+= - (-t->tv_nsec / NANOS_PER_SECOND);
00181     t->tv_nsec = - (-t->tv_nsec % NANOS_PER_SECOND);
00182   }
00183 
00184   if (t->tv_sec > 0 && t->tv_nsec < 0)
00185   {
00186     t->tv_sec--;
00187     t->tv_nsec = NANOS_PER_SECOND + t->tv_nsec;
00188   }
00189   
00190   if (t->tv_sec < 0 && t->tv_nsec > 0)
00191   {
00192     t->tv_sec++;
00193     t->tv_nsec = - NANOS_PER_SECOND + t->tv_nsec;
00194   }
00195   return;
00196 }
00197   
00198 
00199 gboolean
00200 timespec_equal (const Timespec *ta, const Timespec *tb)
00201 {
00202   if(ta == tb) return TRUE;
00203   if(ta->tv_sec != tb->tv_sec) return FALSE;
00204   if(ta->tv_nsec != tb->tv_nsec) return FALSE;
00205   return TRUE;
00206 }
00207 
00208 gint
00209 timespec_cmp(const Timespec *ta, const Timespec *tb)
00210 {
00211   if(ta == tb) return 0;
00212   if(ta->tv_sec < tb->tv_sec) return -1;
00213   if(ta->tv_sec > tb->tv_sec) return 1;
00214   if(ta->tv_nsec < tb->tv_nsec) return -1;
00215   if(ta->tv_nsec > tb->tv_nsec) return 1;
00216   return 0;
00217 }
00218 
00219 Timespec
00220 timespec_diff(const Timespec *ta, const Timespec *tb)
00221 {
00222   Timespec retval;
00223   retval.tv_sec = ta->tv_sec - tb->tv_sec;
00224   retval.tv_nsec = ta->tv_nsec - tb->tv_nsec;
00225   timespec_normalize(&retval);
00226   return retval;
00227 }
00228 
00229 Timespec
00230 timespec_abs(const Timespec *t)
00231 {
00232   Timespec retval = *t;
00233 
00234   timespec_normalize(&retval);
00235   if (retval.tv_sec < 0)
00236   {
00237     retval.tv_sec = - retval.tv_sec;
00238     retval.tv_nsec = - retval.tv_nsec;
00239   }
00240   
00241   return retval;
00242 }
00243 
00244 /* Converts any time on a day to midday that day.
00245 
00246  * given a timepair contains any time on a certain day (local time)
00247  * converts it to be midday that day.
00248  */
00249 Timespec
00250 timespecCanonicalDayTime(Timespec t)
00251 {
00252   struct tm tm, *result;
00253   Timespec retval;
00254   time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
00255   result = localtime(&t_secs);
00256   tm = *result;
00257   gnc_tm_set_day_middle(&tm);
00258   retval.tv_sec = mktime(&tm);
00259   retval.tv_nsec = 0;
00260   return retval;
00261 }
00262 
00263 int gnc_date_my_last_mday (int month, int year)
00264 {
00265   static int last_day_of_month[2][12] = {
00266   /* non leap */ {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
00267   /*   leap   */ {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
00268   };
00269 
00270   /* Is this a leap year? */
00271   if (year % 2000 == 0) return last_day_of_month[1][month-1];
00272   if (year % 400 == 0 ) return last_day_of_month[0][month-1];
00273   if (year % 4   == 0 ) return last_day_of_month[1][month-1];
00274   return last_day_of_month[0][month-1];
00275 }
00276 
00277 /* Retrieve the last numerical day of the month
00278 
00279  Retrieve the last numerical day for the month specified in the
00280  tm_year and tm_mon fields.
00281 
00282 param  tm: the time value in question
00283 return the last day of the month, integer.
00284 */
00285 int date_get_last_mday(struct tm *tm)
00286 {
00287   return gnc_date_my_last_mday (tm->tm_mon+1, tm->tm_year+1900);
00288 }
00289 
00290 /* Determines if tm_mday is the last day of the month.
00291 
00292  Determines whether the tm_mday field contains the last day of the
00293  month as specified in the tm_year and tm_mon fields.
00294 param  tm: the time value in question
00295 return TRUE if tm_mday matches the last day of the month, else FALSE.
00296 */
00297 gboolean date_is_last_mday(struct tm *tm)
00298 {
00299   return(tm->tm_mday == date_get_last_mday(tm));
00300 }
00301 
00302 /* Add a number of months to a time value
00303 
00304  Add a number of months to a time value, and normalize.  Optionally
00305  also track the last day of the month, i.e. 1/31 -> 2/28 -> 3/30.
00306 
00307 param  tm: base time value
00308 param  months: The number of months to add to this time
00309 param  track_last_day: Coerce the date value if necessary.
00310 
00311 return void
00312 */
00313 void date_add_months (struct tm *tm, int months, gboolean track_last_day)
00314 {
00315   gboolean was_last_day;
00316   int new_last_mday;
00317 
00318   /* Have to do this now */
00319   was_last_day = date_is_last_mday(tm);
00320 
00321   /* Add in the months and normalize */
00322   tm->tm_mon += months;
00323   while (tm->tm_mon > 11) {
00324     tm->tm_mon -= 12;
00325     tm->tm_year++;
00326   }
00327 
00328   if (!track_last_day)
00329     return;
00330 
00331   /* Track last day of the month, i.e. 1/31 -> 2/28 -> 3/31 */
00332   new_last_mday = date_get_last_mday(tm);
00333   if (was_last_day || (tm->tm_mday > new_last_mday))
00334     tm->tm_mday = new_last_mday;
00335 }
00336 
00337 /* Return the set dateFormat.
00338 
00339 return QofDateFormat: enumeration indicating preferred format
00340 
00341 Global: dateFormat
00342 */
00343 QofDateFormat qof_date_format_get (void)
00344 {
00345   return dateFormat;
00346 }
00347 
00348 /* set date format
00349 
00350 set date format to one of US, UK, CE, ISO OR UTC
00351 checks to make sure it's a legal value
00352 
00353 param QofDateFormat: enumeration indicating preferred format
00354 
00355 return void
00356 
00357 Globals: dateFormat
00358 */
00359 void qof_date_format_set(QofDateFormat df)
00360 {
00361   if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST)
00362   {
00363     prevQofDateFormat = dateFormat;
00364     dateFormat = df;
00365   }
00366   else
00367   {    /* hack alert - Use a neutral default. */
00368     PERR("non-existent date format set attempted. Setting ISO default");
00369     prevQofDateFormat = dateFormat;
00370     dateFormat = QOF_DATE_FORMAT_ISO;
00371   }
00372 
00373   return;
00374 }
00375 
00376 /*
00377  qof_date_format_get_string
00378  get the date format string for the current format
00379  returns: string
00380 
00381  Globals: dateFormat
00382 */
00383 const gchar *qof_date_format_get_string(QofDateFormat df)
00384 {
00385   switch(df) {
00386    case QOF_DATE_FORMAT_US:
00387     return "%m/%d/%y";
00388    case QOF_DATE_FORMAT_UK:
00389     return "%d/%m/%y";
00390    case QOF_DATE_FORMAT_CE:
00391     return "%d.%m.%y";
00392    case QOF_DATE_FORMAT_UTC:
00393     return "%Y-%m-%dT%H:%M:%SZ";
00394    case QOF_DATE_FORMAT_ISO:
00395     return "%y-%m-%d";
00396    case QOF_DATE_FORMAT_LOCALE:
00397    default:
00398     return GNC_D_FMT;
00399   };
00400 }
00401 
00402 /* get the date format string for the current format
00403 
00404 get the date format string for the current format
00405 
00406 param df Required date format.
00407 return string
00408 
00409 Globals: dateFormat
00410 */
00411 const gchar *qof_date_text_format_get_string(QofDateFormat df)
00412 {
00413   switch(df) {
00414    case QOF_DATE_FORMAT_US:
00415     return "%b %d, %y";
00416    case QOF_DATE_FORMAT_UK:
00417    case QOF_DATE_FORMAT_CE:
00418     return "%d %b, %y";
00419    case QOF_DATE_FORMAT_UTC:
00420     return "%Y-%m-%dT%H:%M:%SZ";
00421    case QOF_DATE_FORMAT_ISO:
00422     return "%y-%b-%d";
00423    case QOF_DATE_FORMAT_LOCALE:
00424    default:
00425     return GNC_D_FMT;
00426   };
00427 }
00428 
00429 /* Convert day, month and year values to a date string
00430 
00431   Convert a date as day / month / year integers into a localized string
00432   representation
00433 
00434 param   buff - pointer to previously allocated character array; its size
00435          must be at lease MAX_DATE_LENTH bytes.
00436 param   day - value to be set with the day of the month as 1 ... 31
00437 param   month - value to be set with the month of the year as 1 ... 12
00438 param   year - value to be set with the year (4-digit)
00439 
00440 return void
00441 
00442 Globals: global dateFormat value
00443 */
00444 size_t
00445 qof_print_date_dmy_buff (char * buff, size_t len, int day, int month, int year)
00446 {
00447   int flen;
00448   if (!buff) return 0;
00449 
00450   /* Note that when printing year, we use %-4d in format string;
00451    * this causes a one, two or three-digit year to be left-adjusted
00452    * when printed (i.e. padded with blanks on the right).  This is 
00453    * important while the user is editing the year, since erasing a 
00454    * digit can temporarily cause a three-digit year, and having the 
00455    * blank on the left is a real pain for the user.  So pad on the 
00456    * right.
00457    */
00458   switch(dateFormat)
00459   {
00460     case QOF_DATE_FORMAT_UK:
00461       flen = g_snprintf (buff, len, "%2d/%2d/%-4d", day, month, year);
00462       break;
00463     case QOF_DATE_FORMAT_CE:
00464       flen = g_snprintf (buff, len, "%2d.%2d.%-4d", day, month, year);
00465       break;
00466    case QOF_DATE_FORMAT_LOCALE:
00467       {
00468         struct tm tm_str;
00469         time_t t;
00470 
00471         tm_str.tm_mday = day;
00472         tm_str.tm_mon = month - 1;    /* tm_mon = 0 through 11 */
00473         tm_str.tm_year = year - 1900; /* this is what the standard 
00474                                        * says, it's not a Y2K thing */
00475 
00476         gnc_tm_set_day_start (&tm_str);
00477         t = mktime (&tm_str);
00478         localtime_r (&t, &tm_str);
00479         flen = strftime (buff, len, GNC_D_FMT, &tm_str);
00480        if (flen != 0)
00481          break;
00482       }
00483       /* FALLTHROUGH */
00484     case QOF_DATE_FORMAT_ISO:
00485     case QOF_DATE_FORMAT_UTC:
00486       flen = g_snprintf (buff, len, "%04d-%02d-%02d", year, month, day);
00487       break;
00488     case QOF_DATE_FORMAT_US:
00489     default:
00490       flen = g_snprintf (buff, len, "%2d/%2d/%-4d", month, day, year);
00491       break;
00492   }
00493 
00494   return flen;
00495 }
00496 
00497 size_t
00498 qof_print_date_buff (char * buff, size_t len, time_t t)
00499 {
00500   struct tm *theTime;
00501 
00502   if (!buff) return 0 ;
00503 
00504   theTime = localtime (&t);
00505 
00506   return qof_print_date_dmy_buff (buff, len,
00507                    theTime->tm_mday, 
00508                    theTime->tm_mon + 1,
00509                    theTime->tm_year + 1900);
00510 }
00511 
00512 size_t
00513 qof_print_gdate( char *buf, size_t len, GDate *gd )
00514 {
00515   return qof_print_date_dmy_buff( buf, len,
00516              g_date_get_day(gd),
00517              g_date_get_month(gd),
00518              g_date_get_year(gd) );
00519 }
00520 
00521 char * 
00522 qof_print_date (time_t t)
00523 {
00524    char buff[MAX_DATE_LENGTH];
00525    qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
00526    return g_strdup (buff);
00527 }
00528 
00529 const char *
00530 gnc_print_date (Timespec ts)
00531 {
00532   static char buff[MAX_DATE_LENGTH];
00533   time_t t;
00534 
00535   t = ts.tv_sec + (ts.tv_nsec / 1000000000.0);
00536 
00537   qof_print_date_buff (buff, MAX_DATE_LENGTH, t);
00538 
00539   return buff;
00540 }
00541 
00542 /* ============================================================== */
00543 
00544 size_t
00545 qof_print_hours_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
00546 {
00547         size_t flen;
00548         if (0 <= secs)
00549         {
00550                 if (show_secs)
00551                 {
00552                         flen = g_snprintf(buff, len,
00553                            "%02d:%02d:%02d", (int)(secs / 3600),
00554                            (int)((secs % 3600) / 60), (int)(secs % 60));
00555                 }
00556                 else
00557                 {
00558                         flen = g_snprintf(buff, len, 
00559                            "%02d:%02d", (int)(secs / 3600),
00560                            (int)((secs % 3600) / 60));
00561                 }
00562         } 
00563         else 
00564         {
00565                 if (show_secs)
00566                 {
00567                         flen = g_snprintf(buff, len,
00568                            "-%02d:%02d:%02d", (int)(-secs / 3600),
00569                            (int)((-secs % 3600) / 60), (int)(-secs % 60));
00570                 }
00571                 else
00572                 {
00573                         flen = g_snprintf(buff, len,
00574                            "-%02d:%02d", (int)(-secs / 3600),
00575                            (int)((-secs % 3600) / 60));
00576                 }
00577         }
00578         return flen;
00579 }
00580 
00581 /* ============================================================== */
00582 
00583 size_t
00584 qof_print_minutes_elapsed_buff (char * buff, size_t len, int secs, gboolean show_secs)
00585 {
00586         size_t flen;
00587         if (0 <= secs)
00588         {
00589                 if (show_secs)
00590                 {
00591                         flen = g_snprintf(buff, len,
00592                            "%02d:%02d", 
00593                                 (int)(secs / 60), (int)(secs % 60));
00594                 }
00595                 else
00596                 {
00597                         flen = g_snprintf(buff, len, 
00598                            "%02d", (int)(secs / 60));
00599                 }
00600         } 
00601         else 
00602         {
00603                 if (show_secs)
00604                 {
00605                         flen = g_snprintf(buff, len,
00606                            "-%02d:%02d", (int)(-secs / 60), (int)(-secs % 60));
00607                 }
00608                 else
00609                 {
00610                         flen = g_snprintf(buff, len,
00611                            "-%02d", (int)(-secs / 60));
00612                 }
00613         }
00614         return flen;
00615 }
00616 
00617 /* ============================================================== */
00618 
00619 size_t
00620 qof_print_date_time_buff (char * buff, size_t len, time_t secs)
00621 {
00622   int flen;
00623   int day, month, year, hour, min, sec;
00624   struct tm ltm, gtm;
00625   
00626   if (!buff) return 0;
00627 
00628   /* Note that when printing year, we use %-4d in format string;
00629    * this causes a one, two or three-digit year to be left-adjusted
00630    * when printed (i.e. padded with blanks on the right).  This is 
00631    * important while the user is editing the year, since erasing a 
00632    * digit can temporarily cause a three-digit year, and having the 
00633    * blank on the left is a real pain for the user.  So pad on the 
00634    * right.
00635    */
00636   ltm = *localtime (&secs);
00637   day = ltm.tm_mday;
00638   month = ltm.tm_mon +1;
00639   year = ltm.tm_year +1900;
00640   hour = ltm.tm_hour;
00641   min = ltm.tm_min;
00642   sec = ltm.tm_sec;
00643   
00644   switch(dateFormat)
00645   {
00646     case QOF_DATE_FORMAT_UK:
00647       flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", day, month, year, hour, min);
00648       break;
00649     case QOF_DATE_FORMAT_CE:
00650       flen = g_snprintf (buff, len, "%2d.%2d.%-4d %2d:%02d", day, month, year, hour, min);
00651       break;
00652     case QOF_DATE_FORMAT_ISO:
00653       flen = g_snprintf (buff, len, "%04d-%02d-%02d %02d:%02d", year, month, day, hour, min);
00654       break;
00655         case QOF_DATE_FORMAT_UTC:
00656         {
00657                 gtm = *gmtime (&secs);
00658                 flen = strftime (buff, len, QOF_UTC_DATE_FORMAT, &gtm);
00659                 break;
00660         }
00661     case QOF_DATE_FORMAT_LOCALE:
00662       {
00663         flen = strftime (buff, len, GNC_D_T_FMT, &ltm);
00664       }
00665       break;
00666 
00667     case QOF_DATE_FORMAT_US:
00668     default:
00669       flen = g_snprintf (buff, len, "%2d/%2d/%-4d %2d:%02d", month, day, year, hour, min);
00670       break;
00671   }
00672   return flen;
00673 }
00674 
00675 size_t 
00676 qof_print_time_buff (char * buff, size_t len, time_t secs)
00677 {
00678         int flen;
00679         struct tm ltm, gtm;
00680         
00681         if (!buff) return 0;
00682         if(dateFormat == QOF_DATE_FORMAT_UTC)
00683         {
00684                 gtm = *gmtime (&secs);
00685                 flen = strftime(buff, len, QOF_UTC_DATE_FORMAT, &gtm);
00686                 return flen;
00687         }
00688         ltm = *localtime (&secs);
00689         flen = strftime (buff, len, GNC_T_FMT, &ltm);
00690         
00691         return flen;
00692 }
00693 
00694 /* ============================================================== */
00695 
00696 int
00697 qof_is_same_day (time_t ta, time_t tb)
00698 {
00699   struct tm lta, ltb;
00700   lta = *localtime (&ta);
00701   ltb = *localtime (&tb);
00702   if (lta.tm_year == ltb.tm_year)
00703   {
00704     return (ltb.tm_yday - lta.tm_yday);
00705   }
00706   return (ltb.tm_year - lta.tm_year)*365;  /* very approximate */
00707 }
00708 
00709 /* ============================================================== */
00710 
00711 /* Convert a string into  day, month and year integers
00712 
00713     Convert a string into  day / month / year integers according to
00714     the current dateFormat value.
00715 
00716     This function will always parse a single number as the day of
00717     the month, regardless of the ordering of the dateFormat value.
00718     Two numbers will always be parsed as the day and the month, in
00719     the same order that they appear in the dateFormat value.  Three
00720     numbers are parsed exactly as specified in the dateFormat field.
00721 
00722     Fully formatted UTC timestamp strings are converted separately.
00723 
00724 param   buff - pointer to date string
00725 param     day -  will store day of the month as 1 ... 31
00726 param     month - will store month of the year as 1 ... 12
00727 param     year - will store the year (4-digit)
00728 
00729 return TRUE if date appeared to be valid.
00730 
00731  Globals: global dateFormat value
00732 */
00733 static gboolean
00734 qof_scan_date_internal (const char *buff, int *day, int *month, int *year,
00735                   QofDateFormat which_format)
00736 {
00737    char *dupe, *tmp, *first_field, *second_field, *third_field;
00738    int iday, imonth, iyear;
00739    struct tm *now, utc;
00740    time_t secs;
00741 
00742    if (!buff) return(FALSE);
00743 
00744         if(which_format == QOF_DATE_FORMAT_UTC)
00745         {
00746                 if(strptime(buff, QOF_UTC_DATE_FORMAT, &utc)) {
00747                         *day = utc.tm_mday;
00748                         *month = utc.tm_mon + 1;
00749                         *year = utc.tm_year + 1900;
00750                         return TRUE;
00751                 }
00752                 else { return FALSE; }
00753         }
00754    dupe = g_strdup (buff);
00755 
00756    tmp = dupe;
00757    first_field = NULL;
00758    second_field = NULL;
00759    third_field = NULL;
00760 
00761    /* Use strtok to find delimiters */
00762    if (tmp) {
00763      static char *delims = ".,-+/\\() ";
00764 
00765       first_field = strtok (tmp, delims);
00766       if (first_field) {
00767          second_field = strtok (NULL, delims);
00768          if (second_field) {
00769             third_field = strtok (NULL, delims);
00770          }
00771       }
00772    }
00773 
00774    /* If any fields appear to be blank, use today's date */
00775    time (&secs);
00776    now = localtime (&secs);
00777    iday = now->tm_mday; 
00778    imonth = now->tm_mon+1;
00779    iyear = now->tm_year+1900;
00780 
00781    /* get numeric values */
00782    switch (which_format)
00783    {
00784      case QOF_DATE_FORMAT_LOCALE:
00785        if (buff[0] != '\0')
00786        {
00787          struct tm thetime;
00788 
00789          /* Parse time string. */
00790          memset(&thetime, -1, sizeof(struct tm));
00791          strptime (buff, GNC_D_FMT, &thetime);
00792 
00793          if (third_field) {
00794            /* Easy.  All three values were parsed. */
00795            iyear = thetime.tm_year + 1900;
00796            iday = thetime.tm_mday;
00797            imonth = thetime.tm_mon + 1;
00798          } else if (second_field) {
00799            /* Hard. Two values parsed.  Figure out the ordering. */
00800            if (thetime.tm_year == -1) {
00801              /* %m-%d or %d-%m. Don't care. Already parsed correctly. */
00802              iday = thetime.tm_mday;
00803              imonth = thetime.tm_mon + 1;
00804            } else if (thetime.tm_mon != -1) {
00805              /* Must be %Y-%m-%d. Reparse as %m-%d.*/
00806              imonth = atoi(first_field);
00807              iday = atoi(second_field);
00808            } else {
00809              /* Must be %Y-%d-%m. Reparse as %d-%m. */
00810              iday = atoi(first_field);
00811              imonth = atoi(second_field);
00812            }
00813          } else if (first_field) {
00814            iday = atoi(first_field);
00815          }
00816        }
00817        break;
00818      case QOF_DATE_FORMAT_UK:
00819      case QOF_DATE_FORMAT_CE:
00820        if (third_field) {
00821          iday = atoi(first_field);
00822          imonth = atoi(second_field);
00823          iyear = atoi(third_field);
00824        } else if (second_field) {
00825          iday = atoi(first_field);
00826          imonth = atoi(second_field);
00827        } else if (first_field) {
00828          iday = atoi(first_field);
00829        }
00830        break;
00831      case QOF_DATE_FORMAT_ISO:
00832        if (third_field) {
00833          iyear = atoi(first_field);
00834          imonth = atoi(second_field);
00835          iday = atoi(third_field);
00836        } else if (second_field) {
00837          imonth = atoi(first_field);
00838          iday = atoi(second_field);
00839        } else if (first_field) {
00840          iday = atoi(first_field);
00841        }
00842        break;
00843     case QOF_DATE_FORMAT_US:
00844     default:
00845        if (third_field) {
00846          imonth = atoi(first_field);
00847          iday = atoi(second_field);
00848          iyear = atoi(third_field);
00849        } else if (second_field) {
00850          imonth = atoi(first_field);
00851          iday = atoi(second_field);
00852        } else if (first_field) {
00853          iday = atoi(first_field);
00854        }
00855        break;
00856    }
00857 
00858    g_free (dupe);
00859 
00860    if ((12 < imonth) || (31 < iday)) 
00861    {
00862      /* 
00863       * Ack! Thppfft!  Someone just fed this routine a string in the
00864       * wrong date format.  This is known to happen if a register
00865       * window is open when changing the date format.  Try the
00866       * previous date format.  If that doesn't work, see if we can
00867       * exchange month and day. If that still doesn't work,
00868       * bail and give the caller what they asked for (garbage) 
00869       * parsed in the new format.
00870       *
00871       * Note: This test cannot detect any format change that only
00872       * swaps month and day field, if the day is 12 or less.  This is
00873       * deemed acceptable given the obscurity of this bug.
00874       */
00875      if ((which_format != prevQofDateFormat) &&
00876          qof_scan_date_internal(buff, day, month, year, prevQofDateFormat))
00877      {
00878        return(TRUE);
00879      }
00880      if ((12 < imonth) && (12 >= iday))
00881      {
00882         int tmp = imonth; imonth = iday; iday = tmp;
00883      } 
00884           else
00885           {
00886         return FALSE;
00887           }
00888    }
00889 
00890    /* If the year entered is smaller than 100, assume we mean the current
00891       century (and are not revising some roman emperor's books) */
00892    if (iyear < 100)
00893      iyear += ((int) ((now->tm_year+1950-iyear)/100)) * 100;
00894 
00895    if (year) *year=iyear;
00896    if (month) *month=imonth;
00897    if (day) *day=iday;
00898    return(TRUE);
00899 }
00900 
00901 gboolean
00902 qof_scan_date (const char *buff, int *day, int *month, int *year)
00903 {
00904   return qof_scan_date_internal(buff, day, month, year, dateFormat);
00905 }
00906 
00907 gboolean
00908 qof_scan_date_secs (const char *buff, time_t *secs)
00909 {
00910   gboolean rc;
00911   int day, month, year;
00912   
00913   rc = qof_scan_date_internal(buff, &day, &month, &year, dateFormat);
00914   if (secs) *secs = xaccDMYToSec (day, month, year);
00915 
00916   return rc;
00917 }
00918 
00919 /* Return the field separator for the current date format
00920 return date character
00921 */
00922 char dateSeparator (void)
00923 {
00924   static char locale_separator = '\0';
00925 
00926   switch (dateFormat)
00927   {
00928     case QOF_DATE_FORMAT_CE:
00929       return '.';
00930     case QOF_DATE_FORMAT_ISO:
00931     case QOF_DATE_FORMAT_UTC:
00932       return '-';
00933     case QOF_DATE_FORMAT_US:
00934     case QOF_DATE_FORMAT_UK:
00935     default:
00936       return '/';
00937     case QOF_DATE_FORMAT_LOCALE:
00938       if (locale_separator != '\0')
00939         return locale_separator;
00940       else
00941       { /* Make a guess */
00942         unsigned char string[256];
00943         struct tm *tm;
00944         time_t secs;
00945         unsigned char *s;
00946 
00947         secs = time(NULL);
00948         tm = localtime(&secs);
00949         strftime(string, sizeof(string), GNC_D_FMT, tm);
00950 
00951         for (s = string; s != '\0'; s++)
00952           if (!isdigit(*s))
00953             return (locale_separator = *s);
00954       }
00955   }
00956 
00957   return '\0';
00958 }
00959 
00960 /********************************************************************\
00961 \********************************************************************/
00962                                                                                 
00963 /* Convert time in seconds to a textual.
00964 
00965 The xaccDateUtilGetStamp() routine will take the given time in
00966 seconds and return a buffer containing a textual for the date.
00967 
00968 param thyme The time in seconds to convert.
00969 return A pointer to the generated string.
00970 The caller owns this buffer and must free it when done.
00971 */
00972 char *
00973 xaccDateUtilGetStamp (time_t thyme)
00974 {
00975    struct tm *stm;
00976                                                                                 
00977    stm = localtime (&thyme);
00978                                                                                 
00979    return g_strdup_printf("%04d%02d%02d%02d%02d%02d",
00980       (stm->tm_year + 1900),
00981       (stm->tm_mon +1),
00982       stm->tm_mday,
00983       stm->tm_hour,
00984       stm->tm_min,
00985       stm->tm_sec
00986    );
00987 }
00988                                                                                 
00989                                                                                 
00990 /* Convert textual to time in seconds.
00991 
00992 The xaccDateUtilGetStampNow() routine returns the current time in
00993 seconds in textual format.
00994 
00995 return A pointer to the generated string.
00996 
00997 note The caller owns this buffer and must free it when done.
00998 */
00999 char *
01000 xaccDateUtilGetStampNow (void)
01001 {
01002    time_t now;
01003    time (&now);
01004    return xaccDateUtilGetStamp (now);
01005 }
01006 
01007 /********************************************************************\
01008  * iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
01009 \********************************************************************/
01010 /* hack alert -- this routine returns incorrect values for 
01011  * dates before 1970 */
01012 
01013 Timespec
01014 gnc_iso8601_to_timespec_gmt(const char *str)
01015 {
01016   char buf[4];
01017   gchar *dupe;
01018   Timespec ts;
01019   struct tm stm;
01020   long int nsec =0;
01021 
01022   ts.tv_sec=0;
01023   ts.tv_nsec=0;
01024   if (!str) return ts;
01025   dupe = g_strdup(str);
01026   stm.tm_year = atoi(str) - 1900;
01027   str = strchr (str, '-'); if (str) { str++; } else { return ts; }
01028   stm.tm_mon = atoi(str) - 1;
01029   str = strchr (str, '-'); if (str) { str++; } else { return ts; }
01030   stm.tm_mday = atoi(str);
01031 
01032   str = strchr (str, ' '); if (str) { str++; } else { return ts; }
01033   stm.tm_hour = atoi(str);
01034   str = strchr (str, ':'); if (str) { str++; } else { return ts; }
01035   stm.tm_min = atoi(str);
01036   str = strchr (str, ':'); if (str) { str++; } else { return ts; }
01037   stm.tm_sec = atoi (str);
01038 
01039   /* The decimal point, optionally present ... */
01040   /* hack alert -- this algo breaks if more than 9 decimal places present */
01041   if (strchr (str, '.')) 
01042   { 
01043      int decimals, i, multiplier=1000000000;
01044      str = strchr (str, '.') +1;
01045      decimals = strcspn (str, "+- ");
01046      for (i=0; i<decimals; i++) multiplier /= 10;
01047      nsec = atoi(str) * multiplier;
01048   }
01049   stm.tm_isdst = -1;
01050 
01051   /* Timezone format can be +hh or +hhmm or +hh.mm (or -) (or not present) */
01052   str += strcspn (str, "+-");
01053   if (str)
01054   {
01055     buf[0] = str[0];
01056     buf[1] = str[1];
01057     buf[2] = str[2];
01058     buf[3] = 0;
01059     stm.tm_hour -= atoi(buf);
01060 
01061     str +=3;
01062     if ('.' == *str) str++;
01063     if (isdigit ((unsigned char)*str) && isdigit ((unsigned char)*(str+1)))
01064     {
01065       int cyn;
01066       /* copy sign from hour part */
01067       if ('+' == buf[0]) { cyn = -1; } else { cyn = +1; } 
01068       buf[0] = str[0];
01069       buf[1] = str[1];
01070       buf[2] = str[2];
01071       buf[3] = 0;
01072       stm.tm_min += cyn * atoi(buf);
01073     }
01074   }
01075 
01076   /* Note that mktime returns 'local seconds' which is the true time
01077    * minus the timezone offset.  We don't want to work with local 
01078    * seconds, since they swim around acording to daylight savings, etc. 
01079    * We want to work with universal time.  Thus, add an offset
01080    * to undo the damage that mktime causes.
01081    */
01082  {
01083     struct tm tmp_tm;
01084     struct tm tm;
01085     long int tz;
01086     int tz_hour;
01087     time_t secs;
01088 
01089     /* Use a temporary tm struct so the mktime call below
01090      * doesn't mess up stm. */
01091     tmp_tm = stm;
01092     tmp_tm.tm_isdst = -1;
01093 
01094     secs = mktime (&tmp_tm);
01095 
01096     if(secs < 0) 
01097     {
01098     /* Workaround buggy mktime implementations that get confused
01099        on the day daylight saving starts or ends. (OSX) */
01100       PWARN (" mktime failed to handle daylight saving: "
01101        "tm_hour=%d tm_year=%d tm_min=%d tm_sec=%d tm_isdst=%d for string=%s", 
01102         stm.tm_hour, stm.tm_year, stm.tm_min,
01103         stm.tm_sec, stm.tm_isdst, dupe ); 
01104       tmp_tm.tm_hour++;
01105       secs = mktime (&tmp_tm);
01106       if (secs < 0) 
01107       { 
01108       /* if, for some strange reason, first attempt didn't fix it,
01109          try reversing the workaround. */
01110         tmp_tm.tm_hour -= 2;
01111         secs = mktime (&tmp_tm);
01112       }
01113       if (secs < 0) 
01114       {
01115         /* Seriously buggy mktime - give up.  */
01116         PERR (" unable to recover from buggy mktime ");
01117         g_free(dupe);
01118         return ts;
01119       }
01120     }
01121 
01122     /* The call to localtime is 'bogus', but it forces 'timezone' to
01123      * be set. Note that we must use the accurate date, since the
01124      * value of 'gnc_timezone' includes daylight savings corrections
01125      * for that date. */
01126 
01127     tm = *localtime_r (&secs, &tm);
01128 
01129     tz = gnc_timezone (&tmp_tm);
01130 
01131     tz_hour = tz / 3600;
01132     stm.tm_hour -= tz_hour;
01133     stm.tm_min -= (tz % 3600) / 60;
01134     stm.tm_isdst = tmp_tm.tm_isdst;
01135     ts.tv_sec = mktime (&stm);
01136     if(ts.tv_sec < 0) { 
01137       PWARN (" mktime failed to adjust calculated time:"
01138         " tm_hour=%d tm_year=%d tm_min=%d tm_sec=%d tm_isdst=%d", 
01139         stm.tm_hour, stm.tm_year, stm.tm_min,
01140         stm.tm_sec, stm.tm_isdst ); 
01141       /* Try and make some sense of the result. */
01142       ts.tv_sec = secs - tz;  
01143     }
01144     ts.tv_nsec = nsec;
01145   }
01146   g_free(dupe);
01147   return ts;
01148 }
01149 
01150 /********************************************************************\
01151 \********************************************************************/
01152 
01153 char * 
01154 gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
01155 {
01156   int len, tz_hour, tz_min;
01157   long int secs;
01158   char cyn;
01159   time_t tmp;
01160   struct tm parsed;
01161 
01162   tmp = ts.tv_sec;
01163   localtime_r(&tmp, &parsed);
01164 
01165   secs = gnc_timezone (&parsed);
01166   tz_hour = secs / 3600;
01167   tz_min = (secs % 3600) / 60;
01168 
01169   /* We also have to print the sign by hand, to work around a bug
01170    * in the glibc 2.1.3 printf (where %+02d fails to zero-pad).
01171    */
01172   cyn = '-';
01173   if (0>tz_hour) { cyn = '+'; tz_hour = -tz_hour; }
01174 
01175   len = sprintf (buff, "%4d-%02d-%02d %02d:%02d:%02d.%06ld %c%02d%02d",
01176                  parsed.tm_year + 1900,
01177                  parsed.tm_mon + 1,
01178                  parsed.tm_mday,
01179                  parsed.tm_hour,
01180                  parsed.tm_min,
01181                  parsed.tm_sec,
01182                  ts.tv_nsec / 1000,
01183                  cyn,
01184                  tz_hour,
01185                  tz_min);
01186 
01187   /* Return pointer to end of string. */
01188   buff += len;
01189   return buff;
01190 }
01191 
01192 int
01193 gnc_timespec_last_mday (Timespec t)
01194 {
01195   struct tm *result;
01196   time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
01197   result = localtime(&t_secs);
01198   return date_get_last_mday (result);
01199 }
01200 
01201 void
01202 gnc_timespec2dmy (Timespec t, int *day, int *month, int *year)
01203 {
01204   struct tm *result;
01205   time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
01206   result = localtime(&t_secs);
01207 
01208   if (day) *day = result->tm_mday;
01209   if (month) *month = result->tm_mon+1;
01210   if (year) *year = result->tm_year+1900;
01211 }
01212 
01213 /********************************************************************\
01214 \********************************************************************/
01215 /* hack alert -- this routine returns incorrect values for 
01216  * dates before 1970 */
01217 
01218 time_t 
01219 xaccDMYToSec (int day, int month, int year)
01220 {
01221   struct tm stm;
01222   time_t secs;
01223 
01224   stm.tm_year = year - 1900;
01225   stm.tm_mon = month - 1;
01226   stm.tm_mday = day;
01227   gnc_tm_set_day_start(&stm);
01228 
01229   /* compute number of seconds */
01230   secs = mktime (&stm);
01231 
01232   return secs;
01233 }
01234 
01235 
01236 #define THIRTY_TWO_YEARS 0x3c30fc00LL
01237 
01238 static Timespec
01239 gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
01240 {
01241   Timespec result;
01242   struct tm date;
01243   long long secs = 0;
01244   long long era = 0;
01245 
01246   year -= 1900;
01247 
01248   /* make a crude attempt to deal with dates outside the range of Dec
01249    * 1901 to Jan 2038. Note we screw up centennial leap years here so
01250    * hack alert */
01251   if ((2 > year) || (136 < year)) 
01252   {
01253     era = year / 32;
01254     year %= 32;
01255     if (0 > year) { year += 32; era -= 1; } 
01256   }
01257 
01258   date.tm_year = year;
01259   date.tm_mon = month - 1;
01260   date.tm_mday = day;
01261 
01262   if (start_of_day)
01263     gnc_tm_set_day_start(&date);
01264   else
01265     gnc_tm_set_day_end(&date);
01266 
01267   /* compute number of seconds */
01268   secs = mktime (&date);
01269 
01270   secs += era * THIRTY_TWO_YEARS;
01271 
01272   result.tv_sec = secs;
01273   result.tv_nsec = 0;
01274 
01275   return result;
01276 }
01277 
01278 Timespec
01279 gnc_dmy2timespec (int day, int month, int year)
01280 {
01281   return gnc_dmy2timespec_internal (day, month, year, TRUE);
01282 }
01283 
01284 Timespec
01285 gnc_dmy2timespec_end (int day, int month, int year)
01286 {
01287   return gnc_dmy2timespec_internal (day, month, year, FALSE);
01288 }
01289 
01290 /********************************************************************\
01291 \********************************************************************/
01292 
01293 long int
01294 gnc_timezone (struct tm *tm)
01295 {
01296   g_return_val_if_fail (tm != NULL, 0);
01297 
01298 #ifdef HAVE_STRUCT_TM_GMTOFF
01299   /* tm_gmtoff is seconds *east* of UTC and is
01300    * already adjusted for daylight savings time. */
01301   return -(tm->tm_gmtoff);
01302 #else
01303   /* timezone is seconds *west* of UTC and is
01304    * not adjusted for daylight savings time.
01305    * In Spring, we spring forward, wheee! */
01306   return (long int)(timezone - (tm->tm_isdst > 0 ? 3600 : 0));
01307 #endif
01308 }
01309 
01310 
01311 void
01312 timespecFromTime_t( Timespec *ts, time_t t )
01313 {
01314     ts->tv_sec = t;
01315     ts->tv_nsec = 0;
01316 }
01317 
01318 time_t 
01319 timespecToTime_t (Timespec ts)
01320 {
01321     return ts.tv_sec;
01322 }
01323 
01324 void
01325 gnc_tm_get_day_start (struct tm *tm, time_t time_val)
01326 {
01327   /* Get the equivalent time structure */
01328   tm = localtime_r(&time_val, tm);
01329   gnc_tm_set_day_start(tm);
01330 }
01331 
01332 void
01333 gnc_tm_get_day_end (struct tm *tm, time_t time_val)
01334 {
01335   /* Get the equivalent time structure */
01336   tm = localtime_r(&time_val, tm);
01337   gnc_tm_set_day_end(tm);
01338 }
01339 
01340 time_t
01341 gnc_timet_get_day_start (time_t time_val)
01342 {
01343   struct tm tm;
01344 
01345   gnc_tm_get_day_start(&tm, time_val);
01346   return mktime(&tm);
01347 }
01348 
01349 time_t
01350 gnc_timet_get_day_end (time_t time_val)
01351 {
01352   struct tm tm;
01353 
01354   gnc_tm_get_day_end(&tm, time_val);
01355   return mktime(&tm);
01356 }
01357 
01358 
01359 #ifndef GNUCASH_MAJOR_VERSION
01360 time_t
01361 gnc_timet_get_day_start_gdate (GDate *date)
01362 {
01363   struct tm stm;
01364   time_t secs;
01365 
01366   stm.tm_year = g_date_get_year (date) - 1900;
01367   stm.tm_mon = g_date_get_month (date) - 1;
01368   stm.tm_mday = g_date_get_day (date);
01369   gnc_tm_set_day_start(&stm);
01370 
01371   /* Compute number of seconds */
01372   secs = mktime (&stm);
01373   return secs;
01374 }
01375 
01376 time_t
01377 gnc_timet_get_day_end_gdate (GDate *date)
01378 {
01379   struct tm stm;
01380   time_t secs;
01381 
01382   stm.tm_year = g_date_get_year (date) - 1900;
01383   stm.tm_mon = g_date_get_month (date) - 1;
01384   stm.tm_mday = g_date_get_day (date);
01385   gnc_tm_set_day_end(&stm);
01386 
01387   /* Compute number of seconds */
01388   secs = mktime (&stm);
01389   return secs;
01390 }
01391 #endif /* GNUCASH_MAJOR_VERSION */
01392 
01393 /* ======================================================== */
01394 
01395 void
01396 gnc_tm_get_today_start (struct tm *tm)
01397 {
01398   gnc_tm_get_day_start(tm, time(NULL));
01399 }
01400 
01401 void
01402 gnc_tm_get_today_end (struct tm *tm)
01403 {
01404   gnc_tm_get_day_end(tm, time(NULL));
01405 }
01406 
01407 time_t
01408 gnc_timet_get_today_start (void)
01409 {
01410   struct tm tm;
01411 
01412   gnc_tm_get_day_start(&tm, time(NULL));
01413   return mktime(&tm);
01414 }
01415 
01416 time_t
01417 gnc_timet_get_today_end (void)
01418 {
01419   struct tm tm;
01420 
01421   gnc_tm_get_day_end(&tm, time(NULL));
01422   return mktime(&tm);
01423 }
01424 
01425 gboolean
01426 qof_date_add_days(Timespec *ts, gint days)
01427 {
01428         struct tm tm;
01429         time_t    tt;
01430 
01431         g_return_val_if_fail(ts, FALSE);
01432         tt = timespecToTime_t(*ts);
01433 #ifdef HAVE_GMTIME_R
01434         tm = *gmtime_r(&tt, &tm);
01435 #else
01436         tm = *gmtime(&tt);
01437 #endif
01438         tm.tm_mday += days;
01439         /* let mktime normalise the months and year
01440         because we aren't tracking last_day_of_month */
01441         tt = mktime(&tm);
01442         if(tt < 0) { return FALSE; }
01443         timespecFromTime_t(ts, tt);
01444         return TRUE;
01445 }
01446 
01447 gboolean
01448 qof_date_add_months(Timespec *ts, gint months, gboolean track_last_day)
01449 {
01450         struct tm tm;
01451         time_t    tt;
01452         gint new_last_mday;
01453         gboolean was_last_day;
01454 
01455         g_return_val_if_fail(ts, FALSE);
01456         tt = timespecToTime_t(*ts);
01457 #ifdef HAVE_GMTIME_R
01458         tm = *gmtime_r(&tt, &tm);
01459 #else
01460         tm = *gmtime(&tt);
01461 #endif
01462         was_last_day = date_is_last_mday(&tm);
01463         tm.tm_mon += months;
01464         while (tm.tm_mon > 11) {
01465                 tm.tm_mon -= 12;
01466                 tm.tm_year++;
01467         }
01468         if (track_last_day) {
01469                 /* Track last day of the month, i.e. 1/31 -> 2/28 -> 3/31 */
01470                 new_last_mday = date_get_last_mday(&tm);
01471                 if (was_last_day || (tm.tm_mday > new_last_mday)) {
01472                         tm.tm_mday = new_last_mday;
01473                 }
01474         }
01475         tt = mktime(&tm);
01476         if(tt < 0) { return FALSE; }
01477         timespecFromTime_t(ts, tt);
01478         return TRUE;
01479 }
01480 
01481 /********************** END OF FILE *********************************\
01482 \********************************************************************/

Generated on Fri May 12 18:00:32 2006 for QOF by  doxygen 1.4.4