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