diff options
author | Sergey Poznyakoff <gray@gnu.org.ua> | 2011-12-08 17:04:29 +0200 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org.ua> | 2011-12-08 18:33:56 +0200 |
commit | acec689ef648adaa5c420049bfb775953e184bda (patch) | |
tree | ac5a96099e5a646fc24986650b4f9073c9e83642 /libmailutils/datetime | |
parent | 5af1d3a7bd8149139ad59baa11501e3d11b138a7 (diff) | |
download | mailutils-acec689ef648adaa5c420049bfb775953e184bda.tar.gz mailutils-acec689ef648adaa5c420049bfb775953e184bda.tar.bz2 |
Revamp date/time calculations.
* configure.ac: Build libmailutils/datetime/Makefile.am
* include/mailutils/datetime.h (mu_datetime_julianday)
(mu_datetime_dayofweek,mu_datetime_dayofyear)
(mu_datetime_year_days): New protos.
* libmailutils/Makefile.am (SUBDIRS): Add datetime
(libmailutils_la_LIBADD): Add libdatetime.la
* libmailutils/base/Makefile.am (libbase_la_SOURCES): Remove date.c
and strftime.c.
* libmailutils/base/date.c: Remove.
* libmailutils/base/strftime.c: Move to libmailutils/datetime
* libmailutils/datetime/Makefile.am: New file.
* libmailutils/datetime/dow.c: New file.
* libmailutils/datetime/doy.c: New file.
* libmailutils/datetime/jd.c: New file.
* libmailutils/datetime/scantime.c: New file.
* libmailutils/datetime/streamftime.c: New file.
* libmailutils/datetime/tab.c: New file.
* libmailutils/datetime/unixtime.c: New file.
* libmailutils/datetime/utcoff.c: New file.
* libmailutils/datetime/yd.c: New file.
* libmailutils/tests/scantime.at: Fix yday numbers.
Diffstat (limited to 'libmailutils/datetime')
-rw-r--r-- | libmailutils/datetime/Makefile.am | 32 | ||||
-rw-r--r-- | libmailutils/datetime/dow.c | 29 | ||||
-rw-r--r-- | libmailutils/datetime/doy.c | 53 | ||||
-rw-r--r-- | libmailutils/datetime/jd.c | 32 | ||||
-rw-r--r-- | libmailutils/datetime/scantime.c | 660 | ||||
-rw-r--r-- | libmailutils/datetime/streamftime.c | 411 | ||||
-rw-r--r-- | libmailutils/datetime/strftime.c | 44 | ||||
-rw-r--r-- | libmailutils/datetime/tab.c | 45 | ||||
-rw-r--r-- | libmailutils/datetime/unixtime.c | 38 | ||||
-rw-r--r-- | libmailutils/datetime/utcoff.c | 32 | ||||
-rw-r--r-- | libmailutils/datetime/yd.c | 29 |
11 files changed, 1405 insertions, 0 deletions
diff --git a/libmailutils/datetime/Makefile.am b/libmailutils/datetime/Makefile.am new file mode 100644 index 000000000..f9a4f451b --- /dev/null +++ b/libmailutils/datetime/Makefile.am @@ -0,0 +1,32 @@ +# GNU Mailutils -- a suite of utilities for electronic mail +# Copyright (C) 2011 Free Software Foundation, 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; either +# version 3 of the License, or (at your option) any later version. +# +# 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 +# <http://www.gnu.org/licenses/>. + +noinst_LTLIBRARIES = libdatetime.la + +libdatetime_la_SOURCES = \ + dow.c\ + doy.c\ + jd.c\ + scantime.c\ + streamftime.c\ + strftime.c\ + tab.c\ + unixtime.c\ + utcoff.c\ + yd.c + +INCLUDES = @MU_LIB_COMMON_INCLUDES@ -I/libmailutils diff --git a/libmailutils/datetime/dow.c b/libmailutils/datetime/dow.c new file mode 100644 index 000000000..9f94a6803 --- /dev/null +++ b/libmailutils/datetime/dow.c @@ -0,0 +1,29 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2011 Free Software Foundation, 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; either + version 3 of the License, or (at your option) any later version. + + 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 + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <mailutils/datetime.h> + +/* Compute day of week (0 - Sunday) */ +int +mu_datetime_dayofweek (int year, int month, int day) +{ + return (mu_datetime_julianday (year, month, day) + 1) % 7; +} diff --git a/libmailutils/datetime/doy.c b/libmailutils/datetime/doy.c new file mode 100644 index 000000000..49883cd32 --- /dev/null +++ b/libmailutils/datetime/doy.c @@ -0,0 +1,53 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2011 Free Software Foundation, 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; either + version 3 of the License, or (at your option) any later version. + + 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 + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <mailutils/datetime.h> + +static int month_start[]= + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }; + /* Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec + 31 28 31 30 31 30 31 31 30 31 30 31 + */ + +#define leap_year(y) ((y) % 4 == 0 && ((y) % 100 != 0 || (y) % 400 == 0)) + +/* Compute number of days since January 1 to the given day of year. */ +int +mu_datetime_dayofyear (int year, int month, int day) +{ + int leap, month_days; + + if (year < 0 || month < 1 || month > 12 || day < 1) + return -1; + + leap = leap_year (year); + + month_days = month_start[month] - month_start[month - 1] + + ((month == 2) ? leap : 0); + + if (day > month_days) + return -1; + + if (month <= 2) + leap = 0; + + return month_start[month-1] + day + leap; +} diff --git a/libmailutils/datetime/jd.c b/libmailutils/datetime/jd.c new file mode 100644 index 000000000..39fdf8014 --- /dev/null +++ b/libmailutils/datetime/jd.c @@ -0,0 +1,32 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 2011 Free Software Foundation, 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; either + version 3 of the License, or (at your option) any later version. + + 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 + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdlib.h> +#include <mailutils/datetime.h> + +int +mu_datetime_julianday (int year, int month, int day) +{ + int a = (14 - month) / 12; + int y = year + 4800 - a; + int m = month + 12*a - 3; + + return day + (153*m + 2)/5 + 365*y + y/4 - y/100 + y/400 - 32045; +} diff --git a/libmailutils/datetime/scantime.c b/libmailutils/datetime/scantime.c new file mode 100644 index 000000000..670052bb3 --- /dev/null +++ b/libmailutils/datetime/scantime.c @@ -0,0 +1,660 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2000, 2001, 2002, 2007, 2009, 2010, 2011 Free + Software Foundation, 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; either + version 3 of the License, or (at your option) any later version. + + 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 + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mailutils/diag.h> +#include <mailutils/datetime.h> +#include <mailutils/util.h> +#include <mailutils/stream.h> +#include <mailutils/errno.h> +#include <mailutils/cstr.h> +#include <mailutils/cctype.h> + +static int +_mu_short_weekday_string (const char *str) +{ + int i; + + for (i = 0; i < 7; i++) + { + if (mu_c_strncasecmp (str, _mu_datetime_short_wday[i], 3) == 0) + return i; + } + return -1; +} + +static int +_mu_full_weekday_string (const char *str, char **endp) +{ + int i; + + for (i = 0; i < 7; i++) + { + if (mu_c_strcasecmp (str, _mu_datetime_full_wday[i]) == 0) + { + if (endp) + *endp = (char*) (str + strlen (_mu_datetime_full_wday[i])); + return i; + } + } + return -1; +} + +static int +_mu_short_month_string (const char *str) +{ + int i; + + for (i = 0; i < 12; i++) + { + if (mu_c_strncasecmp (str, _mu_datetime_short_month[i], 3) == 0) + return i; + } + return -1; +} + +static int +_mu_full_month_string (const char *str, char **endp) +{ + int i; + + for (i = 0; i < 12; i++) + { + if (mu_c_strcasecmp (str, _mu_datetime_full_month[i]) == 0) + { + if (endp) + *endp = (char*) (str + strlen (_mu_datetime_full_month[i])); + return i; + } + } + return -1; +} + +int +get_num (const char *str, char **endp, int ndig, int minval, int maxval, + int *pn) +{ + int x = 0; + int i; + + errno = 0; + for (i = 0; i < ndig && *str && mu_isdigit (*str); str++, i++) + x = x * 10 + *str - '0'; + + *endp = (char*) str; + if (i == 0) + return -1; + else if (pn) + *pn = i; + else if (i != ndig) + return -1; + if (x < minval || x > maxval) + return -1; + return x; +} + +#define DT_YEAR 0x01 +#define DT_MONTH 0x02 +#define DT_MDAY 0x04 +#define DT_WDAY 0x08 +#define DT_HOUR 0x10 +#define DT_MIN 0x20 +#define DT_SEC 0x40 + +#define ST_NON -1 +#define ST_OPT 0 +#define ST_ALT 1 + +struct save_input +{ + int state; + const char *input; +}; + +static int +push_input (mu_list_t *plist, int state, const char *input) +{ + mu_list_t list = *plist; + struct save_input *inp = malloc (sizeof (*inp)); + if (!inp) + return ENOMEM; + if (!list) + { + int rc = mu_list_create (&list); + if (rc) + { + free (inp); + return rc; + } + mu_list_set_destroy_item (list, mu_list_free_item); + *plist = list; + } + inp->state = state; + inp->input = input; + return mu_list_push (list, (void*)inp); +} + +static int +peek_state (mu_list_t list, int *state, const char **input) +{ + int rc; + struct save_input *inp; + + rc = mu_list_tail (list, (void**)&inp); + if (rc) + return rc; + *state = inp->state; + if (input) + *input = inp->input; + return 0; +} + +static int +pop_input (mu_list_t list, int *state, const char **input) +{ + int rc; + struct save_input *inp; + + rc = mu_list_pop (list, (void**)&inp); + if (rc) + return rc; + *state = inp->state; + if (input) + *input = inp->input; + return 0; +} + +static int +bracket_to_state (int c) +{ + switch (c) + { + case '[': + case ']': + return ST_OPT; + case '(': + case ')': + return ST_ALT; + } + return ST_NON; +} + +static int +state_to_closing_bracket (int st) +{ + switch (st) + { + case ST_OPT: + return ']'; + case ST_ALT: + return ')'; + } + return '?'; +} + +static int +scan_recovery (const char *fmt, mu_list_t *plist, int skip_alt, + const char **endp, + const char **input) +{ + int c, rc = 0; + int nesting_level = 1; + int st; + const char *p; + + while (*fmt) + { + c = *fmt++; + + if (c == '%') + { + c = *fmt++; + if (!c) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: %% at the end of input", + __FILE__, __LINE__)); + rc = MU_ERR_FORMAT; + break; + } + + switch (c) + { + case '[': + case '(': + nesting_level++; + rc = push_input (plist, bracket_to_state (c), NULL); + break; + + case ')': + case ']': + rc = pop_input (*plist, &st, &p); + if (rc || st != bracket_to_state (c)) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: %%%c out of context", + __FILE__, __LINE__, c)); + rc = MU_ERR_FORMAT; + break; + } + if (--nesting_level == 0) + { + *endp = fmt; + if (skip_alt) + return 0; + *input = p; + if (st == ST_ALT) + { + if (*fmt == '%' && (fmt[1] == '|' || fmt[1] == ']')) + return 0; + return MU_ERR_PARSE; /* No match found */ + } + return 0; + } + break; + + case '|': + if (skip_alt) + continue; + if (nesting_level == 1) + { + *endp = fmt; + return peek_state (*plist, &st, input); + } + break; + + case '\\': + if (*++fmt == 0) + { + peek_state (*plist, &st, NULL); + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: missing closing %%%c", + __FILE__, __LINE__, + state_to_closing_bracket (st))); + return MU_ERR_FORMAT; + } + } + } + } + + peek_state (*plist, &st, NULL); + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: missing closing %%%c", + __FILE__, __LINE__, + state_to_closing_bracket (st))); + return MU_ERR_FORMAT; +} + +int +mu_scan_datetime (const char *input, const char *fmt, + struct tm *tm, struct mu_timezone *tz, char **endp) +{ + int rc = 0; + char *p; + int n; + int c; + int st; + int recovery = 0; + int eof_ok = 0; + int datetime_parts = 0; + mu_list_t save_input_list = NULL; + + memset (tm, 0, sizeof *tm); +#ifdef HAVE_STRUCT_TM_TM_ISDST + tm->tm_isdst = -1; /* unknown. */ +#endif + /* provide default timezone, in case it is not supplied in input */ + if (tz) + { + memset (tz, 0, sizeof *tz); + tz->utc_offset = mu_utc_offset (); + } + + /* Skip leading whitespace */ + input = mu_str_skip_class (input, MU_CTYPE_BLANK); + for (; *fmt && rc == 0; fmt++) + { + if (mu_isspace (*fmt)) + { + fmt = mu_str_skip_class (fmt, MU_CTYPE_BLANK); + input = mu_str_skip_class (input, MU_CTYPE_BLANK); + if (!*fmt) + break; + } + eof_ok = 0; + + if (*fmt == '%') + { + c = *++fmt; + if (!c) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: %% at the end of input", + __FILE__, __LINE__)); + rc = MU_ERR_FORMAT; + break; + } + + switch (c) + { + case 'a': + /* The abbreviated weekday name. */ + n = _mu_short_weekday_string (input); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_wday = n; + datetime_parts |= DT_WDAY; + input += 3; + } + break; + + case 'A': + /* The full weekday name. */ + n = _mu_full_weekday_string (input, &p); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_wday = n; + datetime_parts |= DT_WDAY; + input = p; + } + break; + + case 'b': + /* The abbreviated month name. */ + n = _mu_short_month_string (input); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_mon = n; + datetime_parts |= DT_MONTH; + input += 3; + } + break; + + case 'B': + /* The full month name. */ + n = _mu_full_month_string (input, &p); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_mon = n; + datetime_parts |= DT_MONTH; + input = p; + } + break; + + case 'd': + /* The day of the month as a decimal number (range 01 to 31). */ + n = get_num (input, &p, 2, 1, 31, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_mday = n; + datetime_parts |= DT_MDAY; + input = p; + } + break; + + case 'e': + /* Like %d, the day of the month as a decimal number, but a + leading zero is replaced by a space. */ + { + int ndig; + + n = get_num (input, &p, 2, 1, 31, &ndig); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_mday = n; + datetime_parts |= DT_MDAY; + input = p; + } + } + break; + + case 'H': + /* The hour as a decimal number using a 24-hour clock (range + 00 to 23). */ + n = get_num (input, &p, 2, 0, 23, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_hour = n; + datetime_parts |= DT_HOUR; + input = p; + } + break; + + case 'm': + /* The month as a decimal number (range 01 to 12). */ + n = get_num (input, &p, 2, 1, 12, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_mon = n - 1; + datetime_parts |= DT_MONTH; + input = p; + } + break; + + case 'M': + /* The minute as a decimal number (range 00 to 59). */ + n = get_num (input, &p, 2, 0, 59, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_min = n; + datetime_parts |= DT_MIN; + input = p; + } + break; + + case 'S': + /* The second as a decimal number (range 00 to 60) */ + n = get_num (input, &p, 2, 0, 60, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + tm->tm_sec = n; + datetime_parts |= DT_SEC; + input = p; + } + break; + + case 'Y': + /* The year as a decimal number including the century. */ + errno = 0; + n = strtoul (input, &p, 10); + if (errno || p == input) + rc = MU_ERR_PARSE; + else + { + tm->tm_year = n - 1900; + datetime_parts |= DT_YEAR; + input = p; + } + break; + + case 'z': + /* The time-zone as hour offset from GMT */ + { + int sign = 1; + int hr; + + if (*input == '+') + input++; + else if (*input == '-') + { + input++; + sign = -1; + } + n = get_num (input, &p, 2, 0, 11, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + input = p; + hr = n; + n = get_num (input, &p, 2, 0, 59, NULL); + if (n == -1) + rc = MU_ERR_PARSE; + else + { + input = p; + if (tz) + tz->utc_offset = sign * (hr * 60 + n) * 60; + } + } + } + break; + + case '%': + if (*input == '%') + input++; + else + rc = MU_ERR_PARSE; + break; + + rc = push_input (&save_input_list, ST_ALT, (void*)input); + break; + + case '(': + case '[': + rc = push_input (&save_input_list, bracket_to_state (c), + (void*)input); + break; + + case ')': + case ']': + if (pop_input (save_input_list, &st, NULL)) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: unbalanced %%%c near %s", + __FILE__, __LINE__, c, fmt)); + rc = MU_ERR_FORMAT; + } + else if (st != bracket_to_state (c)) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: %%%c out of context", + __FILE__, __LINE__, c)); + rc = MU_ERR_FORMAT; + } + break; + + case '|': + rc = scan_recovery (fmt, &save_input_list, 1, &fmt, NULL); + if (rc == 0) + fmt--; + break; + + case '$': + eof_ok = 1; + break; + + case '\\': + c = *++fmt; + if (!c) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: %% at the end of input", + __FILE__, __LINE__)); + rc = MU_ERR_FORMAT; + } + else if (c == *input) + input++; + else + rc = MU_ERR_PARSE; + break; + + case '?': + input++; + break; + + default: + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: unrecognized conversion type" + " near %s", + __FILE__, __LINE__, fmt)); + rc = MU_ERR_FORMAT; + break; + } + + if (eof_ok && rc == 0 && *input == 0) + break; + } + else if (!recovery && *input != *fmt) + rc = MU_ERR_PARSE; + else + input++; + + if (rc == MU_ERR_PARSE && !mu_list_is_empty (save_input_list)) + { + rc = scan_recovery (fmt, &save_input_list, 0, &fmt, &input); + if (rc == 0) + --fmt; + } + } + + if (!mu_list_is_empty (save_input_list)) + { + mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR, + ("%s:%d: error in format: closing bracket missing", + __FILE__, __LINE__)); + rc = MU_ERR_FORMAT; + } + mu_list_destroy (&save_input_list); + + if (rc == 0 && recovery) + rc = MU_ERR_PARSE; + + if (!eof_ok && rc == 0 && *input == 0 && *fmt) + rc = MU_ERR_PARSE; + + if ((datetime_parts & (DT_YEAR|DT_MONTH|DT_MDAY)) == + (DT_YEAR|DT_MONTH|DT_MDAY)) + { + if (!(datetime_parts & DT_WDAY)) + tm->tm_wday = mu_datetime_dayofweek (tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday); + tm->tm_yday = mu_datetime_dayofyear (tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday) - 1; + } + + if (endp) + *endp = (char*) input; + + return rc; +} diff --git a/libmailutils/datetime/streamftime.c b/libmailutils/datetime/streamftime.c new file mode 100644 index 000000000..e67d7d4d0 --- /dev/null +++ b/libmailutils/datetime/streamftime.c @@ -0,0 +1,411 @@ +/* GNU Mailutils -- a suite of utilities for electronic mail + Copyright (C) 1999, 2000, 2001, 2002, 2007, 2009, 2010, 2011 Free + Software Foundation, 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; either + version 3 of the License, or (at your option) any later version. + + 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 + <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mailutils/diag.h> +#include <mailutils/datetime.h> +#include <mailutils/util.h> +#include <mailutils/stream.h> +#include <mailutils/errno.h> +#include <mailutils/cstr.h> +#include <mailutils/cctype.h> + +#define ISO_8601_START_WDAY 1 /* Monday */ +#define ISO_8601_MAX_WDAY 4 /* Thursday */ +#define MAXDAYS 366 /* Max. number of days in a year */ +#define ISO_8601_OFF ((MAXDAYS / 7 + 2) * 7) + +int +ISO_8601_weekdays (int yday, int wday) +{ + return (yday + - (yday - wday + ISO_8601_MAX_WDAY + ISO_8601_OFF) % 7 + + ISO_8601_MAX_WDAY - ISO_8601_START_WDAY); +} + +int +mu_c_streamftime (mu_stream_t str, const char *fmt, struct tm *input_tm, + struct mu_timezone *tz) +{ + int rc = 0; + struct tm tm; + + /* Copy input TM because it might have been received from gmtime and + the fmt might result in further calls to gmtime which will clobber + it. */ + tm = *input_tm; + while (*fmt && rc == 0) + { + size_t len = strcspn (fmt, "%"); + if (len) + { + rc = mu_stream_write (str, fmt, len, NULL); + if (rc) + break; + } + + fmt += len; + + restart: + if (!*fmt || !*++fmt) + break; + + switch (*fmt) + { + case 'a': + /* The abbreviated weekday name. */ + if (tm.tm_wday < 0 || tm.tm_wday > 6) + rc = ERANGE; + else + rc = mu_stream_write (str, _mu_datetime_short_wday[tm.tm_wday], + strlen (_mu_datetime_short_wday[tm.tm_wday]), + NULL); + break; + + case 'A': + /* The full weekday name. */ + if (tm.tm_wday < 0 || tm.tm_wday > 6) + rc = ERANGE; + else + rc = mu_stream_write (str, _mu_datetime_full_wday[tm.tm_wday], + strlen (_mu_datetime_full_wday[tm.tm_wday]), + NULL); + break; + + case 'b': + case 'h': + /* The abbreviated month name. */ + if (tm.tm_mon < 0 || tm.tm_mon > 11) + rc = ERANGE; + else + rc = mu_stream_write (str, _mu_datetime_short_month[tm.tm_mon], + strlen (_mu_datetime_short_month[tm.tm_mon]), + NULL); + break; + + case 'B': + /* The full month name. */ + if (tm.tm_mon < 0 || tm.tm_mon > 11) + rc = ERANGE; + else + rc = mu_stream_write (str, _mu_datetime_full_month[tm.tm_mon], + strlen (_mu_datetime_full_month[tm.tm_mon]), + NULL); + break; + + case 'c': + /* The preferred date and time representation. */ + rc = mu_c_streamftime (str, "%a %b %e %H:%M:%S %Y", &tm, tz); + break; + + case 'C': + /* The century number (year/100) as a 2-digit integer. */ + rc = mu_stream_printf (str, "%02d", (tm.tm_year + 1900) / 100); + break; + + case 'd': + /* The day of the month as a decimal number (range 01 to 31). */ + if (tm.tm_mday < 1 || tm.tm_mday > 31) + rc = ERANGE; + else + rc = mu_stream_printf (str, "%02d", tm.tm_mday); + break; + + case 'D': + /* Equivalent to %m/%d/%y. */ + rc = mu_c_streamftime (str, "%m/%d/%y", &tm, tz); + break; + + case 'e': + /* Like %d, the day of the month as a decimal number, but a leading + zero is replaced by a space. */ + if (tm.tm_mday < 1 || tm.tm_mday > 31) + rc = ERANGE; + else + rc = mu_stream_printf (str, "%2d", tm.tm_mday); + break; + + case 'E': + /* Modifier. The Single Unix Specification mentions %Ec, %EC, %Ex, + %EX, %Ey, and %EY, which are supposed to use a corresponding + locale-dependent alternative representation. + A no-op in POSIX locale */ + goto restart; + + case 'F': + /* Equivalent to %Y-%m-%d (the ISO 8601 date format). */ + rc = mu_c_streamftime (str, "%Y-%m-%d", &tm, tz); + break; + + case 'V': + /* The ISO 8601:1988 week number of the current year as a decimal + number range 01 to 53, where week 1 is the first week that has + at least 4 days in the current year, and with Monday as the + first day of the week. + */ + case 'G': + /* The ISO 8601 year with century as a decimal number. The 4-digit + year corresponding to the ISO week number (see %V). This has + the same format and value as %y, except that if the ISO week + number belongs to the previous or next year, that year is used + instead. + */ + case 'g': + /* Like %G, but without century, that is, with a 2-digit year + (00-99). */ + { + int year = tm.tm_year + 1900; + int days = ISO_8601_weekdays (tm.tm_yday, tm.tm_wday); + + if (days < 0) + { + days = ISO_8601_weekdays (tm.tm_yday + + mu_datetime_year_days (year - 1), + tm.tm_wday); + year--; + } + else + { + int d = ISO_8601_weekdays (tm.tm_yday - + mu_datetime_year_days (year), + tm.tm_wday); + if (d >= 0) + { + year++; + days = d; + } + } + + switch (*fmt) + { + case 'V': + rc = mu_stream_printf (str, "%02d", days / 7 + 1); + break; + + case 'G': + rc = mu_stream_printf (str, "%4d", year); + break; + + case 'g': + rc = mu_stream_printf (str, "%02d", year % 100); + } + } + break; + + case 'H': + /* The hour as a decimal number using a 24-hour clock (range 00 to + 23). */ + rc = mu_stream_printf (str, "%02d", tm.tm_hour); + break; + + case 'I': + /* The hour as a decimal number using a 12-hour clock (range 01 to + 12). */ + { + unsigned n = tm.tm_hour % 12; + rc = mu_stream_printf (str, "%02d", n == 0 ? 12 : n); + } + break; + + case 'j': + /* The day of the year as a decimal number (range 001 to 366). */ + rc = mu_stream_printf (str, "%03d", tm.tm_yday + 1); + break; + + case 'k': + /* The hour (24-hour clock) as a decimal number (range 0 to 23); + single digits are preceded by a blank. */ + rc = mu_stream_printf (str, "%2d", tm.tm_hour); + break; + + case 'l': + /* The hour (12-hour clock) as a decimal number (range 1 to 12); + single digits are preceded by a blank. */ + { + unsigned n = tm.tm_hour % 12; + rc = mu_stream_printf (str, "%2d", n == 0 ? 12 : n); + } + break; + + case 'm': + /* The month as a decimal number (range 01 to 12). */ + rc = mu_stream_printf (str, "%02d", tm.tm_mon + 1); + break; + + case 'M': + /* The minute as a decimal number (range 00 to 59). */ + rc = mu_stream_printf (str, "%02d", tm.tm_min); + break; + + case 'n': + /* A newline character. */ + rc = mu_stream_write (str, "\n", 1, NULL); + break; + + case 'O': + /* Modifier. The Single Unix Specification mentions %Od, %Oe, %OH, < |