/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* Time utility functions * * (C) 2001 Ximian, Inc. * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * * Authors: Damon Chaplin (damon@ximian.com) */ #include #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED 1 /* for strptime */ /* For tm_gmtoff */ #define _BSD_SOURCE 1 #define _DEFAULT_SOURCE 1 #include #include #ifdef HAVE_NL_LANGINFO #include #endif /* HAVE_NL_LANGINFO */ #include #include #include #include "e-time-utils.h" #include "e-data-server-util.h" #ifdef G_OS_WIN32 #ifdef localtime_r #undef localtime_r #endif /* The localtime() in Microsoft's C library is MT-safe */ #define localtime_r(tp,tmp) (localtime(tp)?(*(tmp)=*localtime(tp),(tmp)):0) #include static const gchar * get_locale_string (gint lctype) { gint nbytes = GetLocaleInfo (GetThreadLocale (), lctype, NULL, 0); gchar *tem; GQuark quark; if (nbytes == 0) return "???"; tem = g_malloc (nbytes); if (GetLocaleInfo (GetThreadLocale (), lctype, tem, nbytes) == 0) { g_free (tem); return "???"; } quark = g_quark_from_string (tem); g_free (tem); return g_quark_to_string (quark); } static const gchar * translate_picture (const gchar *picture) { GString *s = g_string_new (""); GQuark quark; while (*picture) { const gchar *q = picture + 1; gint count; while (*picture == *q) q++; count = q - picture; switch (*picture) { case '\'': picture++; while (*picture && *picture != '\'') { g_string_append_c (s, *picture); picture++; } break; case 'd': switch (count) { case 1: case 2: g_string_append (s, "%d"); break; case 3: case 4: g_string_append (s, "%a"); break; } picture += count - 1; break; case 'M': switch (count) { case 1: case 2: g_string_append (s, "%m"); break; case 3: case 4: g_string_append (s, "%b"); break; } picture += count - 1; break; case 'y': switch (count) { case 1: /* Last digit of year. Ugh... */ case 2: g_string_append (s, "%y"); break; case 4: g_string_append (s, "%Y"); break; } picture += count - 1; break; case 'g': /* Era. Huh. Just ignore, as the era stuff * implementation below depends on glibc. */ picture += count - 1; break; case 'h': g_string_append (s, "%I"); picture += count - 1; break; case 'H': g_string_append (s, "%H"); picture += count - 1; break; case 'm': g_string_append (s, "%M"); picture += count - 1; break; case 's': g_string_append (s, "%S"); picture += count - 1; break; case 't': g_string_append (s, "%p"); picture += count - 1; break; default: g_string_append_c (s, *picture); break; } if (*picture) picture++; } quark = g_quark_from_string (s->str); g_string_free (s, TRUE); return g_quark_to_string (quark); } #endif #ifndef HAVE_STRPTIME /* strptime() implementation lifted from glibc */ enum ptime_locale_status { not, loc, raw }; /* Copyright (C) 2002, 2004 Free Software Foundation, Inc. * This file is part of the GNU C Library. * * The GNU C Library is free software; you can redistribute it and / or * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * The GNU C Library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * License along with the GNU C Library; if not, see . * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110 - 1301 USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #ifdef _LIBC # include "../locale/localeinfo.h" #endif #ifndef __P # if defined __GNUC__ || (defined __STDC__ && __STDC__) # define __P(args) args # else # define __P(args) () # endif /* GCC. */ #endif /* Not __P. */ #if !defined HAVE_LOCALTIME_R && !defined localtime_r # ifdef _LIBC # define localtime_r __localtime_r # else /* Approximate localtime_r as best we can in its absence. */ # define localtime_r my_localtime_r static struct tm *localtime_r __P ((const time_t *, struct tm *)); static struct tm * localtime_r (t, tp) const time_t *t; struct tm *tp; { struct tm *l = localtime (t); if (!l) return 0; *tp = *l; return tp; } # endif /* !_LIBC */ #endif /* HAVE_LOCALTIME_R && !defined (localtime_r) */ #define match_char(ch1, ch2) if (ch1 != ch2) return NULL #if defined _LIBC && defined __GNUC__ && __GNUC__ >= 2 # define match_string(cs1, s2) \ ({ gsize len = strlen (cs1); \ gint result = __strncasecmp_l ((cs1), (s2), len, locale) == 0; \ if (result) (s2) += len; \ result; }) #else /* Oh come on. Get a reasonable compiler. */ # define match_string(cs1, s2) \ (g_ascii_strncasecmp ((cs1), (s2), strlen (cs1)) ? 0 : ((s2) += strlen (cs1), 1)) #endif /* We intentionally do not use isdigit() for testing because this will * lead to problems with the wide character version. */ #define get_number(from, to, n) \ do { \ gint __n = n; \ val = 0; \ while (*rp == ' ') \ ++rp; \ if (*rp < '0' || *rp > '9') \ return NULL; \ do { \ val *= 10; \ val += *rp++ - '0'; \ } while (--__n > 0 && val * 10 <= to && *rp >= '0' && *rp <= '9'); \ if (val < from || val > to) \ return NULL; \ } while (0) #ifdef _NL_CURRENT # define get_alt_number(from, to, n) \ ({ \ __label__ do_normal; \ \ if (*decided != raw) \ { \ val = _nl_parse_alt_digit (&rp HELPER_LOCALE_ARG); \ if (val == -1 && *decided != loc) \ { \ *decided = loc; \ goto do_normal; \ } \ if (val < from || val > to) \ return NULL; \ } \ else \ { \ do_normal: \ get_number (from, to, n); \ } \ 0; \ }) #else # define get_alt_number(from, to, n) \ /* We don't have the alternate representation. */ \ get_number (from, to, n) #endif #define recursive(new_fmt) \ (*(new_fmt) != '\0' \ && (rp = __strptime_internal (rp, (new_fmt), tm, \ decided, era_cnt LOCALE_ARG)) != NULL) #ifdef _LIBC /* This is defined in locale/C-time.c in the GNU libc. */ extern const struct locale_data _nl_C_LC_TIME attribute_hidden; # define weekday_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (DAY_1)].string) # define ab_weekday_name \ (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABDAY_1)].string) # define month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (MON_1)].string) # define ab_month_name (&_nl_C_LC_TIME.values[_NL_ITEM_INDEX (ABMON_1)].string) # define HERE_D_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_T_FMT)].string) # define HERE_D_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (D_FMT)].string) # define HERE_AM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (AM_STR)].string) # define HERE_PM_STR (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (PM_STR)].string) # define HERE_T_FMT_AMPM \ (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT_AMPM)].string) # define HERE_T_FMT (_nl_C_LC_TIME.values[_NL_ITEM_INDEX (T_FMT)].string) # define strncasecmp(s1, s2, n) __strncasecmp (s1, s2, n) #else static gchar const weekday_name[][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static gchar const ab_weekday_name[][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static gchar const month_name[][10] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static gchar const ab_month_name[][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; # define HERE_D_T_FMT "%a %b %e %H:%M:%S %Y" # define HERE_D_FMT "%m/%d/%y" # define HERE_AM_STR "AM" # define HERE_PM_STR "PM" # define HERE_T_FMT_AMPM "%I:%M:%S %p" # define HERE_T_FMT "%H:%M:%S" static const gushort __mon_yday[2][13] = { /* Normal years. */ { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, /* Leap years. */ { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } }; #endif #if defined _LIBC /* We use this code also for the extended locale handling where the * function gets as an additional argument the locale which has to be * used. To access the values we have to redefine the _NL_CURRENT * macro. */ # define strptime __strptime_l # undef _NL_CURRENT # define _NL_CURRENT(category, item) \ (current->values[_NL_ITEM_INDEX (item)].string) # undef _NL_CURRENT_WORD # define _NL_CURRENT_WORD(category, item) \ (current->values[_NL_ITEM_INDEX (item)].word) # define LOCALE_PARAM , locale # define LOCALE_ARG , locale # define LOCALE_PARAM_PROTO , __locale_t locale # define LOCALE_PARAM_DECL __locale_t locale; # define HELPER_LOCALE_ARG , current # define ISSPACE(Ch) __isspace_l (Ch, locale) #else # define LOCALE_PARAM # define LOCALE_ARG # define LOCALE_PARAM_DECL # define LOCALE_PARAM_PROTO # define HELPER_LOCALE_ARG # define ISSPACE(Ch) isspace (Ch) #endif #ifndef __isleap /* Nonzero if YEAR is a leap year (every 4 years, * except every 100th isn't, and every 400th is). */ # define __isleap(year) \ ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0)) #endif /* Compute the day of the week. */ static void day_of_the_week (struct tm *tm) { /* We know that January 1st 1970 was a Thursday (= 4). Compute the the difference between this data in the one on TM and so determine the weekday. */ gint corr_year = 1900 + tm->tm_year - (tm->tm_mon < 2); gint wday = (-473 + (365 * (tm->tm_year - 70)) + (corr_year / 4) - ((corr_year / 4) / 25) + ((corr_year / 4) % 25 < 0) + (((corr_year / 4) / 25) / 4) + __mon_yday[0][tm->tm_mon] + tm->tm_mday - 1); tm->tm_wday = ((wday % 7) + 7) % 7; } /* Compute the day of the year. */ static void day_of_the_year (struct tm *tm) { tm->tm_yday = (__mon_yday[__isleap (1900 + tm->tm_year)][tm->tm_mon] + (tm->tm_mday - 1)); } #ifdef _LIBC gchar * internal_function #else static gchar * #endif __strptime_internal (rp, fmt, tm, decided, era_cnt LOCALE_PARAM) const gchar *rp; const gchar *fmt; struct tm *tm; enum ptime_locale_status *decided; gint era_cnt; LOCALE_PARAM_DECL { #ifdef _LIBC struct locale_data *const current = locale->__locales[LC_TIME]; #endif const gchar *rp_backup; gint cnt; gsize val; gint have_I, is_pm; gint century, want_century; gint want_era; gint have_wday, want_xday; gint have_yday; gint have_mon, have_mday; gint have_uweek, have_wweek; gint week_no; #ifdef _NL_CURRENT gsize num_eras; #endif struct era_entry *era; have_I = is_pm = 0; century = -1; want_century = 0; want_era = 0; era = NULL; week_no = 0; have_wday = want_xday = have_yday = have_mon = have_mday = have_uweek = 0; have_wweek = 0; while (*fmt != '\0') { /* A white space in the format string matches 0 more or white space in the input string. */ if (ISSPACE (*fmt)) { while (ISSPACE (*rp)) ++rp; ++fmt; continue; } /* Any character but `%' must be matched by the same character in the iput string. */ if (*fmt != '%') { match_char (*fmt++, *rp++); continue; } ++fmt; #ifndef _NL_CURRENT /* We need this for handling the `E' modifier. */ start_over: #endif /* Make back up of current processing pointer. */ rp_backup = rp; switch (*fmt++) { case '%': /* Match the `%' character itself. */ match_char ('%', *rp++); break; case 'a': case 'A': /* Match day of week. */ for (cnt = 0; cnt < 7; ++cnt) { #ifdef _NL_CURRENT if (*decided !=raw) { if (match_string (_NL_CURRENT (LC_TIME, DAY_1 + cnt), rp)) { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, DAY_1 + cnt), weekday_name[cnt])) *decided = loc; break; } if (match_string (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), rp)) { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, ABDAY_1 + cnt), ab_weekday_name[cnt])) *decided = loc; break; } } #elif defined (G_OS_WIN32) if (*decided !=raw) { const gchar *locale_str; locale_str = get_locale_string (LOCALE_SDAYNAME1 + cnt); if (match_string (locale_str, rp)) { if (*decided == not && strcmp (locale_str, weekday_name[cnt])) *decided = loc; break; } locale_str = get_locale_string (LOCALE_SABBREVDAYNAME1 + cnt); if (match_string (locale_str, rp)) { if (*decided == not && strcmp (locale_str, ab_weekday_name[cnt])) *decided = loc; break; } } #endif if (*decided != loc && (match_string (weekday_name[cnt], rp) || match_string (ab_weekday_name[cnt], rp))) { *decided = raw; break; } } if (cnt == 7) /* Does not match a weekday name. */ return NULL; tm->tm_wday = cnt; have_wday = 1; break; case 'b': case 'B': case 'h': /* Match month name. */ for (cnt = 0; cnt < 12; ++cnt) { #ifdef _NL_CURRENT if (*decided !=raw) { if (match_string (_NL_CURRENT (LC_TIME, MON_1 + cnt), rp)) { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, MON_1 + cnt), month_name[cnt])) *decided = loc; break; } if (match_string (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), rp)) { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, ABMON_1 + cnt), ab_month_name[cnt])) *decided = loc; break; } } #elif defined (G_OS_WIN32) if (*decided !=raw) { const gchar *locale_str; locale_str = get_locale_string (LOCALE_SMONTHNAME1 + cnt); if (match_string (locale_str, rp)) { if (*decided == not && strcmp (locale_str, month_name[cnt])) *decided = loc; break; } locale_str = get_locale_string (LOCALE_SABBREVMONTHNAME1 + cnt); if (match_string (locale_str, rp)) { if (*decided == not && strcmp (locale_str, ab_month_name[cnt])) *decided = loc; break; } } #endif if (match_string (month_name[cnt], rp) || match_string (ab_month_name[cnt], rp)) { *decided = raw; break; } } if (cnt == 12) /* Does not match a month name. */ return NULL; tm->tm_mon = cnt; want_xday = 1; break; case 'c': /* Match locale's date and time format. */ #ifdef _NL_CURRENT if (*decided != raw) { if (!recursive (_NL_CURRENT (LC_TIME, D_T_FMT))) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, D_T_FMT), HERE_D_T_FMT)) *decided = loc; want_xday = 1; break; } *decided = raw; } #elif defined (G_OS_WIN32) if (*decided != raw) { gchar *d_t_fmt = g_strconcat (get_locale_string (LOCALE_SSHORTDATE), " ", get_locale_string (LOCALE_STIMEFORMAT), NULL); const gchar *posix_d_t_fmt = translate_picture (d_t_fmt); g_free (d_t_fmt); if (!recursive (posix_d_t_fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (posix_d_t_fmt, HERE_D_T_FMT)) *decided = loc; want_xday = 1; break; } *decided = raw; } #endif if (!recursive (HERE_D_T_FMT)) return NULL; want_xday = 1; break; case 'C': /* Match century number. */ #ifdef _NL_CURRENT match_century: #endif get_number (0, 99, 2); century = val; want_xday = 1; break; case 'd': case 'e': /* Match day of month. */ get_number (1, 31, 2); tm->tm_mday = val; have_mday = 1; want_xday = 1; break; case 'F': if (!recursive ("%Y-%m-%d")) return NULL; want_xday = 1; break; case 'x': #ifdef _NL_CURRENT if (*decided != raw) { if (!recursive (_NL_CURRENT (LC_TIME, D_FMT))) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, D_FMT), HERE_D_FMT)) *decided = loc; want_xday = 1; break; } *decided = raw; } #elif defined (G_OS_WIN32) if (*decided != raw) { const gchar *picture; const gchar *posix_d_fmt; picture = get_locale_string (LOCALE_SSHORTDATE); posix_d_fmt = translate_picture (picture); if (!recursive (posix_d_fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (posix_d_fmt, HERE_D_FMT)) *decided = loc; want_xday = 1; break; } *decided = raw; } #endif /* Fall through. */ case 'D': /* Match standard day format. */ if (!recursive (HERE_D_FMT)) return NULL; want_xday = 1; break; case 'k': case 'H': /* Match hour in 24-hour clock. */ get_number (0, 23, 2); tm->tm_hour = val; have_I = 0; break; case 'l': /* Match hour in 12-hour clock. GNU extension. */ case 'I': /* Match hour in 12-hour clock. */ get_number (1, 12, 2); tm->tm_hour = val % 12; have_I = 1; break; case 'j': /* Match day number of year. */ get_number (1, 366, 3); tm->tm_yday = val - 1; have_yday = 1; break; case 'm': /* Match number of month. */ get_number (1, 12, 2); tm->tm_mon = val - 1; have_mon = 1; want_xday = 1; break; case 'M': /* Match minute. */ get_number (0, 59, 2); tm->tm_min = val; break; case 'n': case 't': /* Match any white space. */ while (ISSPACE (*rp)) ++rp; break; case 'p': /* Match locale's equivalent of AM/PM. */ #ifdef _NL_CURRENT if (*decided != raw) { if (match_string (_NL_CURRENT (LC_TIME, AM_STR), rp)) { if (strcmp (_NL_CURRENT (LC_TIME, AM_STR), HERE_AM_STR)) *decided = loc; break; } if (match_string (_NL_CURRENT (LC_TIME, PM_STR), rp)) { if (strcmp (_NL_CURRENT (LC_TIME, PM_STR), HERE_PM_STR)) *decided = loc; is_pm = 1; break; } *decided = raw; } #elif defined (G_OS_WIN32) if (*decided != raw) { if (match_string (get_locale_string (LOCALE_S1159), rp)) { if (strcmp (get_locale_string (LOCALE_S1159), HERE_AM_STR)) *decided = loc; break; } if (match_string (get_locale_string (LOCALE_S2359), rp)) { if (strcmp (get_locale_string (LOCALE_S2359), HERE_PM_STR)) *decided = loc; is_pm = 1; break; } *decided = raw; } #endif if (!match_string (HERE_AM_STR, rp)) { if (match_string (HERE_PM_STR, rp)) is_pm = 1; else return NULL; } break; case 'r': #ifdef _NL_CURRENT if (*decided != raw) { if (!recursive (_NL_CURRENT (LC_TIME, T_FMT_AMPM))) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (_NL_CURRENT (LC_TIME, T_FMT_AMPM), HERE_T_FMT_AMPM)) *decided = loc; break; } *decided = raw; } #elif defined (G_OS_WIN32) if (*decided != raw) { gchar *t_p_fmt = g_strconcat (get_locale_string (LOCALE_STIMEFORMAT), " tt", NULL); const gchar *posix_t_p_fmt = translate_picture (t_p_fmt); g_free (t_p_fmt); if (!recursive (posix_t_p_fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (*decided == not && strcmp (posix_t_p_fmt, HERE_T_FMT_AMPM)) *decided = loc; break; } *decided = raw; } #endif if (!recursive (HERE_T_FMT_AMPM)) return NULL; break; case 'R': if (!recursive ("%H:%M")) return NULL; break; case 's': { /* The number of seconds may be very high so we cannot use the `get_number' macro. Instead read the number character for character and construct the result while doing this. */ time_t secs = 0; if (*rp < '0' || *rp > '9') /* We need at least one digit. */ return NULL; do { secs *= 10; secs += *rp++ - '0'; } while (*rp >= '0' && *rp <= '9'); if (localtime_r (&secs, tm) == NULL) /* Error in function. */ return NULL; } break; case 'S': get_number (0, 61, 2); tm->tm_sec = val; break; case 'X': #ifdef _NL_CURRENT if (*decided != raw) { if (!recursive (_NL_CURRENT (LC_TIME, T_FMT))) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (strcmp (_NL_CURRENT (LC_TIME, T_FMT), HERE_T_FMT)) *decided = loc; break; } *decided = raw; } #elif defined (G_OS_WIN32) if (*decided != raw) { const gchar *picture; const gchar *posix_t_fmt; picture = get_locale_string (LOCALE_STIMEFORMAT); posix_t_fmt = translate_picture (picture); if (!recursive (posix_t_fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (strcmp (posix_t_fmt, HERE_T_FMT)) *decided = loc; break; } *decided = raw; } #endif /* Fall through. */ case 'T': if (!recursive (HERE_T_FMT)) return NULL; break; case 'u': get_number (1, 7, 1); tm->tm_wday = val % 7; have_wday = 1; break; case 'g': get_number (0, 99, 2); /* XXX This cannot determine any field in TM. */ break; case 'G': if (*rp < '0' || *rp > '9') return NULL; /* XXX Ignore the number since we would need some more information to compute a real date. */ do ++rp; while (*rp >= '0' && *rp <= '9'); break; case 'U': get_number (0, 53, 2); week_no = val; have_uweek = 1; break; case 'W': get_number (0, 53, 2); week_no = val; have_wweek = 1; break; case 'V': get_number (0, 53, 2); /* XXX This cannot determine any field in TM without some information. */ break; case 'w': /* Match number of weekday. */ get_number (0, 6, 1); tm->tm_wday = val; have_wday = 1; break; case 'y': #ifdef _NL_CURRENT match_year_in_century: #endif /* Match year within century. */ get_number (0, 99, 2); /* The "Year 2000: The Millennium Rollover" paper suggests that * values in the range 69-99 refer to the twentieth century. */ tm->tm_year = val >= 69 ? val : val + 100; /* Indicate that we want to use the century, if specified. */ want_century = 1; want_xday = 1; break; case 'Y': /* Match year including century number. */ get_number (0, 9999, 4); tm->tm_year = val - 1900; want_century = 0; want_xday = 1; break; case 'Z': /* XXX How to handle this? */ break; case 'E': #ifdef _NL_CURRENT switch (*fmt++) { case 'c': /* Match locale's alternate date and time format. */ if (*decided != raw) { const gchar *fmt = _NL_CURRENT (LC_TIME, ERA_D_T_FMT); if (*fmt == '\0') fmt = _NL_CURRENT (LC_TIME, D_T_FMT); if (!recursive (fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (strcmp (fmt, HERE_D_T_FMT)) *decided = loc; want_xday = 1; break; } *decided = raw; } /* The C locale has no era information, so use the normal representation. */ if (!recursive (HERE_D_T_FMT)) return NULL; want_xday = 1; break; case 'C': if (*decided != raw) { if (era_cnt >= 0) { era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); if (era != NULL && match_string (era->era_name, rp)) { *decided = loc; break; } else return NULL; } num_eras = _NL_CURRENT_WORD (LC_TIME, _NL_TIME_ERA_NUM_ENTRIES); for (era_cnt = 0; era_cnt < (gint) num_eras; ++era_cnt, rp = rp_backup) { era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); if (era != NULL && match_string (era->era_name, rp)) { *decided = loc; break; } } if (era_cnt != (gint) num_eras) break; era_cnt = -1; if (*decided == loc) return NULL; *decided = raw; } /* The C locale has no era information, so use the normal representation. */ goto match_century; case 'y': if (*decided != raw) { get_number (0, 9999, 4); tm->tm_year = val; want_era = 1; want_xday = 1; want_century = 1; if (era_cnt >= 0) { assert (*decided == loc); era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); gint match = FALSE; if (era != NULL) { gint delta = ((tm->tm_year - era->offset) * era->absolute_direction); match = (delta >= 0 && delta < (((int64_t) era->stop_date[0] - (int64_t) era->start_date[0]) * era->absolute_direction)); } if (!match) return NULL; break; } num_eras = _NL_CURRENT_WORD (LC_TIME, _NL_TIME_ERA_NUM_ENTRIES); for (era_cnt = 0; era_cnt < (gint) num_eras; ++era_cnt) { era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); if (era != NULL) { gint delta = ((tm->tm_year - era->offset) * era->absolute_direction); if (delta >= 0 && delta < (((int64_t) era->stop_date[0] - (int64_t) era->start_date[0]) * era->absolute_direction)) { *decided = loc; break; } } } if (era_cnt != (gint) num_eras) break; era_cnt = -1; if (*decided == loc) return NULL; *decided = raw; } goto match_year_in_century; case 'Y': if (*decided != raw) { num_eras = _NL_CURRENT_WORD ( LC_TIME, _NL_TIME_ERA_NUM_ENTRIES); for (era_cnt = 0; era_cnt < (gint) num_eras; ++era_cnt, rp = rp_backup) { era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); if (era != NULL && recursive (era->era_format)) break; } if (era_cnt == (gint) num_eras) { era_cnt = -1; if (*decided == loc) return NULL; else rp = rp_backup; } else { *decided = loc; era_cnt = -1; break; } *decided = raw; } get_number (0, 9999, 4); tm->tm_year = val - 1900; want_century = 0; want_xday = 1; break; case 'x': if (*decided != raw) { const gchar *fmt = _NL_CURRENT (LC_TIME, ERA_D_FMT); if (*fmt == '\0') fmt = _NL_CURRENT (LC_TIME, D_FMT); if (!recursive (fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (strcmp (fmt, HERE_D_FMT)) *decided = loc; break; } *decided = raw; } if (!recursive (HERE_D_FMT)) return NULL; break; case 'X': if (*decided != raw) { const gchar *fmt = _NL_CURRENT (LC_TIME, ERA_T_FMT); if (*fmt == '\0') fmt = _NL_CURRENT (LC_TIME, T_FMT); if (!recursive (fmt)) { if (*decided == loc) return NULL; else rp = rp_backup; } else { if (strcmp (fmt, HERE_T_FMT)) *decided = loc; break; } *decided = raw; } if (!recursive (HERE_T_FMT)) return NULL; break; default: return NULL; } break; #else /* We have no information about the era format. Just use the normal format. */ if (*fmt != 'c' && *fmt != 'C' && *fmt != 'y' && *fmt != 'Y' && *fmt != 'x' && *fmt != 'X') /* This is an illegal format. */ return NULL; goto start_over; #endif case 'O': switch (*fmt++) { case 'd': case 'e': /* Match day of month using alternate numeric symbols. */ get_alt_number (1, 31, 2); tm->tm_mday = val; have_mday = 1; want_xday = 1; break; case 'H': /* Match hour in 24-hour clock using alternate numeric symbols. */ get_alt_number (0, 23, 2); tm->tm_hour = val; have_I = 0; break; case 'I': /* Match hour in 12-hour clock using alternate numeric symbols. */ get_alt_number (1, 12, 2); tm->tm_hour = val % 12; have_I = 1; break; case 'm': /* Match month using alternate numeric symbols. */ get_alt_number (1, 12, 2); tm->tm_mon = val - 1; have_mon = 1; want_xday = 1; break; case 'M': /* Match minutes using alternate numeric symbols. */ get_alt_number (0, 59, 2); tm->tm_min = val; break; case 'S': /* Match seconds using alternate numeric symbols. */ get_alt_number (0, 61, 2); tm->tm_sec = val; break; case 'U': get_alt_number (0, 53, 2); week_no = val; have_uweek = 1; break; case 'W': get_alt_number (0, 53, 2); week_no = val; have_wweek = 1; break; case 'V': get_alt_number (0, 53, 2); /* XXX This cannot determine any field in TM without further information. */ break; case 'w': /* Match number of weekday using alternate numeric symbols. */ get_alt_number (0, 6, 1); tm->tm_wday = val; have_wday = 1; break; case 'y': /* Match year within century using alternate numeric symbols. */ get_alt_number (0, 99, 2); tm->tm_year = val >= 69 ? val : val + 100; want_xday = 1; break; default: return NULL; } break; default: return NULL; } } if (have_I && is_pm) tm->tm_hour += 12; if (century != -1) { if (want_century) tm->tm_year = tm->tm_year % 100 + (century - 19) * 100; else /* Only the century, but not the year. Strange, but so be it. */ tm->tm_year = (century - 19) * 100; } #ifdef _NL_CURRENT if (era_cnt != -1) { era = _nl_select_era_entry (era_cnt HELPER_LOCALE_ARG); if (era == NULL) return NULL; if (want_era) tm->tm_year = (era->start_date[0] + ((tm->tm_year - era->offset) * era->absolute_direction)); else /* Era start year assumed. */ tm->tm_year = era->start_date[0]; } else #endif if (want_era) { /* No era found but we have seen an E modifier. Rectify some * values. */ if (want_century && century == -1 && tm->tm_year < 69) tm->tm_year += 100; } if (want_xday && !have_wday) { if ( !(have_mon && have_mday) && have_yday) { /* We don't have tm_mon and/or tm_mday, compute them. */ gint t_mon = 0; while (__mon_yday[__isleap (1900 + tm->tm_year)][t_mon] <= tm->tm_yday) t_mon++; if (!have_mon) tm->tm_mon = t_mon - 1; if (!have_mday) tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap (1900 + tm->tm_year)][t_mon - 1] + 1); } day_of_the_week (tm); } if (want_xday && !have_yday) day_of_the_year (tm); if ((have_uweek || have_wweek) && have_wday) { gint save_wday = tm->tm_wday; gint save_mday = tm->tm_mday; gint save_mon = tm->tm_mon; gint w_offset = have_uweek ? 0 : 1; tm->tm_mday = 1; tm->tm_mon = 0; day_of_the_week (tm); if (have_mday) tm->tm_mday = save_mday; if (have_mon) tm->tm_mon = save_mon; if (!have_yday) tm->tm_yday = ((7 - (tm->tm_wday - w_offset)) % 7 + (week_no - 1) *7 + save_wday - w_offset); if (!have_mday || !have_mon) { gint t_mon = 0; while (__mon_yday[__isleap (1900 + tm->tm_year)][t_mon] <= tm->tm_yday) t_mon++; if (!have_mon) tm->tm_mon = t_mon - 1; if (!have_mday) tm->tm_mday = (tm->tm_yday - __mon_yday[__isleap (1900 + tm->tm_year)][t_mon - 1] + 1); } tm->tm_wday = save_wday; } return (gchar *) rp; } static gchar * strptime (buf, format, tm LOCALE_PARAM) const gchar *buf; const gchar *format; struct tm *tm; LOCALE_PARAM_DECL { enum ptime_locale_status decided; #ifdef _NL_CURRENT decided = not; #elif defined (G_OS_WIN32) decided = not; #else decided = raw; #endif return __strptime_internal (buf, format, tm, &decided, -1 LOCALE_ARG); } #ifdef _LIBC weak_alias (__strptime_l, strptime_l) #endif #endif /* HAVE_STRPTIME */ /* Returns whether a string is NULL, empty, or full of whitespace */ static gboolean string_is_empty (const gchar *value) { const gchar *p; gboolean empty = TRUE; if (value) { p = value; while (*p) { if (!isspace (*p)) { empty = FALSE; break; } p++; } } return empty; } /* Takes a number of format strings for strptime() and attempts to parse a * string with them. */ static ETimeParseStatus parse_with_strptime (const gchar *value, struct tm *result, const gchar **formats, gint n_formats) { const gchar *parse_end = NULL, *pos; gchar *locale_str; gchar *format_str; ETimeParseStatus parse_ret; gint i, n; if (string_is_empty (value)) { memset (result, 0, sizeof (*result)); result->tm_isdst = -1; return E_TIME_PARSE_NONE; } parse_ret = E_TIME_PARSE_INVALID; locale_str = g_locale_from_utf8 (value, -1, NULL, NULL, NULL); pos = (const gchar *) locale_str; if (!locale_str) return E_TIME_PARSE_INVALID; /* Skip whitespace */ while (n = (gint)((guchar) * pos), isspace (n) != 0) pos++; /* Try each of the formats in turn */ for (i = 0; i < n_formats; i++) { memset (result, 0, sizeof (*result)); format_str = g_locale_from_utf8 (formats[i], -1, NULL, NULL, NULL); parse_end = strptime (pos, format_str, result); g_free (format_str); if (parse_end) { /* If we parsed something, make sure we parsed the entire string. */ /* Skip whitespace */ while (isspace (*parse_end)) parse_end++; if (*parse_end == '\0') { parse_ret = E_TIME_PARSE_OK; break; } } } result->tm_isdst = -1; g_free (locale_str); return (parse_ret); } static void correct_two_digit_year (struct tm *result, gboolean *two_digit_year) { g_return_if_fail (result != NULL); if (two_digit_year) *two_digit_year = FALSE; /* If a 2-digit year was used we use the current century. */ if (result->tm_year < 0 && result->tm_year < -1800) { time_t t = time (NULL); struct tm *today_tm = localtime (&t); /* This should convert it into a value from 0 to 99. */ result->tm_year += 1900; /* Now add on the century. */ result->tm_year += today_tm->tm_year - (today_tm->tm_year % 100); if (two_digit_year) *two_digit_year = TRUE; } } /* Returns TRUE if the locale has 'am' and 'pm' strings defined, in which * case the user can choose between 12 and 24-hour time formats. */ static gboolean locale_supports_12_hour_format (void) { struct tm tmp_tm = { 0 }; gchar s[40]; /* Fill the struct tm with some sane values. */ tmp_tm.tm_year = 2000; tmp_tm.tm_mon = 0; tmp_tm.tm_mday = 1; tmp_tm.tm_sec = 0; tmp_tm.tm_isdst = 0; tmp_tm.tm_hour = 1; tmp_tm.tm_min = 0; tmp_tm.tm_wday = 6; tmp_tm.tm_yday = 6; e_utf8_strftime (s, sizeof (s), "%p", &tmp_tm); if (!s[0]) { tmp_tm.tm_hour = 13; tmp_tm.tm_min = 0; e_utf8_strftime (s, sizeof (s), "%p", &tmp_tm); } return s[0] != '\0'; } static gboolean has_correct_date (const struct tm *value) { const gint days_in_month[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; gint days, year; g_return_val_if_fail (value != NULL, FALSE); g_return_val_if_fail (value->tm_mon >= 0 && value->tm_mon < 12, FALSE); year = value->tm_year + 1900; days = days_in_month[value->tm_mon]; if (value->tm_mon == 1 && ((year <= 1752) ? (!(year % 4)) : ((!(year % 4) && (year % 100)) || !(year % 400)))) days++; return value->tm_mday >= 1 && value->tm_mday <= days; } /** * e_time_parse_date_and_time_ex: * @value: The string to parse a date and time from. * @result: A #tm to store the result in. * @two_digit_year: set to TRUE, is parsing with two-digit year, else FALSE, * but only when not NULL. * * Parses a string @value containing a date and a time and stores the * result in @result. The date in @value is expected to be in a format * like "Wed 3/13/00 14:20:00", though gettext() is used to support the * appropriate local formats. There is also some leniency on the * format of the string, e.g. the weekday can be skipped or 12-hour * formats with am/pm can be used. * * Returns: E_TIME_PARSE_OK if the string was successfully parsed, * E_TIME_PARSE_NONE if the string was empty, or * E_TIME_PARSE_INVALID if the string could not be parsed. * * Since: 2.22 */ ETimeParseStatus e_time_parse_date_and_time_ex (const gchar *value, struct tm *result, gboolean *two_digit_year) { struct tm *today_tm; time_t t; const gchar *format[16]; gint num_formats = 0; gboolean use_12_hour_formats = locale_supports_12_hour_format (); ETimeParseStatus status; if (string_is_empty (value)) { memset (result, 0, sizeof (*result)); result->tm_isdst = -1; return E_TIME_PARSE_NONE; } /* We'll parse the whole date and time in one go, otherwise we get * into i18n problems. We attempt to parse with several formats, * longest first. Note that we only use the '%p' specifier if the * locale actually has 'am' and 'pm' strings defined, otherwise we * will get incorrect results. Note also that we try to use exactly * the same strings as in e_time_format_date_and_time (), to try to * avoid i18n problems. We also use cut-down versions, so users don't * have to type in the weekday or the seconds, for example. * Note that all these formats include the full date, and the time * will be set to 00:00:00 before parsing, so we don't need to worry * about filling in any missing fields after parsing. */ /* * Try the full times, with the weekday. Then try without seconds, * and without minutes, and finally with no time at all. */ if (use_12_hour_formats) { /* strptime format of a weekday, a date and a time, * in 12-hour format. */ format[num_formats++] = _("%a %m/%d/%Y %I:%M:%S %p"); } /* strptime format of a weekday, a date and a time, * in 24-hour format. */ format[num_formats++] = _("%a %m/%d/%Y %H:%M:%S"); if (use_12_hour_formats) { /* strptime format of a weekday, a date and a time, * in 12-hour format, without seconds. */ format[num_formats++] = _("%a %m/%d/%Y %I:%M %p"); } /* strptime format of a weekday, a date and a time, * in 24-hour format, without seconds. */ format[num_formats++] = _("%a %m/%d/%Y %H:%M"); if (use_12_hour_formats) { /* strptime format of a weekday, a date and a time, * in 12-hour format, without minutes or seconds. */ format[num_formats++] = _("%a %m/%d/%Y %I %p"); } /* strptime format of a weekday, a date and a time, * in 24-hour format, without minutes or seconds. */ format[num_formats++] = _("%a %m/%d/%Y %H"); /* strptime format of a weekday and a date. */ format[num_formats++] = _("%a %m/%d/%Y"); /* * Now try all the above formats again, but without the weekday. */ if (use_12_hour_formats) { /* strptime format of a date and a time, in 12-hour format. */ format[num_formats++] = _("%m/%d/%Y %I:%M:%S %p"); } /* strptime format of a date and a time, in 24-hour format. */ format[num_formats++] = _("%m/%d/%Y %H:%M:%S"); if (use_12_hour_formats) { /* strptime format of a date and a time, in 12-hour format, * without seconds. */ format[num_formats++] = _("%m/%d/%Y %I:%M %p"); } /* strptime format of a date and a time, in 24-hour format, * without seconds. */ format[num_formats++] = _("%m/%d/%Y %H:%M"); if (use_12_hour_formats) { /* strptime format of a date and a time, in 12-hour format, * without minutes or seconds. */ format[num_formats++] = _("%m/%d/%Y %I %p"); } /* strptime format of a date and a time, in 24-hour format, * without minutes or seconds. */ format[num_formats++] = _("%m/%d/%Y %H"); /* strptime format of a weekday and a date. */ format[num_formats++] = _("%m/%d/%Y"); if (two_digit_year) *two_digit_year = FALSE; status = parse_with_strptime (value, result, format, num_formats); if (status == E_TIME_PARSE_OK && !has_correct_date (result)) status = E_TIME_PARSE_INVALID; /* Note that we checked if it was empty already, so it is either OK * or INVALID here. */ if (status == E_TIME_PARSE_OK) { correct_two_digit_year (result, two_digit_year); } else { /* Now we try to just parse a time, assuming the current day.*/ status = e_time_parse_time (value, result); if (status == E_TIME_PARSE_OK) { /* We fill in the current day. */ t = time (NULL); today_tm = localtime (&t); result->tm_mday = today_tm->tm_mday; result->tm_mon = today_tm->tm_mon; result->tm_year = today_tm->tm_year; } } return status; } /** * e_time_parse_date_and_time: * @value: the string to parse a date and time from * @result: a #tm to store the result in * * Parses a string @value containing a date and a time and stores the * result in @result. The date in @value is expected to be in a format * like "Wed 3/13/00 14:20:00", though gettext() is used to support the * appropriate local formats. There is also some leniency on the * format of the string, e.g. the weekday can be skipped or 12-hour * formats with am/pm can be used. * * Returns: E_TIME_PARSE_OK if the string was successfully parsed, * E_TIME_PARSE_NONE if the string was empty, or * E_TIME_PARSE_INVALID if the string could not be parsed. */ ETimeParseStatus e_time_parse_date_and_time (const gchar *value, struct tm *result) { return e_time_parse_date_and_time_ex (value, result, NULL); } /** * e_time_parse_date_ex: * @value: A date string. * @result: Return value for the parsed date. * @two_digit_year: set to TRUE, is parsing with two-digit year, else FALSE, * but only when not NULL. * * Takes in a date string entered by the user and tries to convert it to * a struct #tm. * * Returns: An #ETimeParseStatus result code indicating whether * @value was an empty string, a valid date, or an invalid date. * * Since: 2.22 **/ ETimeParseStatus e_time_parse_date_ex (const gchar *value, struct tm *result, gboolean *two_digit_year) { const gchar *format[4]; ETimeParseStatus status; g_return_val_if_fail (value != NULL, E_TIME_PARSE_INVALID); g_return_val_if_fail (result != NULL, E_TIME_PARSE_INVALID); /* according to the current locale */ format[0] = ("%x"); /* according to the current locale with forced 4-digit year*/ format[1] = e_time_get_d_fmt_with_4digit_year (); /* strptime format of a weekday and a date. */ format[2] = _("%a %m/%d/%Y"); /* This is the preferred date format for the locale. */ format[3] = _("%m/%d/%Y"); if (two_digit_year) { /* when we need to know about two digit year, then always first try * full year, because for example nl_NL have format %d-%m-%y and it * changes from two year itself, which isn't what we want */ const gchar *tmp = format[1]; format[1] = format[0]; format[0] = tmp; *two_digit_year = FALSE; } status = parse_with_strptime (value, result, format, G_N_ELEMENTS (format)); if (status == E_TIME_PARSE_OK && !has_correct_date (result)) status = E_TIME_PARSE_INVALID; if (status == E_TIME_PARSE_OK) { correct_two_digit_year (result, two_digit_year); } if (two_digit_year) g_free ((gchar *) format[0]); else g_free ((gchar *) format[1]); return status; } /** * e_time_parse_date: * @value: A date string. * @result: Return value for the parsed date. * * Takes in a date string entered by the user and tries to convert it to * a struct #tm. * * Returns: An #ETimeParseStatus result code indicating whether * @value was an empty string, a valid date, or an invalid date. **/ ETimeParseStatus e_time_parse_date (const gchar *value, struct tm *result) { return e_time_parse_date_ex (value, result, NULL); } /** * e_time_parse_time: * @value: The string to parse a time from. * @result: A #tm to store the result in. * * Parses @value, a string containing a time. @value is expected to be * in a format like "14:20:00". gettext() is used to * support the appropriate local formats and slightly * different formats, such as 12-hour formats with am/pm, * are accepted as well. * * Returns: An #ETimeParseStatus result code indicating whether * @value was an empty string, a valid date, or an invalid date. **/ ETimeParseStatus e_time_parse_time (const gchar *value, struct tm *result) { const gchar *format[7]; gint num_formats = 0; gboolean use_12_hour_formats = locale_supports_12_hour_format (); if (use_12_hour_formats) { /* strptime format for a time of day, in 12-hour format. */ format[num_formats++] = _("%I:%M:%S %p"); } /* strptime format for a time of day, in 24-hour format. */ format[num_formats++] = _("%H:%M:%S"); if (use_12_hour_formats) { /* strptime format for time of day, without seconds, * in 12-hour format. */ format[num_formats++] = _("%I:%M %p"); } /* strptime format for time of day, without seconds 24-hour format. */ format[num_formats++] = _("%H:%M"); /* strptime format for time of day, without seconds 24-hour format, * and no colon. */ format[num_formats++] = _("%H%M"); if (use_12_hour_formats) { /* strptime format for hour and AM/PM, 12-hour format. */ format[num_formats++] = _("%I %p"); } /* strptime format for hour, 24-hour format. */ format[num_formats++] = "%H"; return parse_with_strptime (value, result, format, num_formats); } /** * e_time_format_date_and_time: * @date_tm: The #tm to convert to a string. * @use_24_hour_format: A #gboolean. * @show_midnight: A #gboolean. * @show_zero_seconds: A #gboolean. * @buffer: A #char buffer to store the time string in. * @buffer_size: The length of @buffer. * * Creates a string representation of the time value @date_tm and * stores it in @buffer. @buffer_size should be at least 64 to be * safe. If @show_midnight is %FALSE, and the time is midnight, then * only the date is stored in @buffer. If @show_zero_seconds is * %FALSE, then if the time has zero seconds only the hour and minute * of the time are stored in @buffer. **/ 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) { gchar *format; if (!show_midnight && date_tm->tm_hour == 0 && date_tm->tm_min == 0 && date_tm->tm_sec == 0) { /* strftime format of a weekday and a date. */ format = _("%a %m/%d/%Y"); } else if (use_24_hour_format) { if (!show_zero_seconds && date_tm->tm_sec == 0) /* strftime format of a weekday, a date and a * time, in 24-hour format, without seconds. */ format = _("%a %m/%d/%Y %H:%M"); else /* strftime format of a weekday, a date and a * time, in 24-hour format. */ format = _("%a %m/%d/%Y %H:%M:%S"); } else { if (!show_zero_seconds && date_tm->tm_sec == 0) /* strftime format of a weekday, a date and a * time, in 12-hour format, without seconds. */ format = _("%a %m/%d/%Y %I:%M %p"); else /* strftime format of a weekday, a date and a * time, in 12-hour format. */ format = _("%a %m/%d/%Y %I:%M:%S %p"); } /* strftime returns 0 if the string doesn't fit, and leaves the buffer * undefined, so we set it to the empty string in that case. */ if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0) buffer[0] = '\0'; } /** * e_time_format_time: * @date_tm: The #tm to convert to a string. * @use_24_hour_format: A #gboolean. * @show_zero_seconds: A #gboolean. * @buffer: The #char buffer to store the result in. * @buffer_size: The length of @buffer. * * Creates a string representation of a time value in @date_tm and * stores it in @buffer. @buffer_size should be at least 64. **/ void e_time_format_time (struct tm *date_tm, gboolean use_24_hour_format, gboolean show_zero_seconds, gchar *buffer, gint buffer_size) { gchar *format; if (use_24_hour_format) { if (!show_zero_seconds && date_tm->tm_sec == 0) /* strftime format of a time in 24-hour format, * without seconds. */ format = _("%H:%M"); else /* strftime format of a time in 24-hour format. */ format = _("%H:%M:%S"); } else { if (!show_zero_seconds && date_tm->tm_sec == 0) /* strftime format of a time in 12-hour format, * without seconds. */ format = _("%I:%M %p"); else /* strftime format of a time in 12-hour format. */ format = _("%I:%M:%S %p"); } /* strftime returns 0 if the string doesn't fit, and leaves the buffer * undefined, so we set it to the empty string in that case. */ if (e_utf8_strftime (buffer, buffer_size, format, date_tm) == 0) buffer[0] = '\0'; } /** * e_mktime_utc: * @tm: The #tm to convert to a calendar time representation. * * Like mktime(3), but assumes UTC instead of local timezone. * * Returns: The calendar time representation of @tm. **/ time_t e_mktime_utc (struct tm *tm) { time_t tt; tm->tm_isdst = -1; tt = mktime (tm); #if defined (HAVE_TM_GMTOFF) tt += tm->tm_gmtoff; #elif defined (HAVE_TIMEZONE) if (tm->tm_isdst > 0) { #if defined (HAVE_ALTZONE) tt -= altzone; #else /* !defined (HAVE_ALTZONE) */ tt -= (timezone - 3600); #endif } else tt -= timezone; #endif return tt; } /** * e_localtime_with_offset: * @tt: The #time_t to convert. * @tm: The #tm to store the result in. * @offset: The #int to store the offset in. * * Converts the calendar time time representation @tt to a broken-down * time representation, store in @tm, and provides the offset in * seconds from UTC time, stored in @offset. **/ void e_localtime_with_offset (time_t tt, struct tm *tm, gint *offset) { localtime_r (&tt, tm); #if defined (HAVE_TM_GMTOFF) *offset = tm->tm_gmtoff; #elif defined (HAVE_TIMEZONE) if (tm->tm_isdst > 0) { #if defined (HAVE_ALTZONE) *offset = -altzone; #else /* !defined (HAVE_ALTZONE) */ *offset = -(timezone - 3600); #endif } else *offset = -timezone; #endif } #ifdef G_OS_WIN32 static gint _e_string_replace (gchar **str, const gchar *old, const gchar *new) { GRegex *my_regex = g_regex_new (old, 0, 0, NULL); gchar *buf = *str; *str = g_regex_replace(my_regex, buf, -1, 0, new, 0, NULL); g_free (buf); g_regex_unref (my_regex); return 0; } #endif /** * e_time_get_d_fmt_with_4digit_year: * * Retrieves a date format string with a 4-digit year (D_FMT on systems with * nl_langinfo() available). Free the returned string with g_free(). * * Returns: a newly-allocated date format string * * Since: 2.22 **/ gchar * e_time_get_d_fmt_with_4digit_year (void) { gchar *p; gchar *res = NULL; #if defined(HAVE_NL_LANGINFO) res = g_strdup (nl_langinfo (D_FMT) ); #elif defined(G_OS_WIN32) #define GET_LOCALE_INFO(str, len) \ GetLocaleInfoA (LOCALE_USER_DEFAULT, LOCALE_SLONGDATE, str, len) gint format_string_length = GET_LOCALE_INFO (NULL, 0); if (format_string_length > 0) { gsize format_bytes_read; gsize format_bytes_written; gchar *format_string; format_string = g_strnfill (format_string_length + 1, '\0'); GET_LOCALE_INFO (format_string, format_string_length); res = g_locale_to_utf8 ( format_string, format_string_length, &format_bytes_read, &format_bytes_written, NULL); g_free (format_string); /* now, convert the res to format of nl_langinfo */ _e_string_replace (&res, "\\bd\\b", "%#d"); /* d -> %#d */ _e_string_replace (&res, "\\bdd\\b", "%d"); /* dd -> %d */ _e_string_replace (&res, "\\bddd\\b", "%a"); /* ddd -> %a */ _e_string_replace (&res, "\\bdddd\\b", "%A"); /* dddd -> %A */ _e_string_replace (&res, "\\bM\\b", "%#m"); /* M -> %#m */ _e_string_replace (&res, "\\bMM\\b", "%m"); /* MM -> %m */ _e_string_replace (&res, "\\bMMM\\b", "%b"); /* MMM -> %b */ _e_string_replace (&res, "\\bMMMM\\b", "%B"); /* MMMM -> %B */ _e_string_replace (&res, "\\by\\b", "%#y"); /* y -> %y */ _e_string_replace (&res, "\\byy\\b", "%y"); /* yy -> %y */ _e_string_replace (&res, "\\byyyy\\b", "%Y"); /* yyyy -> %Y */ _e_string_replace (&res, "\\byyyyy\\b", "%Y"); /* yyyyy -> %Y */ } #undef GET_LOCALE_INFO /* TODO Implement this for other systems. */ #else /* This will not work for other systems. */ res = g_strdup ("%x"); #endif while (p = strchr (res, 'y'), p) *p = 'Y'; return res; }