Translate from locale era to AD in g_date_set_parse(). (#133400, Theppitak
[platform/upstream/glib.git] / glib / gdate.c
1 /* GLIB - Library of useful routines for C programming
2  * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GLib Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GLib Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GLib at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 /* 
28  * MT safe
29  */
30
31 #include "config.h"
32
33 #define DEBUG_MSG(x)    /* */
34 #ifdef G_ENABLE_DEBUG
35 /* #define DEBUG_MSG(args)      g_message args ; */
36 #endif
37
38 #include "glib.h"
39
40 #include <time.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <locale.h>
44
45 GDate*
46 g_date_new ()
47 {
48   GDate *d = g_new0 (GDate, 1); /* happily, 0 is the invalid flag for everything. */
49   
50   return d;
51 }
52
53 GDate*
54 g_date_new_dmy (GDateDay day, GDateMonth m, GDateYear y)
55 {
56   GDate *d;
57   g_return_val_if_fail (g_date_valid_dmy (day, m, y), NULL);
58   
59   d = g_new (GDate, 1);
60   
61   d->julian = FALSE;
62   d->dmy    = TRUE;
63   
64   d->month = m;
65   d->day   = day;
66   d->year  = y;
67   
68   g_assert (g_date_valid (d));
69   
70   return d;
71 }
72
73 GDate*
74 g_date_new_julian (guint32 j)
75 {
76   GDate *d;
77   g_return_val_if_fail (g_date_valid_julian (j), NULL);
78   
79   d = g_new (GDate, 1);
80   
81   d->julian = TRUE;
82   d->dmy    = FALSE;
83   
84   d->julian_days = j;
85   
86   g_assert (g_date_valid (d));
87   
88   return d;
89 }
90
91 void
92 g_date_free (GDate *d)
93 {
94   g_return_if_fail (d != NULL);
95   
96   g_free (d);
97 }
98
99 gboolean     
100 g_date_valid (const GDate *d)
101 {
102   g_return_val_if_fail (d != NULL, FALSE);
103   
104   return (d->julian || d->dmy);
105 }
106
107 static const guint8 days_in_months[2][13] = 
108 {  /* error, jan feb mar apr may jun jul aug sep oct nov dec */
109   {  0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, 
110   {  0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } /* leap year */
111 };
112
113 static const guint16 days_in_year[2][14] = 
114 {  /* 0, jan feb mar apr may  jun  jul  aug  sep  oct  nov  dec */
115   {  0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 
116   {  0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
117 };
118
119 gboolean     
120 g_date_valid_month (GDateMonth   m)
121
122   return ( (m > G_DATE_BAD_MONTH) && (m < 13) );
123 }
124
125 gboolean     
126 g_date_valid_year (GDateYear    y)
127 {
128   return ( y > G_DATE_BAD_YEAR );
129 }
130
131 gboolean     
132 g_date_valid_day (GDateDay     d)
133 {
134   return ( (d > G_DATE_BAD_DAY) && (d < 32) );
135 }
136
137 gboolean     
138 g_date_valid_weekday (GDateWeekday w)
139 {
140   return ( (w > G_DATE_BAD_WEEKDAY) && (w < 8) );
141 }
142
143 gboolean     
144 g_date_valid_julian (guint32      j)
145 {
146   return (j > G_DATE_BAD_JULIAN);
147 }
148
149 gboolean     
150 g_date_valid_dmy (GDateDay     d, 
151                   GDateMonth   m, 
152                   GDateYear    y)
153 {
154   return ( (m > G_DATE_BAD_MONTH) &&
155            (m < 13)               && 
156            (d > G_DATE_BAD_DAY)   && 
157            (y > G_DATE_BAD_YEAR)  &&   /* must check before using g_date_is_leap_year */
158            (d <=  (g_date_is_leap_year (y) ? 
159                    days_in_months[1][m] : days_in_months[0][m])) );
160 }
161
162
163 /* "Julian days" just means an absolute number of days, where Day 1 ==
164  *   Jan 1, Year 1
165  */
166 static void
167 g_date_update_julian (const GDate *const_d)
168 {
169   GDate *d = (GDate *) const_d;
170   GDateYear year;
171   gint index;
172   
173   g_return_if_fail (d != NULL);
174   g_return_if_fail (d->dmy);
175   g_return_if_fail (!d->julian);
176   g_return_if_fail (g_date_valid_dmy (d->day, d->month, d->year));
177   
178   /* What we actually do is: multiply years * 365 days in the year,
179    *  add the number of years divided by 4, subtract the number of
180    *  years divided by 100 and add the number of years divided by 400,
181    *  which accounts for leap year stuff. Code from Steffen Beyer's
182    *  DateCalc. 
183    */
184   
185   year = d->year - 1; /* we know d->year > 0 since it's valid */
186   
187   d->julian_days = year * 365U;
188   d->julian_days += (year >>= 2); /* divide by 4 and add */
189   d->julian_days -= (year /= 25); /* divides original # years by 100 */
190   d->julian_days += year >> 2;    /* divides by 4, which divides original by 400 */
191   
192   index = g_date_is_leap_year (d->year) ? 1 : 0;
193   
194   d->julian_days += days_in_year[index][d->month] + d->day;
195   
196   g_return_if_fail (g_date_valid_julian (d->julian_days));
197   
198   d->julian = TRUE;
199 }
200
201 static void 
202 g_date_update_dmy (const GDate *const_d)
203 {
204   GDate *d = (GDate *) const_d;
205   GDateYear y;
206   GDateMonth m;
207   GDateDay day;
208   
209   guint32 A, B, C, D, E, M;
210   
211   g_return_if_fail (d != NULL);
212   g_return_if_fail (d->julian);
213   g_return_if_fail (!d->dmy);
214   g_return_if_fail (g_date_valid_julian (d->julian_days));
215   
216   /* Formula taken from the Calendar FAQ; the formula was for the
217    *  Julian Period which starts on 1 January 4713 BC, so we add
218    *  1,721,425 to the number of days before doing the formula.
219    *
220    * I'm sure this can be simplified for our 1 January 1 AD period
221    * start, but I can't figure out how to unpack the formula.  
222    */
223   
224   A = d->julian_days + 1721425 + 32045;
225   B = ( 4 *(A + 36524) )/ 146097 - 1;
226   C = A - (146097 * B)/4;
227   D = ( 4 * (C + 365) ) / 1461 - 1;
228   E = C - ((1461*D) / 4);
229   M = (5 * (E - 1) + 2)/153;
230   
231   m = M + 3 - (12*(M/10));
232   day = E - (153*M + 2)/5;
233   y = 100 * B + D - 4800 + (M/10);
234   
235 #ifdef G_ENABLE_DEBUG
236   if (!g_date_valid_dmy (day, m, y)) 
237     {
238       g_warning ("\nOOPS julian: %u  computed dmy: %u %u %u\n", 
239                  d->julian_days, day, m, y);
240     }
241 #endif
242   
243   d->month = m;
244   d->day   = day;
245   d->year  = y;
246   
247   d->dmy = TRUE;
248 }
249
250 GDateWeekday 
251 g_date_get_weekday (const GDate *d)
252 {
253   g_return_val_if_fail (d != NULL, G_DATE_BAD_WEEKDAY);
254   g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_WEEKDAY);
255   
256   if (!d->julian) 
257     {
258       g_date_update_julian (d);
259     }
260   g_return_val_if_fail (d->julian, G_DATE_BAD_WEEKDAY);
261   
262   return ((d->julian_days - 1) % 7) + 1;
263 }
264
265 GDateMonth   
266 g_date_get_month (const GDate *d)
267 {
268   g_return_val_if_fail (d != NULL, G_DATE_BAD_MONTH);
269   g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_MONTH);
270   
271   if (!d->dmy) 
272     {
273       g_date_update_dmy (d);
274     }
275   g_return_val_if_fail (d->dmy, G_DATE_BAD_MONTH);
276   
277   return d->month;
278 }
279
280 GDateYear    
281 g_date_get_year (const GDate *d)
282 {
283   g_return_val_if_fail (d != NULL, G_DATE_BAD_YEAR);
284   g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_YEAR);
285   
286   if (!d->dmy) 
287     {
288       g_date_update_dmy (d);
289     }
290   g_return_val_if_fail (d->dmy, G_DATE_BAD_YEAR);  
291   
292   return d->year;
293 }
294
295 GDateDay     
296 g_date_get_day (const GDate *d)
297 {
298   g_return_val_if_fail (d != NULL, G_DATE_BAD_DAY);
299   g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_DAY);
300   
301   if (!d->dmy) 
302     {
303       g_date_update_dmy (d);
304     }
305   g_return_val_if_fail (d->dmy, G_DATE_BAD_DAY);  
306   
307   return d->day;
308 }
309
310 guint32      
311 g_date_get_julian (const GDate *d)
312 {
313   g_return_val_if_fail (d != NULL, G_DATE_BAD_JULIAN);
314   g_return_val_if_fail (g_date_valid (d), G_DATE_BAD_JULIAN);
315   
316   if (!d->julian) 
317     {
318       g_date_update_julian (d);
319     }
320   g_return_val_if_fail (d->julian, G_DATE_BAD_JULIAN);  
321   
322   return d->julian_days;
323 }
324
325 guint        
326 g_date_get_day_of_year (const GDate *d)
327 {
328   gint index;
329   
330   g_return_val_if_fail (d != NULL, 0);
331   g_return_val_if_fail (g_date_valid (d), 0);
332   
333   if (!d->dmy) 
334     {
335       g_date_update_dmy (d);
336     }
337   g_return_val_if_fail (d->dmy, 0);  
338   
339   index = g_date_is_leap_year (d->year) ? 1 : 0;
340   
341   return (days_in_year[index][d->month] + d->day);
342 }
343
344 guint        
345 g_date_get_monday_week_of_year (const GDate *d)
346 {
347   GDateWeekday wd;
348   guint day;
349   GDate first;
350   
351   g_return_val_if_fail (d != NULL, 0);
352   g_return_val_if_fail (g_date_valid (d), 0);
353   
354   if (!d->dmy) 
355     {
356       g_date_update_dmy (d);
357     }
358   g_return_val_if_fail (d->dmy, 0);  
359   
360   g_date_clear (&first, 1);
361   
362   g_date_set_dmy (&first, 1, 1, d->year);
363   
364   wd = g_date_get_weekday (&first) - 1; /* make Monday day 0 */
365   day = g_date_get_day_of_year (d) - 1;
366   
367   return ((day + wd)/7U + (wd == 0 ? 1 : 0));
368 }
369
370 guint        
371 g_date_get_sunday_week_of_year (const GDate *d)
372 {
373   GDateWeekday wd;
374   guint day;
375   GDate first;
376   
377   g_return_val_if_fail (d != NULL, 0);
378   g_return_val_if_fail (g_date_valid (d), 0);
379   
380   if (!d->dmy) 
381     {
382       g_date_update_dmy (d);
383     }
384   g_return_val_if_fail (d->dmy, 0);  
385   
386   g_date_clear (&first, 1);
387   
388   g_date_set_dmy (&first, 1, 1, d->year);
389   
390   wd = g_date_get_weekday (&first);
391   if (wd == 7) wd = 0; /* make Sunday day 0 */
392   day = g_date_get_day_of_year (d) - 1;
393   
394   return ((day + wd)/7U + (wd == 0 ? 1 : 0));
395 }
396
397 gint
398 g_date_days_between (const GDate *d1,
399                      const GDate *d2)
400 {
401   g_return_val_if_fail (d1 != NULL, 0);
402   g_return_val_if_fail (d2 != NULL, 0);
403
404   g_return_val_if_fail (g_date_valid (d1), 0);
405   g_return_val_if_fail (g_date_valid (d2), 0);
406
407   return (gint)g_date_get_julian (d2) - (gint)g_date_get_julian (d1);
408 }
409
410 void         
411 g_date_clear (GDate       *d, guint ndates)
412 {
413   g_return_if_fail (d != NULL);
414   g_return_if_fail (ndates != 0);
415   
416   memset (d, 0x0, ndates*sizeof (GDate)); 
417 }
418
419 G_LOCK_DEFINE_STATIC (g_date_global);
420
421 /* These are for the parser, output to the user should use *
422  * g_date_strftime () - this creates more never-freed memory to annoy
423  * all those memory debugger users. :-) 
424  */
425
426 static gchar *long_month_names[13] = 
427
428   "Error", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL 
429 };
430
431 static gchar *short_month_names[13] = 
432 {
433   "Error", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL 
434 };
435
436 /* This tells us if we need to update the parse info */
437 static gchar *current_locale = NULL;
438
439 /* order of these in the current locale */
440 static GDateDMY dmy_order[3] = 
441 {
442    G_DATE_DAY, G_DATE_MONTH, G_DATE_YEAR
443 };
444
445 /* Where to chop two-digit years: i.e., for the 1930 default, numbers
446  * 29 and below are counted as in the year 2000, numbers 30 and above
447  * are counted as in the year 1900.  
448  */
449
450 static GDateYear twodigit_start_year = 1930;
451
452 /* It is impossible to enter a year between 1 AD and 99 AD with this
453  * in effect.  
454  */
455 static gboolean using_twodigit_years = FALSE;
456
457 /* Adjustment of locale era to AD, non-zero means using locale era
458  */
459 static gint locale_era_adjust = 0;
460
461 struct _GDateParseTokens {
462   gint num_ints;
463   gint n[3];
464   guint month;
465 };
466
467 typedef struct _GDateParseTokens GDateParseTokens;
468
469 #define NUM_LEN 10
470
471 /* HOLDS: g_date_global_lock */
472 static void
473 g_date_fill_parse_tokens (const gchar *str, GDateParseTokens *pt)
474 {
475   gchar num[4][NUM_LEN+1];
476   gint i;
477   const guchar *s;
478   
479   /* We count 4, but store 3; so we can give an error
480    * if there are 4.
481    */
482   num[0][0] = num[1][0] = num[2][0] = num[3][0] = '\0';
483   
484   s = (const guchar *) str;
485   pt->num_ints = 0;
486   while (*s && pt->num_ints < 4) 
487     {
488       
489       i = 0;
490       while (*s && g_ascii_isdigit (*s) && i <= NUM_LEN)
491         {
492           num[pt->num_ints][i] = *s;
493           ++s; 
494           ++i;
495         }
496       
497       if (i > 0) 
498         {
499           num[pt->num_ints][i] = '\0';
500           ++(pt->num_ints);
501         }
502       
503       if (*s == '\0') break;
504       
505       ++s;
506     }
507   
508   pt->n[0] = pt->num_ints > 0 ? atoi (num[0]) : 0;
509   pt->n[1] = pt->num_ints > 1 ? atoi (num[1]) : 0;
510   pt->n[2] = pt->num_ints > 2 ? atoi (num[2]) : 0;
511   
512   pt->month = G_DATE_BAD_MONTH;
513   
514   if (pt->num_ints < 3)
515     {
516       gchar *casefold;
517       gchar *normalized;
518       
519       casefold = g_utf8_casefold (str, -1);
520       normalized = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL);
521       g_free (casefold);
522
523       i = 1;
524       while (i < 13)
525         {
526           if (long_month_names[i] != NULL) 
527             {
528               const gchar *found = strstr (normalized, long_month_names[i]);
529               
530               if (found != NULL)
531                 {
532                   pt->month = i;
533                   break;
534                 }
535             }
536           
537           if (short_month_names[i] != NULL) 
538             {
539               const gchar *found = strstr (normalized, short_month_names[i]);
540               
541               if (found != NULL)
542                 {
543                   pt->month = i;
544                   break;
545                 }
546             }
547
548           ++i;
549         }
550
551       g_free (normalized);
552     }
553 }
554
555 /* HOLDS: g_date_global_lock */
556 static void
557 g_date_prepare_to_parse (const gchar *str, GDateParseTokens *pt)
558 {
559   const gchar *locale = setlocale (LC_TIME, NULL);
560   gboolean recompute_localeinfo = FALSE;
561   GDate d;
562   
563   g_return_if_fail (locale != NULL); /* should not happen */
564   
565   g_date_clear (&d, 1);              /* clear for scratch use */
566   
567   if ( (current_locale == NULL) || (strcmp (locale, current_locale) != 0) ) 
568     {
569       recompute_localeinfo = TRUE;  /* Uh, there used to be a reason for the temporary */
570     }
571   
572   if (recompute_localeinfo)
573     {
574       int i = 1;
575       GDateParseTokens testpt;
576       gchar buf[128];
577       
578       g_free (current_locale); /* still works if current_locale == NULL */
579       
580       current_locale = g_strdup (locale);
581       
582       while (i < 13) 
583         {
584           gchar *casefold;
585           
586           g_date_set_dmy (&d, 1, i, 1);
587           
588           g_return_if_fail (g_date_valid (&d));
589           
590           g_date_strftime (buf, 127, "%b", &d);
591
592           casefold = g_utf8_casefold (buf, -1);
593           g_free (short_month_names[i]);
594           short_month_names[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL);
595           g_free (casefold);
596           
597           g_date_strftime (buf, 127, "%B", &d);
598           casefold = g_utf8_casefold (buf, -1);
599           g_free (long_month_names[i]);
600           long_month_names[i] = g_utf8_normalize (casefold, -1, G_NORMALIZE_ALL);
601           g_free (casefold);
602           
603           ++i;
604         }
605       
606       /* Determine DMY order */
607       
608       /* had to pick a random day - don't change this, some strftimes
609        * are broken on some days, and this one is good so far. */
610       g_date_set_dmy (&d, 4, 7, 1976);
611       
612       g_date_strftime (buf, 127, "%x", &d);
613       
614       g_date_fill_parse_tokens (buf, &testpt);
615       
616       i = 0;
617       while (i < testpt.num_ints)
618         {
619           switch (testpt.n[i])
620             {
621             case 7:
622               dmy_order[i] = G_DATE_MONTH;
623               break;
624             case 4:
625               dmy_order[i] = G_DATE_DAY;
626               break;
627             case 76:
628               using_twodigit_years = TRUE; /* FALL THRU */
629             case 1976:
630               dmy_order[i] = G_DATE_YEAR;
631               break;
632             default:
633               /* assume locale era */
634               locale_era_adjust = 1976 - testpt.n[i];
635               dmy_order[i] = G_DATE_YEAR;
636               break;
637             }
638           ++i;
639         }
640       
641 #ifdef G_ENABLE_DEBUG
642       DEBUG_MSG (("**GDate prepared a new set of locale-specific parse rules."));
643       i = 1;
644       while (i < 13) 
645         {
646           DEBUG_MSG (("  %s   %s", long_month_names[i], short_month_names[i]));
647           ++i;
648         }
649       if (using_twodigit_years)
650         DEBUG_MSG (("**Using twodigit years with cutoff year: %u", twodigit_start_year));
651       { 
652         gchar *strings[3];
653         i = 0;
654         while (i < 3)
655           {
656             switch (dmy_order[i])
657               {
658               case G_DATE_MONTH:
659                 strings[i] = "Month";
660                 break;
661               case G_DATE_YEAR:
662                 strings[i] = "Year";
663                 break;
664               case G_DATE_DAY:
665                 strings[i] = "Day";
666                 break;
667               default:
668                 strings[i] = NULL;
669                 break;
670               }
671             ++i;
672           }
673         DEBUG_MSG (("**Order: %s, %s, %s", strings[0], strings[1], strings[2]));
674         DEBUG_MSG (("**Sample date in this locale: `%s'", buf));
675       }
676 #endif
677     }
678   
679   g_date_fill_parse_tokens (str, pt);
680 }
681
682 void         
683 g_date_set_parse (GDate       *d, 
684                   const gchar *str)
685 {
686   GDateParseTokens pt;
687   guint m = G_DATE_BAD_MONTH, day = G_DATE_BAD_DAY, y = G_DATE_BAD_YEAR;
688   
689   g_return_if_fail (d != NULL);
690   
691   /* set invalid */
692   g_date_clear (d, 1);
693   
694   G_LOCK (g_date_global);
695
696   g_date_prepare_to_parse (str, &pt);
697   
698   DEBUG_MSG (("Found %d ints, `%d' `%d' `%d' and written out month %d", 
699               pt.num_ints, pt.n[0], pt.n[1], pt.n[2], pt.month));
700   
701   
702   if (pt.num_ints == 4) 
703     {
704       G_UNLOCK (g_date_global);
705       return; /* presumably a typo; bail out. */
706     }
707   
708   if (pt.num_ints > 1)
709     {
710       int i = 0;
711       int j = 0;
712       
713       g_assert (pt.num_ints < 4); /* i.e., it is 2 or 3 */
714       
715       while (i < pt.num_ints && j < 3) 
716         {
717           switch (dmy_order[j])
718             {
719             case G_DATE_MONTH:
720             {
721               if (pt.num_ints == 2 && pt.month != G_DATE_BAD_MONTH)
722                 {
723                   m = pt.month;
724                   ++j;      /* skip months, but don't skip this number */
725                   continue;
726                 }
727               else 
728                 m = pt.n[i];
729             }
730             break;
731             case G_DATE_DAY:
732             {
733               if (pt.num_ints == 2 && pt.month == G_DATE_BAD_MONTH)
734                 {
735                   day = 1;
736                   ++j;      /* skip days, since we may have month/year */
737                   continue;
738                 }
739               day = pt.n[i];
740             }
741             break;
742             case G_DATE_YEAR:
743             {
744               y  = pt.n[i];
745               
746               if (locale_era_adjust != 0)
747                 {
748                   y += locale_era_adjust;
749                 }
750               else if (using_twodigit_years && y < 100)
751                 {
752                   guint two     =  twodigit_start_year % 100;
753                   guint century = (twodigit_start_year / 100) * 100;
754                   
755                   if (y < two)
756                     century += 100;
757                   
758                   y += century;
759                 }
760             }
761             break;
762             default:
763               break;
764             }
765           
766           ++i;
767           ++j;
768         }
769       
770       
771       if (pt.num_ints == 3 && !g_date_valid_dmy (day, m, y))
772         {
773           /* Try YYYY MM DD */
774           y   = pt.n[0];
775           m   = pt.n[1];
776           day = pt.n[2];
777           
778           if (using_twodigit_years && y < 100) 
779             y = G_DATE_BAD_YEAR; /* avoids ambiguity */
780         }
781       else if (pt.num_ints == 2)
782         {
783           if (m == G_DATE_BAD_MONTH && pt.month != G_DATE_BAD_MONTH)
784             {
785               m = pt.month;
786             }
787         }
788     }
789   else if (pt.num_ints == 1) 
790     {
791       if (pt.month != G_DATE_BAD_MONTH)
792         {
793           /* Month name and year? */
794           m    = pt.month;
795           day  = 1;
796           y = pt.n[0];
797         }
798       else
799         {
800           /* Try yyyymmdd and yymmdd */
801           
802           m   = (pt.n[0]/100) % 100;
803           day = pt.n[0] % 100;
804           y   = pt.n[0]/10000;
805           
806           /* FIXME move this into a separate function */
807           if (using_twodigit_years && y < 100)
808             {
809               guint two     =  twodigit_start_year % 100;
810               guint century = (twodigit_start_year / 100) * 100;
811               
812               if (y < two)
813                 century += 100;
814               
815               y += century;
816             }
817         }
818     }
819   
820   /* See if we got anything valid out of all this. */
821   /* y < 8000 is to catch 19998 style typos; the library is OK up to 65535 or so */
822   if (y < 8000 && g_date_valid_dmy (day, m, y)) 
823     {
824       d->month = m;
825       d->day   = day;
826       d->year  = y;
827       d->dmy   = TRUE;
828     }
829 #ifdef G_ENABLE_DEBUG
830   else 
831     DEBUG_MSG (("Rejected DMY %u %u %u", day, m, y));
832 #endif
833   G_UNLOCK (g_date_global);
834 }
835
836 void         
837 g_date_set_time (GDate *d,
838                  GTime  time)
839 {
840   time_t t = time;
841   struct tm tm;
842   
843   g_return_if_fail (d != NULL);
844   
845 #ifdef HAVE_LOCALTIME_R
846   localtime_r (&t, &tm);
847 #else
848   {
849     struct tm *ptm = localtime (&t);
850     g_assert (ptm);
851     memcpy ((void *) &tm, (void *) ptm, sizeof(struct tm));
852   }
853 #endif
854   
855   d->julian = FALSE;
856   
857   d->month = tm.tm_mon + 1;
858   d->day   = tm.tm_mday;
859   d->year  = tm.tm_year + 1900;
860   
861   g_return_if_fail (g_date_valid_dmy (d->day, d->month, d->year));
862   
863   d->dmy    = TRUE;
864 }
865
866 void         
867 g_date_set_month (GDate     *d, 
868                   GDateMonth m)
869 {
870   g_return_if_fail (d != NULL);
871   g_return_if_fail (g_date_valid_month (m));
872
873   if (d->julian && !d->dmy) g_date_update_dmy(d);
874   d->julian = FALSE;
875   
876   d->month = m;
877   
878   if (g_date_valid_dmy (d->day, d->month, d->year))
879     d->dmy = TRUE;
880   else 
881     d->dmy = FALSE;
882 }
883
884 void         
885 g_date_set_day (GDate     *d, 
886                 GDateDay day)
887 {
888   g_return_if_fail (d != NULL);
889   g_return_if_fail (g_date_valid_day (day));
890   
891   if (d->julian && !d->dmy) g_date_update_dmy(d);
892   d->julian = FALSE;
893   
894   d->day = day;
895   
896   if (g_date_valid_dmy (d->day, d->month, d->year))
897     d->dmy = TRUE;
898   else 
899     d->dmy = FALSE;
900 }
901
902 void         
903 g_date_set_year (GDate     *d, 
904                  GDateYear  y)
905 {
906   g_return_if_fail (d != NULL);
907   g_return_if_fail (g_date_valid_year (y));
908   
909   if (d->julian && !d->dmy) g_date_update_dmy(d);
910   d->julian = FALSE;
911   
912   d->year = y;
913   
914   if (g_date_valid_dmy (d->day, d->month, d->year))
915     d->dmy = TRUE;
916   else 
917     d->dmy = FALSE;
918 }
919
920 void         
921 g_date_set_dmy (GDate     *d, 
922                 GDateDay   day, 
923                 GDateMonth m, 
924                 GDateYear  y)
925 {
926   g_return_if_fail (d != NULL);
927   g_return_if_fail (g_date_valid_dmy (day, m, y));
928   
929   d->julian = FALSE;
930   
931   d->month = m;
932   d->day   = day;
933   d->year  = y;
934   
935   d->dmy = TRUE;
936 }
937
938 void         
939 g_date_set_julian (GDate *d, guint32 j)
940 {
941   g_return_if_fail (d != NULL);
942   g_return_if_fail (g_date_valid_julian (j));
943   
944   d->julian_days = j;
945   d->julian = TRUE;
946   d->dmy = FALSE;
947 }
948
949
950 gboolean     
951 g_date_is_first_of_month (const GDate *d)
952 {
953   g_return_val_if_fail (d != NULL, FALSE);
954   g_return_val_if_fail (g_date_valid (d), FALSE);
955   
956   if (!d->dmy) 
957     {
958       g_date_update_dmy (d);
959     }
960   g_return_val_if_fail (d->dmy, FALSE);  
961   
962   if (d->day == 1) return TRUE;
963   else return FALSE;
964 }
965
966 gboolean     
967 g_date_is_last_of_month (const GDate *d)
968 {
969   gint index;
970   
971   g_return_val_if_fail (d != NULL, FALSE);
972   g_return_val_if_fail (g_date_valid (d), FALSE);
973   
974   if (!d->dmy) 
975     {
976       g_date_update_dmy (d);
977     }
978   g_return_val_if_fail (d->dmy, FALSE);  
979   
980   index = g_date_is_leap_year (d->year) ? 1 : 0;
981   
982   if (d->day == days_in_months[index][d->month]) return TRUE;
983   else return FALSE;
984 }
985
986 void         
987 g_date_add_days (GDate *d, guint ndays)
988 {
989   g_return_if_fail (d != NULL);
990   g_return_if_fail (g_date_valid (d));
991   
992   if (!d->julian)
993     {
994       g_date_update_julian (d);
995     }
996   g_return_if_fail (d->julian);
997   
998   d->julian_days += ndays;
999   d->dmy = FALSE;
1000 }
1001
1002 void         
1003 g_date_subtract_days (GDate *d, guint ndays)
1004 {
1005   g_return_if_fail (d != NULL);
1006   g_return_if_fail (g_date_valid (d));
1007   
1008   if (!d->julian)
1009     {
1010       g_date_update_julian (d);
1011     }
1012   g_return_if_fail (d->julian);
1013   g_return_if_fail (d->julian_days > ndays);
1014   
1015   d->julian_days -= ndays;
1016   d->dmy = FALSE;
1017 }
1018
1019 void         
1020 g_date_add_months (GDate       *d, 
1021                    guint        nmonths)
1022 {
1023   guint years, months;
1024   gint index;
1025   
1026   g_return_if_fail (d != NULL);
1027   g_return_if_fail (g_date_valid (d));
1028   
1029   if (!d->dmy) 
1030     {
1031       g_date_update_dmy (d);
1032     }
1033   g_return_if_fail (d->dmy);  
1034   
1035   nmonths += d->month - 1;
1036   
1037   years  = nmonths/12;
1038   months = nmonths%12;
1039   
1040   d->month = months + 1;
1041   d->year  += years;
1042   
1043   index = g_date_is_leap_year (d->year) ? 1 : 0;
1044   
1045   if (d->day > days_in_months[index][d->month])
1046     d->day = days_in_months[index][d->month];
1047   
1048   d->julian = FALSE;
1049   
1050   g_return_if_fail (g_date_valid (d));
1051 }
1052
1053 void         
1054 g_date_subtract_months (GDate       *d, 
1055                         guint        nmonths)
1056 {
1057   guint years, months;
1058   gint index;
1059   
1060   g_return_if_fail (d != NULL);
1061   g_return_if_fail (g_date_valid (d));
1062   
1063   if (!d->dmy) 
1064     {
1065       g_date_update_dmy (d);
1066     }
1067   g_return_if_fail (d->dmy);  
1068   
1069   years  = nmonths/12;
1070   months = nmonths%12;
1071   
1072   g_return_if_fail (d->year > years);
1073   
1074   d->year  -= years;
1075   
1076   if (d->month > months) d->month -= months;
1077   else 
1078     {
1079       months -= d->month;
1080       d->month = 12 - months;
1081       d->year -= 1;
1082     }
1083   
1084   index = g_date_is_leap_year (d->year) ? 1 : 0;
1085   
1086   if (d->day > days_in_months[index][d->month])
1087     d->day = days_in_months[index][d->month];
1088   
1089   d->julian = FALSE;
1090   
1091   g_return_if_fail (g_date_valid (d));
1092 }
1093
1094 void         
1095 g_date_add_years (GDate       *d, 
1096                   guint        nyears)
1097 {
1098   g_return_if_fail (d != NULL);
1099   g_return_if_fail (g_date_valid (d));
1100   
1101   if (!d->dmy) 
1102     {
1103       g_date_update_dmy (d);
1104     }
1105   g_return_if_fail (d->dmy);  
1106   
1107   d->year += nyears;
1108   
1109   if (d->month == 2 && d->day == 29)
1110     {
1111       if (!g_date_is_leap_year (d->year))
1112         {
1113           d->day = 28;
1114         }
1115     }
1116   
1117   d->julian = FALSE;
1118 }
1119
1120 void         
1121 g_date_subtract_years (GDate       *d, 
1122                        guint        nyears)
1123 {
1124   g_return_if_fail (d != NULL);
1125   g_return_if_fail (g_date_valid (d));
1126   
1127   if (!d->dmy) 
1128     {
1129       g_date_update_dmy (d);
1130     }
1131   g_return_if_fail (d->dmy);  
1132   g_return_if_fail (d->year > nyears);
1133   
1134   d->year -= nyears;
1135   
1136   if (d->month == 2 && d->day == 29)
1137     {
1138       if (!g_date_is_leap_year (d->year))
1139         {
1140           d->day = 28;
1141         }
1142     }
1143   
1144   d->julian = FALSE;
1145 }
1146
1147
1148 gboolean     
1149 g_date_is_leap_year (GDateYear  year)
1150 {
1151   g_return_val_if_fail (g_date_valid_year (year), FALSE);
1152   
1153   return ( (((year % 4) == 0) && ((year % 100) != 0)) ||
1154            (year % 400) == 0 );
1155 }
1156
1157 guint8         
1158 g_date_get_days_in_month (GDateMonth month, 
1159                           GDateYear  year)
1160 {
1161   gint index;
1162   
1163   g_return_val_if_fail (g_date_valid_year (year), 0);
1164   g_return_val_if_fail (g_date_valid_month (month), 0);
1165   
1166   index = g_date_is_leap_year (year) ? 1 : 0;
1167   
1168   return days_in_months[index][month];
1169 }
1170
1171 guint8       
1172 g_date_get_monday_weeks_in_year (GDateYear  year)
1173 {
1174   GDate d;
1175   
1176   g_return_val_if_fail (g_date_valid_year (year), 0);
1177   
1178   g_date_clear (&d, 1);
1179   g_date_set_dmy (&d, 1, 1, year);
1180   if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
1181   g_date_set_dmy (&d, 31, 12, year);
1182   if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
1183   if (g_date_is_leap_year (year)) 
1184     {
1185       g_date_set_dmy (&d, 2, 1, year);
1186       if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
1187       g_date_set_dmy (&d, 30, 12, year);
1188       if (g_date_get_weekday (&d) == G_DATE_MONDAY) return 53;
1189     }
1190   return 52;
1191 }
1192
1193 guint8       
1194 g_date_get_sunday_weeks_in_year (GDateYear  year)
1195 {
1196   GDate d;
1197   
1198   g_return_val_if_fail (g_date_valid_year (year), 0);
1199   
1200   g_date_clear (&d, 1);
1201   g_date_set_dmy (&d, 1, 1, year);
1202   if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
1203   g_date_set_dmy (&d, 31, 12, year);
1204   if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
1205   if (g_date_is_leap_year (year)) 
1206     {
1207       g_date_set_dmy (&d, 2, 1, year);
1208       if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
1209       g_date_set_dmy (&d, 30, 12, year);
1210       if (g_date_get_weekday (&d) == G_DATE_SUNDAY) return 53;
1211     }
1212   return 52;
1213 }
1214
1215 gint         
1216 g_date_compare (const GDate *lhs, 
1217                 const GDate *rhs)
1218 {
1219   g_return_val_if_fail (lhs != NULL, 0);
1220   g_return_val_if_fail (rhs != NULL, 0);
1221   g_return_val_if_fail (g_date_valid (lhs), 0);
1222   g_return_val_if_fail (g_date_valid (rhs), 0);
1223   
1224   /* Remember the self-comparison case! I think it works right now. */
1225   
1226   while (TRUE)
1227     {
1228       
1229       if (lhs->julian && rhs->julian) 
1230         {
1231           if (lhs->julian_days < rhs->julian_days) return -1;
1232           else if (lhs->julian_days > rhs->julian_days) return 1;
1233           else                                          return 0;
1234         }
1235       else if (lhs->dmy && rhs->dmy) 
1236         {
1237           if (lhs->year < rhs->year)               return -1;
1238           else if (lhs->year > rhs->year)               return 1;
1239           else 
1240             {
1241               if (lhs->month < rhs->month)         return -1;
1242               else if (lhs->month > rhs->month)         return 1;
1243               else 
1244                 {
1245                   if (lhs->day < rhs->day)              return -1;
1246                   else if (lhs->day > rhs->day)              return 1;
1247                   else                                       return 0;
1248                 }
1249               
1250             }
1251           
1252         }
1253       else
1254         {
1255           if (!lhs->julian) g_date_update_julian (lhs);
1256           if (!rhs->julian) g_date_update_julian (rhs);
1257           g_return_val_if_fail (lhs->julian, 0);
1258           g_return_val_if_fail (rhs->julian, 0);
1259         }
1260       
1261     }
1262   return 0; /* warnings */
1263 }
1264
1265
1266 void        
1267 g_date_to_struct_tm (const GDate *d, 
1268                      struct tm   *tm)
1269 {
1270   GDateWeekday day;
1271      
1272   g_return_if_fail (d != NULL);
1273   g_return_if_fail (g_date_valid (d));
1274   g_return_if_fail (tm != NULL);
1275   
1276   if (!d->dmy) 
1277     {
1278       g_date_update_dmy (d);
1279     }
1280   g_return_if_fail (d->dmy);
1281   
1282   /* zero all the irrelevant fields to be sure they're valid */
1283   
1284   /* On Linux and maybe other systems, there are weird non-POSIX
1285    * fields on the end of struct tm that choke strftime if they
1286    * contain garbage.  So we need to 0 the entire struct, not just the
1287    * fields we know to exist. 
1288    */
1289   
1290   memset (tm, 0x0, sizeof (struct tm));
1291   
1292   tm->tm_mday = d->day;
1293   tm->tm_mon  = d->month - 1; /* 0-11 goes in tm */
1294   tm->tm_year = ((int)d->year) - 1900; /* X/Open says tm_year can be negative */
1295   
1296   day = g_date_get_weekday (d);
1297   if (day == 7) day = 0; /* struct tm wants days since Sunday, so Sunday is 0 */
1298   
1299   tm->tm_wday = (int)day;
1300   
1301   tm->tm_yday = g_date_get_day_of_year (d) - 1; /* 0 to 365 */
1302   tm->tm_isdst = -1; /* -1 means "information not available" */
1303 }
1304
1305 void
1306 g_date_clamp (GDate *date,
1307               const GDate *min_date,
1308               const GDate *max_date)
1309 {
1310   g_return_if_fail (date);
1311   g_return_if_fail (g_date_valid (date));
1312   if (min_date != NULL)
1313     g_return_if_fail (g_date_valid (min_date));
1314   if (max_date != NULL)
1315     g_return_if_fail (g_date_valid (max_date));
1316   if (min_date != NULL && max_date != NULL)
1317     g_return_if_fail (g_date_compare (min_date, max_date) <= 0);
1318
1319   if (min_date && g_date_compare (date, min_date) < 0)
1320     *date = *min_date;
1321
1322   if (max_date && g_date_compare (max_date, date) < 0)
1323     *date = *max_date;
1324 }
1325
1326 void
1327 g_date_order (GDate *date1,
1328               GDate *date2)
1329 {
1330   g_return_if_fail (date1 != NULL);
1331   g_return_if_fail (date2 != NULL);
1332   g_return_if_fail (g_date_valid (date1));
1333   g_return_if_fail (g_date_valid (date2));
1334
1335   if (g_date_compare (date1, date2) > 0)
1336     {
1337       GDate tmp = *date1;
1338       *date1 = *date2;
1339       *date2 = tmp;
1340     }
1341 }
1342
1343 gsize     
1344 g_date_strftime (gchar       *s, 
1345                  gsize        slen, 
1346                  const gchar *format, 
1347                  const GDate *d)
1348 {
1349   struct tm tm;
1350   gsize locale_format_len = 0;
1351   gchar *locale_format;
1352   gsize tmplen;
1353   gchar *tmpbuf;
1354   gsize tmpbufsize;
1355   gsize convlen = 0;
1356   gchar *convbuf;
1357   GError *error = NULL;
1358   gsize retval;
1359
1360   g_return_val_if_fail (d != NULL, 0);
1361   g_return_val_if_fail (g_date_valid (d), 0);
1362   g_return_val_if_fail (slen > 0, 0); 
1363   g_return_val_if_fail (format != 0, 0);
1364   g_return_val_if_fail (s != 0, 0);
1365
1366   g_date_to_struct_tm (d, &tm);
1367
1368   locale_format = g_locale_from_utf8 (format, -1, NULL, &locale_format_len, &error);
1369
1370   if (error)
1371     {
1372       g_warning (G_STRLOC "Error converting format to locale encoding: %s\n", error->message);
1373       g_error_free (error);
1374
1375       s[0] = '\0';
1376       return 0;
1377     }
1378
1379   tmpbufsize = MAX (128, locale_format_len * 2);
1380   while (TRUE)
1381     {
1382       tmpbuf = g_malloc (tmpbufsize);
1383
1384       /* Set the first byte to something other than '\0', to be able to
1385        * recognize whether strftime actually failed or just returned "".
1386        */
1387       tmpbuf[0] = '\1';
1388       tmplen = strftime (tmpbuf, tmpbufsize, locale_format, &tm);
1389
1390       if (tmplen == 0 && tmpbuf[0] != '\0')
1391         {
1392           g_free (tmpbuf);
1393           tmpbufsize *= 2;
1394
1395           if (tmpbufsize > 65536)
1396             {
1397               g_warning (G_STRLOC "Maximum buffer size for g_date_strftime exceeded: giving up\n");
1398               g_free (locale_format);
1399
1400               s[0] = '\0';
1401               return 0;
1402             }
1403         }
1404       else
1405         break;
1406     }
1407   g_free (locale_format);
1408
1409   convbuf = g_locale_to_utf8 (tmpbuf, tmplen, NULL, &convlen, &error);
1410   g_free (tmpbuf);
1411
1412   if (error)
1413     {
1414       g_warning (G_STRLOC "Error converting results of strftime to UTF-8: %s\n", error->message);
1415       g_error_free (error);
1416
1417       s[0] = '\0';
1418       return 0;
1419     }
1420
1421   if (slen <= convlen)
1422     {
1423       /* Ensure only whole characters are copied into the buffer.
1424        */
1425       gchar *end = g_utf8_find_prev_char (convbuf, convbuf + slen);
1426       g_assert (end != NULL);
1427       convlen = end - convbuf;
1428
1429       /* Return 0 because the buffer isn't large enough.
1430        */
1431       retval = 0;
1432     }
1433   else
1434     retval = convlen;
1435
1436   memcpy (s, convbuf, convlen);
1437   s[convlen] = '\0';
1438   g_free (convbuf);
1439
1440   return retval;
1441 }
1442