summaryrefslogtreecommitdiff
path: root/libmailutils/datetime/scantime.c
diff options
context:
space:
mode:
Diffstat (limited to 'libmailutils/datetime/scantime.c')
-rw-r--r--libmailutils/datetime/scantime.c660
1 files changed, 660 insertions, 0 deletions
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;
+}

Return to:

Send suggestions and report system problems to the System administrator.