Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-time-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * Time utility functions
4  *
5  * Author:
6  *   Damon Chaplin (damon@ximian.com)
7  *
8  * (C) 2001 Ximian, Inc.
9  */
10
11 #include <config.h>
12
13 #ifdef __linux__
14 /* We need this to get a prototype for strptime. */
15 #define _GNU_SOURCE
16 #endif /* __linux__ */
17
18 #include <time.h>
19 #include <sys/time.h>
20
21 #ifdef __linux__
22 #undef _GNU_SOURCE
23 #endif /* __linux__ */
24
25 #include <string.h>
26 #include <ctype.h>
27 #include <glib.h>
28 #include <glib/gi18n-lib.h>
29 #include "e-time-utils.h"
30 #include "e-data-server-util.h"
31
32 #ifdef G_OS_WIN32
33 /* The localtime_r() definition in pthreads-win32's pthread.h doesn't guard
34  * against localtime() returning NULL.
35  */
36 #undef localtime_r
37 /* The localtime() in Microsoft's C library is MT-safe */
38 #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0)
39
40 #include <windows.h>
41
42 static const gchar *
43 get_locale_string (int lctype)
44 {
45         int nbytes = GetLocaleInfo (GetThreadLocale (), lctype, NULL, 0);
46         char *tem;
47         GQuark quark; 
48
49         if (nbytes == 0)
50                 return "???";
51
52         tem = g_malloc (nbytes);
53
54         if (GetLocaleInfo (GetThreadLocale (), lctype, tem, nbytes) == 0) {
55                 g_free (tem);
56                 return "???";
57         }
58
59         quark = g_quark_from_string (tem);
60         g_free (tem);
61
62         return g_quark_to_string (quark);
63 }
64
65 static const char *
66 translate_picture (const char *picture)
67 {
68         GString *s = g_string_new ("");
69         GQuark quark;
70
71         while (*picture) {
72                 const char *q = picture + 1;
73                 int count;
74
75                 while (*picture == *q)
76                         q++;
77                 count = q - picture;
78
79                 switch (*picture) {
80                 case '\'':
81                         picture++;
82                         while (*picture && *picture != '\'') {
83                                 g_string_append_c (s, *picture);
84                                 picture++;
85                         }
86                         break;
87                 case 'd':
88                         switch (count) {
89                         case 1:
90                         case 2:
91                                 g_string_append (s, "%d");
92                                 break;
93                         case 3:
94                         case 4:
95                                 g_string_append (s, "%a");
96                                 break;
97                         }
98                         picture += count - 1;
99                         break;
100                 case 'M':
101                         switch (count) {
102                         case 1:
103                         case 2:
104                                 g_string_append (s, "%m");
105                                 break;
106                         case 3:
107                         case 4:
108                                 g_string_append (s, "%b");
109                                 break;
110                         }
111                         picture += count - 1;
112                         break;
113                 case 'y':
114                         switch (count) {
115                         case 1: /* Last digit of year. Ugh... */
116                         case 2:
117                                 g_string_append (s, "%y");
118                                 break;
119                         case 4:
120                                 g_string_append (s, "%Y");
121                                 break;
122                         }
123                         picture += count - 1;
124                         break;
125                 case 'g':
126                         /* Era. Huh. Just ignore, as the era stuff
127                          * implementation below depends on glibc.
128                          */
129                         picture += count - 1;
130                         break;
131                 case 'h':
132                         g_string_append (s, "%I");
133                         picture += count - 1;
134                         break;
135                 case 'H':
136                         g_string_append (s, "%H");
137                         picture += count - 1;
138                         break;
139                 case 'm':
140                         g_string_append (s, "%M");
141                         picture += count - 1;
142                         break;
143                 case 's':
144                         g_string_append (s, "%S");
145                         picture += count - 1;
146                         break;
147                 case 't':
148                         g_string_append (s, "%p");
149                         picture += count - 1;
150                         break;
151                 default:
152                         g_string_append_c (s, *picture);
153                         break;
154                 }
155                 if (*picture)
156                         picture++;
157         }
158
159         quark = g_quark_from_string (s->str);
160         g_string_free (s, TRUE);
161
162         return g_quark_to_string (quark);
163 }
164
165 #endif
166
167 #ifndef HAVE_STRPTIME
168
169 /* strptime() implementation lifted from glibc */
170
171 enum ptime_locale_status { not, loc, raw };
172
173 /* Copyright (C) 2002, 2004 Free Software Foundation, Inc.
174    This file is part of the GNU C Library.
175
176    The GNU C Library is free software; you can redistribute it and/or
177    modify it under the terms of the GNU Lesser General Public
178    License as published by the Free Software Foundation; either
179    version 2.1 of the License, or (at your option) any later version.
180
181    The GNU C Library is distributed in the hope that it will be useful,
182    but WITHOUT ANY WARRANTY; without even the implied warranty of
183    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
184    Lesser General Public License for more details.
185
186    You should have received a copy of the GNU Lesser General Public
187    License along with the GNU C Library; if not, write to the Free
188    Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
189    02110-1301 USA.  */
190
191 #ifdef HAVE_CONFIG_H
192 # include <config.h>
193 #endif
194
195 #include <assert.h>
196 #include <ctype.h>
197 #include <limits.h>
198 #include <string.h>
199 #include <time.h>
200
201 #ifdef _LIBC
202 # include "../locale/localeinfo.h"
203 #endif
204
205
206 #ifndef __P
207 # if defined __GNUC__ || (defined __STDC__ && __STDC__)
208 #  define __P(args) args
209 # else
210 #  define __P(args) ()
211 # endif  /* GCC.  */
212 #endif  /* Not __P.  */
213
214
215 #if ! HAVE_LOCALTIME_R && ! defined localtime_r
216 # ifdef _LIBC
217 #  define localtime_r __localtime_r
218 # else
219 /* Approximate localtime_r as best we can in its absence.  */
220 #  define localtime_r my_localtime_r
221 static struct tm *localtime_r __P ((const time_t *, struct tm *));
222 static struct tm *
223 localtime_r (t, tp)
224      const time_t *t;
225      struct tm *tp;
226 {
227   struct tm *l = localtime (t);
228   if (! l)
229     return 0;
230   *tp = *l;
231   return tp;
232 }
233 # endif /* ! _LIBC */
234 #endif /* ! HAVE_LOCALTIME_R && ! defined (localtime_r) */
235
236
237 #define match_char(ch1, ch2) if (ch1 != ch2) return NULL
238 #if defined _LIBC && defined __GNUC__ && __GNUC__ >= 2
239 # define match_string(cs1, s2) \
240   ({ size_t len = strlen (cs1);                                               \
241      int result = __strncasecmp_l ((cs1), (s2), len, locale) == 0;            \
242      if (result) (s2) += len;                                                 \
243      result; })
244 #else
245 /* Oh come on.  Get a reasonable compiler.  */
246 # define match_string(cs1, s2) \
247   (g_ascii_strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1))
248 #endif
249 /* We intentionally do not use isdigit() for testing because this will
250    lead to problems with the wide character version.  */
251 #define get_number(from, to, n) \
252   do {                                                                        \
253     int __n = n;                                                              \
254     val = 0;                                                                  \
255     while (*rp == ' ')                                                        \
256       ++rp;                                                                   \
257     if (*rp < '0' || *rp > '9')                                               \
258       return NULL;                                                            \
259     do {                                                                      \
260       val *= 10;                                                              \
261       val += *rp++ - '0';                                                     \
262     } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9');        \
263     if (val < from || val > to)                                               \
264       return NULL;                                                            \
265   } while (0)
266 #ifdef _NL_CURRENT
267 # define get_alt_number(from, to, n) \
268   ({                                                                          \
269      __label__ do_normal;                                                     \
270                                                                               \
271      if (*decided != raw)                                                     \
272        {                                                                      \
273          val = _nl_parse_alt_digit (&rp HELPER_LOCALE_ARG);                   \
274          if (val == -1 && *decided != loc)                                    \
275            {                                                                  \
276              *decided = loc;                                                  \
277              goto do_normal;                                                  \
278            }                                                                  \
279         if (val < from || val > to)                                           \
280           return NULL;                                                        \
281        }                                                                      \
282      else                                                                     \
283        {                                                                      \
284        do_normal:                                                             \
285          get_number (from, to, n);                                            \
286        }                                                                      \
287     0;                                                                        \
288   })
289 #else
290 # define get_alt_number(from, to, n) \
291   /* We don't have the alternate representation.  */                          \
292   get_number(from, to, n)
293 #endif
294 #define recursive(new_fmt) \
295   (*(new_fmt) != '\0'                                                         \
296    && (rp = __strptime_internal (rp, (new_fmt), tm,                           \
297                                  decided, era_cnt LOCALE_ARG)) != NULL)
298
299
300 #ifdef _LIBC
301 /* This is defined in locale/C-time.c in the GNU libc.  */
302 extern const struct locale_data _nl_C_LC_TIME attribute_hidden;
303
304 # define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string)
305 # define ab_weekday_name \
306   (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string)
307 # define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string)
308 # define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string)
309 # define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string)
310 # define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string)
311 # define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string)
312 # define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string)
313 # define HERE_T_FMT_AMPM \
314   (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string)
315 # define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string)
316
317 # define strncasecmp(s1, s2, n) __strncasecmp (s1, s2, n)
318 #else
319 static char const weekday_name[][10] =
320   {
321     "Sunday", "Monday", "Tuesday", "Wednesday",
322     "Thursday", "Friday", "Saturday"
323   };
324 static char const ab_weekday_name[][4] =
325   {
326     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
327   };
328 static char const month_name[][10] =
329   {
330     "January", "February", "March", "April", "May", "June",
331     "July", "August", "September", "October", "November", "December"
332   };
333 static char const ab_month_name[][4] =
334   {
335     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
336     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
337   };
338 # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y"
339 # define HERE_D_FMT "%m/%d/%y"
340 # define HERE_AM_STR "AM"
341 # define HERE_PM_STR "PM"
342 # define HERE_T_FMT_AMPM "%I:%M:%S %p"
343 # define HERE_T_FMT "%H:%M:%S"
344
345 static const unsigned short int __mon_yday[2][13] =
346   {
347     /* Normal years.  */
348     { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
349     /* Leap years.  */
350     { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
351   };
352 #endif
353
354 #if defined _LIBC
355 /* We use this code also for the extended locale handling where the
356    function gets as an additional argument the locale which has to be
357    used.  To access the values we have to redefine the _NL_CURRENT
358    macro.  */
359 # define strptime               __strptime_l
360 # undef _NL_CURRENT
361 # define _NL_CURRENT(category, item) \
362   (current->values[_NL_ITEM_INDEX (item)].string)
363 # undef _NL_CURRENT_WORD
364 # define _NL_CURRENT_WORD(category, item) \
365   (current->values[_NL_ITEM_INDEX (item)].word)
366 # define LOCALE_PARAM , locale
367 # define LOCALE_ARG , locale
368 # define LOCALE_PARAM_PROTO , __locale_t locale
369 # define LOCALE_PARAM_DECL __locale_t locale;
370 # define HELPER_LOCALE_ARG , current
371 # define ISSPACE(Ch) __isspace_l (Ch, locale)
372 #else
373 # define LOCALE_PARAM
374 # define LOCALE_ARG
375 # define LOCALE_PARAM_DECL
376 # define LOCALE_PARAM_PROTO
377 # define HELPER_LOCALE_ARG
378 # define ISSPACE(Ch) isspace (Ch)
379 #endif
380
381
382
383
384 #ifndef __isleap
385 /* Nonzero if YEAR is a leap year (every 4 years,
386    except every 100th isn't, and every 400th is).  */
387 # define __isleap(year) \
388   ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
389 #endif
390
391 /* Compute the day of the week.  */
392 static void
393 day_of_the_week (struct tm *tm)
394 {
395   /* We know that January 1st 1970 was a Thursday (= 4).  Compute the
396      the difference between this data in the one on TM and so determine
397      the weekday.  */
398   int corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2);
399   int wday = (-473
400               + (365 * (tm->tm_year - 70))
401               + (corr_year / 4)
402               - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0)
403               + (((corr_year / 4) / 25) / 4)
404               + __mon_yday[0][tm->tm_mon]
405               + tm->tm_mday - 1);
406   tm->tm_wday = ((wday % 7) + 7) % 7;
407 }
408
409 /* Compute the day of the year.  */
410 static void
411 day_of_the_year (struct tm *tm)
412 {
413   tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon]
414                  + (tm->tm_mday - 1));
415 }
416
417
418 #ifdef _LIBC
419 char *
420 internal_function
421 #else
422 static char *
423 #endif
424 __strptime_internal (rp, fmt, tm, decided, era_cnt LOCALE_PARAM)
425      const char *rp;
426      const char *fmt;
427      struct tm *tm;
428      enum ptime_locale_status *decided;
429      int era_cnt;
430      LOCALE_PARAM_DECL
431 {
432 #ifdef _LIBC
433   struct locale_data *const current = locale->__locales[LC_TIME];
434 #endif
435
436   const char *rp_backup;
437   int cnt;
438   size_t val;
439   int have_I, is_pm;
440   int century, want_century;
441   int want_era;
442   int have_wday, want_xday;
443   int have_yday;
444   int have_mon, have_mday;
445   int have_uweek, have_wweek;
446   int week_no;
447 #ifdef _NL_CURRENT
448   size_t num_eras;
449 #endif
450   struct era_entry *era;
451
452   have_I = is_pm = 0;
453   century = -1;
454   want_century = 0;
455   want_era = 0;
456   era = NULL;
457   week_no = 0;
458
459   have_wday = want_xday = have_yday = have_mon = have_mday = have_uweek = 0;
460   have_wweek = 0;
461
462   while (*fmt != '\0')
463     {
464       /* A white space in the format string matches 0 more or white
465          space in the input string.  */
466       if (ISSPACE (*fmt))
467         {
468           while (ISSPACE (*rp))
469             ++rp;
470           ++fmt;
471           continue;
472         }
473
474       /* Any character but `%' must be matched by the same character
475          in the iput string.  */
476       if (*fmt != '%')
477         {
478           match_char (*fmt++, *rp++);
479           continue;
480         }
481
482       ++fmt;
483 #ifndef _NL_CURRENT
484       /* We need this for handling the `E' modifier.  */
485     start_over:
486 #endif
487
488       /* Make back up of current processing pointer.  */
489       rp_backup = rp;
490
491       switch (*fmt++)
492         {
493         case '%':
494           /* Match the `%' character itself.  */
495           match_char ('%', *rp++);
496           break;
497         case 'a':
498         case 'A':
499           /* Match day of week.  */
500           for (cnt = 0; cnt < 7; ++cnt)
501             {
502 #ifdef _NL_CURRENT
503               if (*decided !=raw)
504                 {
505                   if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp))
506                     {
507                       if (*decided == not
508                           && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt),
509                                      weekday_name[cnt]))
510                         *decided = loc;
511                       break;
512                     }
513                   if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp))
514                     {
515                       if (*decided == not
516                           && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt),
517                                      ab_weekday_name[cnt]))
518                         *decided = loc;
519                       break;
520                     }
521                 }
522 #elif defined (G_OS_WIN32)
523               if (*decided !=raw)
524                 {
525                   if (match_string (get_locale_string (LOCALE_SDAYNAME1 + cnt), rp))
526                     {
527                       if (*decided == not
528                           && strcmp (get_locale_string (LOCALE_SDAYNAME1 + cnt),
529                                      weekday_name[cnt]))
530                         *decided = loc;
531                       break;
532                     }
533                   if (match_string (get_locale_string (LOCALE_SABBREVDAYNAME1 + cnt), rp))
534                     {
535                       if (*decided == not
536                           && strcmp (get_locale_string (LOCALE_SABBREVDAYNAME1 + cnt),
537                                      ab_weekday_name[cnt]))
538                         *decided = loc;
539                       break;
540                     }
541                 }
542 #endif
543               if (*decided != loc
544                   && (match_string (weekday_name[cnt], rp)
545                       || match_string (ab_weekday_name[cnt], rp)))
546                 {
547                   *decided = raw;
548                   break;
549                 }
550             }
551           if (cnt == 7)
552             /* Does not match a weekday name.  */
553             return NULL;
554           tm->tm_wday = cnt;
555           have_wday = 1;
556           break;
557         case 'b':
558         case 'B':
559         case 'h':
560           /* Match month name.  */
561           for (cnt = 0; cnt < 12; ++cnt)
562             {
563 #ifdef _NL_CURRENT
564               if (*decided !=raw)
565                 {
566                   if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp))
567                     {
568                       if (*decided == not
569                           && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt),
570                                      month_name[cnt]))
571                         *decided = loc;
572                       break;
573                     }
574                   if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp))
575                     {
576                       if (*decided == not
577                           && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt),
578                                      ab_month_name[cnt]))
579                         *decided = loc;
580                       break;
581                     }
582                 }
583 #elif defined (G_OS_WIN32)
584               if (*decided !=raw)
585                 {
586                   if (match_string (get_locale_string (LOCALE_SMONTHNAME1 + cnt), rp))
587                     {
588                       if (*decided == not
589                           && strcmp (get_locale_string (LOCALE_SMONTHNAME1 + cnt),
590                                      month_name[cnt]))
591                         *decided = loc;
592                       break;
593                     }
594                   if (match_string (get_locale_string (LOCALE_SABBREVMONTHNAME1 + cnt), rp))
595                     {
596                       if (*decided == not
597                           && strcmp (get_locale_string (LOCALE_SABBREVMONTHNAME1 + cnt),
598                                      ab_month_name[cnt]))
599                         *decided = loc;
600                       break;
601                     }
602                 }
603 #endif
604               if (match_string (month_name[cnt], rp)
605                   || match_string (ab_month_name[cnt], rp))
606                 {
607                   *decided = raw;
608                   break;
609                 }
610             }
611           if (cnt == 12)
612             /* Does not match a month name.  */
613             return NULL;
614           tm->tm_mon = cnt;
615           want_xday = 1;
616           break;
617         case 'c':
618           /* Match locale's date and time format.  */
619 #ifdef _NL_CURRENT
620           if (*decided != raw)
621             {
622               if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT)))
623                 {
624                   if (*decided == loc)
625                     return NULL;
626                   else
627                     rp = rp_backup;
628                 }
629               else
630                 {
631                   if (*decided == not &&
632                       strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT))
633                     *decided = loc;
634                   want_xday = 1;
635                   break;
636                 }
637               *decided = raw;
638             }
639 #elif defined (G_OS_WIN32)
640           if (*decided != raw)
641             {
642               char *d_t_fmt =
643                       g_strconcat (get_locale_string (LOCALE_SSHORTDATE),
644                                    " ",
645                                    get_locale_string (LOCALE_STIMEFORMAT),
646                                    NULL);
647               const char *posix_d_t_fmt = translate_picture (d_t_fmt);
648               
649               g_free (d_t_fmt);
650                       
651               if (!recursive (posix_d_t_fmt))
652                 {
653                   if (*decided == loc)
654                     return NULL;
655                   else
656                     rp = rp_backup;
657                 }
658               else
659                 {
660                   if (*decided == not &&
661                       strcmp (posix_d_t_fmt, HERE_D_T_FMT))
662                     *decided = loc;
663                   want_xday = 1;
664                   break;
665                 }
666               *decided = raw;
667             }
668 #endif
669           if (!recursive (HERE_D_T_FMT))
670             return NULL;
671           want_xday = 1;
672           break;
673         case 'C':
674           /* Match century number.  */
675 #ifdef _NL_CURRENT
676         match_century:
677 #endif
678           get_number (0, 99, 2);
679           century = val;
680           want_xday = 1;
681           break;
682         case 'd':
683         case 'e':
684           /* Match day of month.  */
685           get_number (1, 31, 2);
686           tm->tm_mday = val;
687           have_mday = 1;
688           want_xday = 1;
689           break;
690         case 'F':
691           if (!recursive ("%Y-%m-%d"))
692             return NULL;
693           want_xday = 1;
694           break;
695         case 'x':
696 #ifdef _NL_CURRENT
697           if (*decided != raw)
698             {
699               if (!recursive (_NL_CURRENT (LC_TIME, D_FMT)))
700                 {
701                   if (*decided == loc)
702                     return NULL;
703                   else
704                     rp = rp_backup;
705                 }
706               else
707                 {
708                   if (*decided == not
709                       && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT))
710                     *decided = loc;
711                   want_xday = 1;
712                   break;
713                 }
714               *decided = raw;
715             }
716 #elif defined (G_OS_WIN32)
717           if (*decided != raw)
718             {
719                 const char *posix_d_fmt = translate_picture (get_locale_string (LOCALE_SSHORTDATE));
720                 if (!recursive (posix_d_fmt))
721                 {
722                   if (*decided == loc)
723                     return NULL;
724                   else
725                     rp = rp_backup;
726                 }
727               else
728                 {
729                   if (*decided == not
730                       && strcmp (posix_d_fmt, HERE_D_FMT))
731                     *decided = loc;
732                   want_xday = 1;
733                   break;
734                 }
735               *decided = raw;
736             }
737 #endif
738           /* Fall through.  */
739         case 'D':
740           /* Match standard day format.  */
741           if (!recursive (HERE_D_FMT))
742             return NULL;
743           want_xday = 1;
744           break;
745         case 'k':
746         case 'H':
747           /* Match hour in 24-hour clock.  */
748           get_number (0, 23, 2);
749           tm->tm_hour = val;
750           have_I = 0;
751           break;
752         case 'l':
753           /* Match hour in 12-hour clock.  GNU extension.  */
754         case 'I':
755           /* Match hour in 12-hour clock.  */
756           get_number (1, 12, 2);
757           tm->tm_hour = val % 12;
758           have_I = 1;
759           break;
760         case 'j':
761           /* Match day number of year.  */
762           get_number (1, 366, 3);
763           tm->tm_yday = val - 1;
764           have_yday = 1;
765           break;
766         case 'm':
767           /* Match number of month.  */
768           get_number (1, 12, 2);
769           tm->tm_mon = val - 1;
770           have_mon = 1;
771           want_xday = 1;
772           break;
773         case 'M':
774           /* Match minute.  */
775           get_number (0, 59, 2);
776           tm->tm_min = val;
777           break;
778         case 'n':
779         case 't':
780           /* Match any white space.  */
781           while (ISSPACE (*rp))
782             ++rp;
783           break;
784         case 'p':
785           /* Match locale's equivalent of AM/PM.  */
786 #ifdef _NL_CURRENT
787           if (*decided != raw)
788             {
789               if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp))
790                 {
791                   if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR))
792                     *decided = loc;
793                   break;
794                 }
795               if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp))
796                 {
797                   if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR))
798                     *decided = loc;
799                   is_pm = 1;
800                   break;
801                 }
802               *decided = raw;
803             }
804 #elif defined (G_OS_WIN32)
805           if (*decided != raw)
806             {
807                 if (match_string (get_locale_string (LOCALE_S1159), rp))
808                 {
809                     if (strcmp (get_locale_string (LOCALE_S1159), HERE_AM_STR))
810                     *decided = loc;
811                   break;
812                 }
813                 if (match_string (get_locale_string (LOCALE_S2359), rp))
814                 {
815                   if (strcmp (get_locale_string (LOCALE_S2359), HERE_PM_STR))
816                     *decided = loc;
817                   is_pm = 1;
818                   break;
819                 }
820               *decided = raw;
821             }
822 #endif
823           if (!match_string (HERE_AM_STR, rp))
824             {
825               if (match_string (HERE_PM_STR, rp))
826                 is_pm = 1;
827               else
828                 return NULL;
829             }
830           break;
831         case 'r':
832 #ifdef _NL_CURRENT
833           if (*decided != raw)
834             {
835               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM)))
836                 {
837                   if (*decided == loc)
838                     return NULL;
839                   else
840                     rp = rp_backup;
841                 }
842               else
843                 {
844                   if (*decided == not &&
845                       strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM),
846                               HERE_T_FMT_AMPM))
847                     *decided = loc;
848                   break;
849                 }
850               *decided = raw;
851             }
852 #elif defined (G_OS_WIN32)
853           if (*decided != raw)
854             {
855               char *t_p_fmt =
856                       g_strconcat (get_locale_string (LOCALE_STIMEFORMAT),
857                                    " tt",
858                                    NULL);
859               const char *posix_t_p_fmt = translate_picture (t_p_fmt);
860
861               g_free (t_p_fmt);
862
863               if (!recursive (posix_t_p_fmt))
864                 {
865                   if (*decided == loc)
866                     return NULL;
867                   else
868                     rp = rp_backup;
869                 }
870               else
871                 {
872                   if (*decided == not &&
873                       strcmp (posix_t_p_fmt,
874                               HERE_T_FMT_AMPM))
875                     *decided = loc;
876                   break;
877                 }
878               *decided = raw;
879             }
880 #endif
881           if (!recursive (HERE_T_FMT_AMPM))
882             return NULL;
883           break;
884         case 'R':
885           if (!recursive ("%H:%M"))
886             return NULL;
887           break;
888         case 's':
889           {
890             /* The number of seconds may be very high so we cannot use
891                the `get_number' macro.  Instead read the number
892                character for character and construct the result while
893                doing this.  */
894             time_t secs = 0;
895             if (*rp < '0' || *rp > '9')
896               /* We need at least one digit.  */
897               return NULL;
898
899             do
900               {
901                 secs *= 10;
902                 secs += *rp++ - '0';
903               }
904             while (*rp >= '0' && *rp <= '9');
905
906             if (localtime_r (&secs, tm) == NULL)
907               /* Error in function.  */
908               return NULL;
909           }
910           break;
911         case 'S':
912           get_number (0, 61, 2);
913           tm->tm_sec = val;
914           break;
915         case 'X':
916 #ifdef _NL_CURRENT
917           if (*decided != raw)
918             {
919               if (!recursive (_NL_CURRENT (LC_TIME, T_FMT)))
920                 {
921                   if (*decided == loc)
922                     return NULL;
923                   else
924                     rp = rp_backup;
925                 }
926               else
927                 {
928                   if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT))
929                     *decided = loc;
930                   break;
931                 }
932               *decided = raw;
933             }
934 #elif defined (G_OS_WIN32)
935           if (*decided != raw)
936             {
937               const char *posix_t_fmt = translate_picture (get_locale_string (LOCALE_STIMEFORMAT));
938               if (!recursive (posix_t_fmt))
939                 {
940                   if (*decided == loc)
941                     return NULL;
942                   else
943                     rp = rp_backup;
944                 }
945               else
946                 {
947                   if (strcmp (posix_t_fmt, HERE_T_FMT))
948                     *decided = loc;
949                   break;
950                 }
951               *decided = raw;
952             }
953 #endif
954           /* Fall through.  */
955         case 'T':
956           if (!recursive (HERE_T_FMT))
957             return NULL;
958           break;
959         case 'u':
960           get_number (1, 7, 1);
961           tm->tm_wday = val % 7;
962           have_wday = 1;
963           break;
964         case 'g':
965           get_number (0, 99, 2);
966           /* XXX This cannot determine any field in TM.  */
967           break;
968         case 'G':
969           if (*rp < '0' || *rp > '9')
970             return NULL;
971           /* XXX Ignore the number since we would need some more
972              information to compute a real date.  */
973           do
974             ++rp;
975           while (*rp >= '0' && *rp <= '9');
976           break;
977         case 'U':
978           get_number (0, 53, 2);
979           week_no = val;
980           have_uweek = 1;
981           break;
982         case 'W':
983           get_number (0, 53, 2);
984           week_no = val;
985           have_wweek = 1;
986           break;
987         case 'V':
988           get_number (0, 53, 2);
989           /* XXX This cannot determine any field in TM without some
990              information.  */
991           break;
992         case 'w':
993           /* Match number of weekday.  */
994           get_number (0, 6, 1);
995           tm->tm_wday = val;
996           have_wday = 1;
997           break;
998         case 'y':
999 #ifdef _NL_CURRENT
1000         match_year_in_century:
1001 #endif
1002           /* Match year within century.  */
1003           get_number (0, 99, 2);
1004           /* The "Year 2000: The Millennium Rollover" paper suggests that
1005              values in the range 69-99 refer to the twentieth century.  */
1006           tm->tm_year = val >= 69 ? val : val + 100;
1007           /* Indicate that we want to use the century, if specified.  */
1008           want_century = 1;
1009           want_xday = 1;
1010           break;
1011         case 'Y':
1012           /* Match year including century number.  */
1013           get_number (0, 9999, 4);
1014           tm->tm_year = val - 1900;
1015           want_century = 0;
1016           want_xday = 1;
1017           break;
1018         case 'Z':
1019           /* XXX How to handle this?  */
1020           break;
1021         case 'E':
1022 #ifdef _NL_CURRENT
1023           switch (*fmt++)
1024             {
1025             case 'c':
1026               /* Match locale's alternate date and time format.  */
1027               if (*decided != raw)
1028                 {
1029                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT);
1030
1031                   if (*fmt == '\0')
1032                     fmt = _NL_CURRENT (LC_TIME, D_T_FMT);
1033
1034                   if (!recursive (fmt))
1035                     {
1036                       if (*decided == loc)
1037                         return NULL;
1038                       else
1039                         rp = rp_backup;
1040                     }
1041                   else
1042                     {
1043                       if (strcmp (fmt, HERE_D_T_FMT))
1044                         *decided = loc;
1045                       want_xday = 1;
1046                       break;
1047                     }
1048                   *decided = raw;
1049                 }
1050               /* The C locale has no era information, so use the
1051                  normal representation.  */
1052               if (!recursive (HERE_D_T_FMT))
1053                 return NULL;
1054               want_xday = 1;
1055               break;
1056             case 'C':
1057               if (*decided != raw)
1058                 {
1059                   if (era_cnt >= 0)
1060                     {
1061                       era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG);
1062                       if (era != NULL && match_string (era->era_name, rp))
1063                         {
1064                           *decided = loc;
1065                           break;
1066                         }
1067                       else
1068                         return NULL;
1069                     }
1070
1071                   num_eras = _NL_CURRENT_WORD (LC_TIME,
1072                                                _NL_TIME_ERA_NUM_ENTRIES);
1073                   for (era_cnt = 0; era_cnt < (int) num_eras;
1074                        ++era_cnt, rp = rp_backup)
1075                     {
1076                       era = _nl_select_era_entry (era_cnt
1077                                                   HELPER_LOCALE_ARG);
1078                       if (era != NULL && match_string (era->era_name, rp))
1079                         {
1080                           *decided = loc;
1081                           break;
1082                         }
1083                     }
1084                   if (era_cnt != (int) num_eras)
1085                     break;
1086
1087                   era_cnt = -1;
1088                   if (*decided == loc)
1089                     return NULL;
1090
1091                   *decided = raw;
1092                 }
1093               /* The C locale has no era information, so use the
1094                  normal representation.  */
1095               goto match_century;
1096             case 'y':
1097               if (*decided != raw)
1098                 {
1099                   get_number(0, 9999, 4);
1100                   tm->tm_year = val;
1101                   want_era = 1;
1102                   want_xday = 1;
1103                   want_century = 1;
1104
1105                   if (era_cnt >= 0)
1106                     {
1107                       assert (*decided == loc);
1108
1109                       era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG);
1110                       int match = FALSE;
1111                       if (era != NULL)
1112                         {
1113                           int delta = ((tm->tm_year - era->offset)
1114                                        * era->absolute_direction);
1115                           match = (delta >= 0
1116                                    && delta < (((int64_t) era->stop_date[0]
1117                                                 - (int64_t) era->start_date[0])
1118                                                * era->absolute_direction));
1119                         }
1120                       if (! match)
1121                         return NULL;
1122
1123                       break;
1124                     }
1125
1126                   num_eras = _NL_CURRENT_WORD (LC_TIME,
1127                                                _NL_TIME_ERA_NUM_ENTRIES);
1128                   for (era_cnt = 0; era_cnt < (int) num_eras; ++era_cnt)
1129                     {
1130                       era = _nl_select_era_entry (era_cnt
1131                                                   HELPER_LOCALE_ARG);
1132                       if (era != NULL)
1133                         {
1134                           int delta = ((tm->tm_year - era->offset)
1135                                        * era->absolute_direction);
1136                           if (delta >= 0
1137                               && delta < (((int64_t) era->stop_date[0]
1138                                            - (int64_t) era->start_date[0])
1139                                           * era->absolute_direction))
1140                             {
1141                               *decided = loc;
1142                               break;
1143                             }
1144                         }
1145                     }
1146                   if (era_cnt != (int) num_eras)
1147                     break;
1148
1149                   era_cnt = -1;
1150                   if (*decided == loc)
1151                     return NULL;
1152
1153                   *decided = raw;
1154                 }
1155
1156               goto match_year_in_century;
1157             case 'Y':
1158               if (*decided != raw)
1159                 {
1160                   num_eras = _NL_CURRENT_WORD (LC_TIME,
1161                                                _NL_TIME_ERA_NUM_ENTRIES);
1162                   for (era_cnt = 0; era_cnt < (int) num_eras;
1163                        ++era_cnt, rp = rp_backup)
1164                     {
1165                       era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG);
1166                       if (era != NULL && recursive (era->era_format))
1167                         break;
1168                     }
1169                   if (era_cnt == (int) num_eras)
1170                     {
1171                       era_cnt = -1;
1172                       if (*decided == loc)
1173                         return NULL;
1174                       else
1175                         rp = rp_backup;
1176                     }
1177                   else
1178                     {
1179                       *decided = loc;
1180                       era_cnt = -1;
1181                       break;
1182                     }
1183
1184                   *decided = raw;
1185                 }
1186               get_number (0, 9999, 4);
1187               tm->tm_year = val - 1900;
1188               want_century = 0;
1189               want_xday = 1;
1190               break;
1191             case 'x':
1192               if (*decided != raw)
1193                 {
1194                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT);
1195
1196                   if (*fmt == '\0')
1197                     fmt = _NL_CURRENT (LC_TIME, D_FMT);
1198
1199                   if (!recursive (fmt))
1200                     {
1201                       if (*decided == loc)
1202                         return NULL;
1203                       else
1204                         rp = rp_backup;
1205                     }
1206                   else
1207                     {
1208                       if (strcmp (fmt, HERE_D_FMT))
1209                         *decided = loc;
1210                       break;
1211                     }
1212                   *decided = raw;
1213                 }
1214               if (!recursive (HERE_D_FMT))
1215                 return NULL;
1216               break;
1217             case 'X':
1218               if (*decided != raw)
1219                 {
1220                   const char *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT);
1221
1222                   if (*fmt == '\0')
1223                     fmt = _NL_CURRENT (LC_TIME, T_FMT);
1224
1225                   if (!recursive (fmt))
1226                     {
1227                       if (*decided == loc)
1228                         return NULL;
1229                       else
1230                         rp = rp_backup;
1231                     }
1232                   else
1233                     {
1234                       if (strcmp (fmt, HERE_T_FMT))
1235                         *decided = loc;
1236                       break;
1237                     }
1238                   *decided = raw;
1239                 }
1240               if (!recursive (HERE_T_FMT))
1241                 return NULL;
1242               break;
1243             default:
1244               return NULL;
1245             }
1246           break;
1247 #else
1248           /* We have no information about the era format.  Just use
1249              the normal format.  */
1250           if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y'
1251               && *fmt != 'x' && *fmt != 'X')
1252             /* This is an illegal format.  */
1253             return NULL;
1254
1255           goto start_over;
1256 #endif
1257         case 'O':
1258           switch (*fmt++)
1259             {
1260             case 'd':
1261             case 'e':
1262               /* Match day of month using alternate numeric symbols.  */
1263               get_alt_number (1, 31, 2);
1264               tm->tm_mday = val;
1265               have_mday = 1;
1266               want_xday = 1;
1267               break;
1268             case 'H':
1269               /* Match hour in 24-hour clock using alternate numeric
1270                  symbols.  */
1271               get_alt_number (0, 23, 2);
1272               tm->tm_hour = val;
1273               have_I = 0;
1274               break;
1275             case 'I':
1276               /* Match hour in 12-hour clock using alternate numeric
1277                  symbols.  */
1278               get_alt_number (1, 12, 2);
1279               tm->tm_hour = val % 12;
1280               have_I = 1;
1281               break;
1282             case 'm':
1283               /* Match month using alternate numeric symbols.  */
1284               get_alt_number (1, 12, 2);
1285               tm->tm_mon = val - 1;
1286               have_mon = 1;
1287               want_xday = 1;
1288               break;
1289             case 'M':
1290               /* Match minutes using alternate numeric symbols.  */
1291               get_alt_number (0, 59, 2);
1292               tm->tm_min = val;
1293               break;
1294             case 'S':
1295               /* Match seconds using alternate numeric symbols.  */
1296               get_alt_number (0, 61, 2);
1297               tm->tm_sec = val;
1298               break;
1299             case 'U':
1300               get_alt_number (0, 53, 2);
1301               week_no = val;
1302               have_uweek = 1;
1303               break;
1304             case 'W':
1305               get_alt_number (0, 53, 2);
1306               week_no = val;
1307               have_wweek = 1;
1308               break;
1309             case 'V':
1310               get_alt_number (0, 53, 2);
1311               /* XXX This cannot determine any field in TM without
1312                  further information.  */
1313               break;
1314             case 'w':
1315               /* Match number of weekday using alternate numeric symbols.  */
1316               get_alt_number (0, 6, 1);
1317               tm->tm_wday = val;
1318               have_wday = 1;
1319               break;
1320             case 'y':
1321               /* Match year within century using alternate numeric symbols.  */
1322               get_alt_number (0, 99, 2);
1323               tm->tm_year = val >= 69 ? val : val + 100;
1324               want_xday = 1;
1325               break;
1326             default:
1327               return NULL;
1328             }
1329           break;
1330         default:
1331           return NULL;
1332         }
1333     }
1334
1335   if (have_I && is_pm)
1336     tm->tm_hour += 12;
1337
1338   if (century != -1)
1339     {
1340       if (want_century)
1341         tm->tm_year = tm->tm_year % 100 + (century - 19) * 100;
1342       else
1343         /* Only the century, but not the year.  Strange, but so be it.  */
1344         tm->tm_year = (century - 19) * 100;
1345     }
1346
1347 #ifdef _NL_CURRENT
1348   if (era_cnt != -1)
1349     {
1350       era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG);
1351       if (era == NULL)
1352         return NULL;
1353       if (want_era)
1354         tm->tm_year = (era->start_date[0]
1355                        + ((tm->tm_year - era->offset)
1356                           * era->absolute_direction));
1357       else
1358         /* Era start year assumed.  */
1359         tm->tm_year = era->start_date[0];
1360     }
1361   else
1362 #endif
1363     if (want_era)
1364       {
1365         /* No era found but we have seen an E modifier.  Rectify some
1366            values.  */
1367         if (want_century && century == -1 && tm->tm_year < 69)
1368           tm->tm_year += 100;
1369       }
1370
1371   if (want_xday && !have_wday)
1372     {
1373       if ( !(have_mon && have_mday) && have_yday)
1374         {
1375           /* We don't have tm_mon and/or tm_mday, compute them.  */
1376           int t_mon = 0;
1377           while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon] <= tm->tm_yday)
1378               t_mon++;
1379           if (!have_mon)
1380               tm->tm_mon = t_mon - 1;
1381           if (!have_mday)
1382               tm->tm_mday =
1383                 (tm->tm_yday
1384                  - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
1385         }
1386       day_of_the_week (tm);
1387     }
1388
1389   if (want_xday && !have_yday)
1390     day_of_the_year (tm);
1391
1392   if ((have_uweek || have_wweek) && have_wday)
1393     {
1394       int save_wday = tm->tm_wday;
1395       int save_mday = tm->tm_mday;
1396       int save_mon = tm->tm_mon;
1397       int w_offset = have_uweek ? 0 : 1;
1398
1399       tm->tm_mday = 1;
1400       tm->tm_mon = 0;
1401       day_of_the_week (tm);
1402       if (have_mday)
1403         tm->tm_mday = save_mday;
1404       if (have_mon)
1405         tm->tm_mon = save_mon;
1406
1407       if (!have_yday)
1408         tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7
1409                        + (week_no - 1) *7
1410                        + save_wday - w_offset);
1411
1412       if (!have_mday || !have_mon)
1413         {
1414           int t_mon = 0;
1415           while (__mon_yday[__isleap(1900 + tm->tm_year)][t_mon]
1416                  <= tm->tm_yday)
1417             t_mon++;
1418           if (!have_mon)
1419             tm->tm_mon = t_mon - 1;
1420           if (!have_mday)
1421               tm->tm_mday =
1422                 (tm->tm_yday
1423                  - __mon_yday[__isleap(1900 + tm->tm_year)][t_mon - 1] + 1);
1424         }
1425
1426       tm->tm_wday = save_wday;
1427     }
1428
1429   return (char *) rp;
1430 }
1431
1432 static char *
1433 strptime (buf, format, tm LOCALE_PARAM)
1434      const char *buf;
1435      const char *format;
1436      struct tm *tm;
1437      LOCALE_PARAM_DECL
1438 {
1439   enum ptime_locale_status decided;
1440
1441 #ifdef _NL_CURRENT
1442   decided = not;
1443 #elif defined (G_OS_WIN32)
1444   decided = not;
1445 #else
1446   decided = raw;
1447 #endif
1448   return __strptime_internal (buf, format, tm, &decided, -1 LOCALE_ARG);
1449 }
1450
1451 #ifdef _LIBC
1452 weak_alias (__strptime_l, strptime_l)
1453 #endif
1454 #endif  /* HAVE_STRPTIME */
1455
1456 /* Returns whether a string is NULL, empty, or full of whitespace */
1457 static gboolean
1458 string_is_empty (const char *value)
1459 {
1460         const char *p;
1461         gboolean empty = TRUE;
1462
1463         if (value) {
1464                 p = value;
1465                 while (*p) {
1466                         if (!isspace (*p)) {
1467                                 empty = FALSE;
1468                                 break;
1469                         }
1470                         p++;
1471                 }
1472         }
1473         return empty;
1474 }
1475
1476
1477 /* Takes a number of format strings for strptime() and attempts to parse a
1478  * string with them.
1479  */
1480 static ETimeParseStatus
1481 parse_with_strptime (const char *value, struct tm *result, const char **formats, int n_formats)
1482 {
1483         const char *parse_end = NULL, *pos;
1484         gchar *locale_str;
1485         gchar *format_str;
1486         ETimeParseStatus parse_ret;
1487         gboolean parsed = FALSE;
1488         int i, n;
1489
1490         if (string_is_empty (value)) {
1491                 memset (result, 0, sizeof (*result));
1492                 result->tm_isdst = -1;
1493                 return E_TIME_PARSE_NONE;
1494         }
1495         
1496         locale_str = g_locale_from_utf8 (value, -1, NULL, NULL, NULL);
1497         pos = (const char *) locale_str;
1498
1499         if (!locale_str)
1500                 return E_TIME_PARSE_INVALID;
1501         
1502         /* Skip whitespace */
1503         while (n = (int)((unsigned char)*pos), isspace (n) != 0)
1504                 pos++;
1505
1506         /* Try each of the formats in turn */
1507
1508         for (i = 0; i < n_formats; i++) {
1509                 memset (result, 0, sizeof (*result));
1510                 format_str = g_locale_from_utf8 (formats[i], -1, NULL, NULL, NULL);
1511                 parse_end = strptime (pos, format_str, result);
1512                 g_free (format_str);
1513                 if (parse_end) {
1514                         parsed = TRUE;
1515                         break;
1516                 }
1517         }
1518
1519         result->tm_isdst = -1;
1520
1521         parse_ret =  E_TIME_PARSE_INVALID;
1522
1523         /* If we parsed something, make sure we parsed the entire string. */
1524         if (parsed) {
1525                 /* Skip whitespace */
1526                 while (isspace (*parse_end))
1527                         parse_end++;
1528
1529                 if (*parse_end == '\0')
1530                         parse_ret = E_TIME_PARSE_OK;
1531         }
1532
1533         g_free (locale_str);
1534
1535         return (parse_ret);
1536
1537 }
1538
1539
1540 /* Returns TRUE if the locale has 'am' and 'pm' strings defined, in which
1541    case the user can choose between 12 and 24-hour time formats. */
1542 static gboolean
1543 locale_supports_12_hour_format (void)
1544 {  
1545         struct tm tmp_tm = { 0 };
1546         char s[16];
1547
1548         e_utf8_strftime (s, sizeof (s), "%p", &tmp_tm);
1549         return s[0] != '\0';
1550 }
1551
1552
1553 /**
1554  * e_time_parse_date_and_time:
1555  * @value: The string to parse a date and time from.
1556  * @result: A #tm to store the result in.
1557  *
1558  * Parses a string @value containing a date and a time and stores the
1559  * result in @result. The date in @value is expected to be in a format
1560  * like "Wed 3/13/00 14:20:00", though gettext() is used to support the
1561  * appropriate local formats. There is also some leniency on the
1562  * format of the string, e.g. the weekday can be skipped or 12-hour
1563  * formats with am/pm can be used.
1564  *
1565  * Returns: E_TIME_PARSE_OK if the string was successfully parsed,
1566  *          E_TIME_PARSE_NONE if the string was empty, or 
1567  *          E_TIME_PARSE_INVALID if the string could not be parsed.
1568  */
1569 ETimeParseStatus
1570 e_time_parse_date_and_time              (const char     *value,
1571                                          struct tm      *result)
1572 {
1573         struct tm *today_tm;
1574         time_t t;
1575         const char *format[16];
1576         int num_formats = 0;
1577         gboolean use_12_hour_formats = locale_supports_12_hour_format ();
1578         ETimeParseStatus status;
1579
1580         if (string_is_empty (value)) {
1581                 memset (result, 0, sizeof (*result));
1582                 result->tm_isdst = -1;
1583                 return E_TIME_PARSE_NONE;
1584         }
1585
1586         /* We'll parse the whole date and time in one go, otherwise we get
1587            into i18n problems. We attempt to parse with several formats,
1588            longest first. Note that we only use the '%p' specifier if the
1589            locale actually has 'am' and 'pm' strings defined, otherwise we
1590            will get incorrect results. Note also that we try to use exactly
1591            the same strings as in e_time_format_date_and_time(), to try to
1592            avoid i18n problems. We also use cut-down versions, so users don't
1593            have to type in the weekday or the seconds, for example.
1594            Note that all these formats include the full date, and the time
1595            will be set to 00:00:00 before parsing, so we don't need to worry
1596            about filling in any missing fields after parsing. */
1597
1598         /*
1599          * Try the full times, with the weekday. Then try without seconds,
1600          * and without minutes, and finally with no time at all.
1601          */
1602         if (use_12_hour_formats) {
1603                 /* strptime format of a weekday, a date and a time,
1604                    in 12-hour format. */
1605                 format[num_formats++] = _("%a %m/%d/%Y %I:%M:%S %p");
1606         }
1607
1608         /* strptime format of a weekday, a date and a time, 
1609            in 24-hour format. */
1610         format[num_formats++] = _("%a %m/%d/%Y %H:%M:%S");
1611
1612         if (use_12_hour_formats) {
1613                 /* strptime format of a weekday, a date and a time,
1614                    in 12-hour format, without seconds. */
1615                 format[num_formats++] = _("%a %m/%d/%Y %I:%M %p");
1616         }
1617
1618         /* strptime format of a weekday, a date and a time,
1619            in 24-hour format, without seconds. */
1620         format[num_formats++] = _("%a %m/%d/%Y %H:%M");
1621
1622         if (use_12_hour_formats) {
1623                 /* strptime format of a weekday, a date and a time,
1624                    in 12-hour format, without minutes or seconds. */
1625                 format[num_formats++] = _("%a %m/%d/%Y %I %p");
1626         }
1627
1628         /* strptime format of a weekday, a date and a time,
1629            in 24-hour format, without minutes or seconds. */
1630         format[num_formats++] = _("%a %m/%d/%Y %H");
1631
1632         /* strptime format of a weekday and a date. */
1633         format[num_formats++] = _("%a %m/%d/%Y");
1634
1635
1636         /*
1637          * Now try all the above formats again, but without the weekday.
1638          */
1639         if (use_12_hour_formats) {
1640                 /* strptime format of a date and a time, in 12-hour format. */
1641                 format[num_formats++] = _("%m/%d/%Y %I:%M:%S %p");
1642         }
1643
1644         /* strptime format of a date and a time, in 24-hour format. */
1645         format[num_formats++] = _("%m/%d/%Y %H:%M:%S");
1646
1647         if (use_12_hour_formats) {
1648                 /* strptime format of a date and a time, in 12-hour format,
1649                    without seconds. */
1650                 format[num_formats++] = _("%m/%d/%Y %I:%M %p");
1651         }
1652
1653         /* strptime format of a date and a time, in 24-hour format,
1654            without seconds. */
1655         format[num_formats++] = _("%m/%d/%Y %H:%M");
1656
1657         if (use_12_hour_formats) {
1658                 /* strptime format of a date and a time, in 12-hour format,
1659                    without minutes or seconds. */
1660                 format[num_formats++] = _("%m/%d/%Y %I %p");
1661         }
1662
1663         /* strptime format of a date and a time, in 24-hour format,
1664            without minutes or seconds. */
1665         format[num_formats++] = _("%m/%d/%Y %H");
1666
1667         /* strptime format of a weekday and a date. */
1668         format[num_formats++] = _("%m/%d/%Y");
1669
1670
1671         status = parse_with_strptime (value, result, format, num_formats);
1672         /* Note that we checked if it was empty already, so it is either OK
1673            or INVALID here. */
1674         if (status == E_TIME_PARSE_OK) {
1675                 /* If a 2-digit year was used we use the current century. */
1676                 if (result->tm_year < 0) {
1677                         t = time (NULL);
1678                         today_tm = localtime (&t);
1679
1680                         /* This should convert it into a value from 0 to 99. */
1681                         result->tm_year += 1900;
1682
1683                         /* Now add on the century. */
1684                         result->tm_year += today_tm->tm_year
1685                                 - (today_tm->tm_year % 100);
1686                 }
1687         } else {
1688                 /* Now we try to just parse a time, assuming the current day.*/
1689                 status = e_time_parse_time (value, result);
1690                 if (status == E_TIME_PARSE_OK) {
1691                         /* We fill in the current day. */
1692                         t = time (NULL);
1693                         today_tm = localtime (&t);
1694                         result->tm_mday = today_tm->tm_mday;
1695                         result->tm_mon  = today_tm->tm_mon;
1696                         result->tm_year = today_tm->tm_year;
1697                 }
1698         }
1699
1700         return status;
1701 }
1702
1703 /**
1704  * e_time_parse_date:
1705  * @value: A date string.
1706  * @result: Return value for the parsed date.
1707  * 
1708  * Takes in a date string entered by the user and tries to convert it to
1709  * a struct #tm.
1710  * 
1711  * Returns: An #ETimeParseStatus result code indicating whether
1712  * @value was an empty string, a valid date, or an invalid date.
1713  **/
1714 ETimeParseStatus
1715 e_time_parse_date (const char *value, struct tm *result)
1716 {
1717         const char *format[3];
1718         struct tm *today_tm;
1719         time_t t;
1720         ETimeParseStatus status;
1721
1722         g_return_val_if_fail (value != NULL, E_TIME_PARSE_INVALID);
1723         g_return_val_if_fail (result != NULL, E_TIME_PARSE_INVALID);
1724
1725         /* according to the current locale */
1726         format [0] = ("%x");
1727         
1728         /* strptime format of a weekday and a date. */
1729         format[1] = _("%a %m/%d/%Y");
1730
1731         /* This is the preferred date format for the locale. */
1732         format[2] = _("%m/%d/%Y");
1733
1734
1735         status = parse_with_strptime (value, result, format, sizeof (format)/sizeof (format [0]));
1736         if (status == E_TIME_PARSE_OK) {
1737                 /* If a 2-digit year was used we use the current century. */
1738                 if (result->tm_year < 0) {
1739                         t = time (NULL);
1740                         today_tm = localtime (&t);
1741                         
1742                         /* This should convert it into a value from 0 to 99. */
1743                         result->tm_year += 1900;
1744                         
1745                         /* Now add on the century. */
1746                         result->tm_year += today_tm->tm_year
1747                                 - (today_tm->tm_year % 100);
1748                 }
1749         }
1750         
1751         return status;
1752 }
1753
1754
1755 /**
1756  * e_time_parse_time:
1757  * @value: The string to parse a time from.
1758  * @result: A #tm to store the result in.
1759  *
1760  * Parses @value, a string containing a time. @value is expected to be
1761  * in a format like "14:20:00". gettext() is used to
1762  * support the appropriate local formats and slightly
1763  * different formats, such as 12-hour formats with am/pm,
1764  * are accepted as well.
1765  *
1766  * Returns: An #ETimeParseStatus result code indicating whether
1767  * @value was an empty string, a valid date, or an invalid date.
1768  **/
1769 ETimeParseStatus
1770 e_time_parse_time (const char *value, struct tm *result)
1771 {
1772         const char *format[6];
1773         int num_formats = 0;
1774         gboolean use_12_hour_formats = locale_supports_12_hour_format ();
1775
1776         if (use_12_hour_formats) {
1777                 /* strptime format for a time of day, in 12-hour format. */
1778                 format[num_formats++] = _("%I:%M:%S %p");
1779         }
1780
1781         /* strptime format for a time of day, in 24-hour format. */
1782         format[num_formats++] = _("%H:%M:%S");
1783
1784         if (use_12_hour_formats) {
1785                 /* strptime format for time of day, without seconds,
1786                    in 12-hour format. */
1787                 format[num_formats++] = _("%I:%M %p");
1788         }
1789
1790         /* strptime format for time of day, without seconds 24-hour format. */
1791         format[num_formats++] = _("%H:%M");
1792
1793         if (use_12_hour_formats) {
1794                 /* strptime format for hour and AM/PM, 12-hour format. */
1795                 format[num_formats++] = _("%I %p");
1796         }
1797
1798         /* strptime format for hour, 24-hour format. */
1799         format[num_formats++] = "%H";
1800
1801         return parse_with_strptime (value, result, format, num_formats);
1802 }
1803
1804
1805 /**
1806  * e_time_format_date_and_time:
1807  * @date_tm: The #tm to convert to a string.
1808  * @use_24_hour_format: A #gboolean.
1809  * @show_midnight: A #gboolean.
1810  * @show_zero_seconds: A #gboolean.
1811  * @buffer: A #char buffer to store the time string in.
1812  * @buffer_size: The length of @buffer.
1813  *
1814  * Creates a string representation of the time value @date_tm and
1815  * stores it in @buffer.  @buffer_size should be at least 64 to be
1816  * safe. If @show_midnight is #FALSE, and the time is midnight, then
1817  * only the date is stored in @buffer. If @show_zero_seconds is
1818  * #FALSE, then if the time has zero seconds only the hour and minute
1819  * of the time are stored in @buffer.
1820  **/
1821 void
1822 e_time_format_date_and_time             (struct tm      *date_tm,
1823                                          gboolean        use_24_hour_format,
1824                                          gboolean        show_midnight,
1825                                          gboolean        show_zero_seconds,
1826                                          char           *buffer,
1827                                          int             buffer_size)
1828 {
1829         char *format;
1830
1831         if (!show_midnight && date_tm->tm_hour == 0
1832             && date_tm->tm_min == 0 && date_tm->tm_sec == 0) {
1833                 /* strftime format of a weekday and a date. */
1834                 format = _("%a %m/%d/%Y");
1835         } else if (use_24_hour_format) {
1836                 if (!show_zero_seconds && date_tm->tm_sec == 0)
1837                         /* strftime format of a weekday, a date and a
1838                            time, in 24-hour format, without seconds. */
1839                         format = _("%a %m/%d/%Y %H:%M");
1840                 else
1841                         /* strftime format of a weekday, a date and a
1842                            time, in 24-hour format. */
1843                         format = _("%a %m/%d/%Y %H:%M:%S");
1844         } else {
1845                 if (!show_zero_seconds && date_tm->tm_sec == 0)
1846                         /* strftime format of a weekday, a date and a
1847                            time, in 12-hour format, without seconds. */
1848                         format = _("%a %m/%d/%Y %I:%M %p");
1849                 else
1850                         /* strftime format of a weekday, a date and a
1851                            time, in 12-hour format. */
1852                         format = _("%a %m/%d/%Y %I:%M:%S %p");
1853         }
1854
1855         /* strftime returns 0 if the string doesn't fit, and leaves the buffer
1856            undefined, so we set it to the empty string in that case. */
1857         if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0)
1858                 buffer[0] = '\0';
1859 }
1860
1861
1862 /**
1863  * e_time_format_time:
1864  * @date_tm: The #tm to convert to a string.
1865  * @use_24_hour_format: A #gboolean.
1866  * @show_zero_seconds: A #gboolean.
1867  * @buffer: The #char buffer to store the result in.
1868  * @buffer_size: The length of @buffer.
1869  *  
1870  * Creates a string representation of a time value in @date_tm and
1871  * stores it in @buffer. @buffer_size should be at least 64.
1872  **/
1873 void
1874 e_time_format_time                      (struct tm      *date_tm,
1875                                          gboolean        use_24_hour_format,
1876                                          gboolean        show_zero_seconds,
1877                                          char           *buffer,
1878                                          int             buffer_size)
1879 {
1880         char *format;
1881
1882         if (use_24_hour_format) {
1883                 if (!show_zero_seconds && date_tm->tm_sec == 0)
1884                         /* strftime format of a time in 24-hour format,
1885                            without seconds. */
1886                         format = _("%H:%M");
1887                 else
1888                         /* strftime format of a time in 24-hour format. */
1889                         format = _("%H:%M:%S");
1890         } else {
1891                 if (!show_zero_seconds && date_tm->tm_sec == 0)
1892                         /* strftime format of a time in 12-hour format,
1893                            without seconds. */
1894                         format = _("%I:%M %p");
1895                 else
1896                         /* strftime format of a time in 12-hour format. */
1897                         format = _("%I:%M:%S %p");
1898         }
1899                         
1900         /* strftime returns 0 if the string doesn't fit, and leaves the buffer
1901            undefined, so we set it to the empty string in that case. */
1902         if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0)
1903                 buffer[0] = '\0';
1904 }
1905
1906
1907 /**
1908  * e_mktime_utc:
1909  * @tm: The #tm to convert to a calendar time representation.
1910  * 
1911  * Like mktime(3), but assumes UTC instead of local timezone.
1912  * 
1913  * Returns: The calendar time representation of @tm.
1914  **/
1915 time_t
1916 e_mktime_utc (struct tm *tm)
1917 {
1918         time_t tt;
1919
1920         tm->tm_isdst = -1;
1921         tt = mktime (tm);
1922
1923 #if defined (HAVE_TM_GMTOFF)
1924         tt += tm->tm_gmtoff;
1925 #elif defined (HAVE_TIMEZONE)
1926         if (tm->tm_isdst > 0) {
1927   #if defined (HAVE_ALTZONE)
1928                 tt -= altzone;
1929   #else /* !defined (HAVE_ALTZONE) */
1930                 tt -= (timezone - 3600);
1931   #endif
1932         } else
1933                 tt -= timezone;
1934 #endif
1935
1936         return tt;
1937 }
1938
1939 /**
1940  * e_localtime_with_offset:
1941  * @tt: The #time_t to convert.
1942  * @tm: The #tm to store the result in.
1943  * @offset: The #int to store the offset in.
1944  *
1945  * Converts the calendar time time representation @tt to a broken-down
1946  * time representation, store in @tm, and provides the offset in
1947  * seconds from UTC time, stored in @offset.
1948  **/
1949 void
1950 e_localtime_with_offset (time_t tt, struct tm *tm, int *offset)
1951 {
1952         localtime_r (&tt, tm);
1953
1954 #if defined (HAVE_TM_GMTOFF)
1955         *offset = tm->tm_gmtoff;
1956 #elif defined (HAVE_TIMEZONE)
1957         if (tm->tm_isdst > 0) {
1958   #if defined (HAVE_ALTZONE)
1959                 *offset = -altzone;
1960   #else /* !defined (HAVE_ALTZONE) */
1961                 *offset = -(timezone - 3600);
1962   #endif
1963         } else
1964                 *offset = -timezone;
1965 #endif
1966 }