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