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