/* * Copyright 2007 Rob Kendrick * Copyright 2004-2007 James Bursa * Copyright 2003 Phil Mellor * Copyright 2003 John M Bell * Copyright 2004 John Tytgat * * This file is part of NetSurf, http://www.netsurf-browser.org/ * * NetSurf is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * NetSurf 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * \file utils/time.c * \brief Implementation of time operations. */ #include #include #include #ifdef WITH_CURL #include #endif #include "utils/ascii.h" #include "utils/errors.h" #include "utils/time.h" /** * Weekdays * * Must be calender order. */ enum nsc_time_weekdays { NSC_TIME_WEEKDAY_SUN, NSC_TIME_WEEKDAY_MON, NSC_TIME_WEEKDAY_TUE, NSC_TIME_WEEKDAY_WED, NSC_TIME_WEEKDAY_THU, NSC_TIME_WEEKDAY_FRI, NSC_TIME_WEEKDAY_SAT, NSC_TIME_WEEKDAY__COUNT }; /** * Months * * Must be calender order. */ enum nsc_time_months { NSC_TIME_MONTH_JAN, NSC_TIME_MONTH_FEB, NSC_TIME_MONTH_MAR, NSC_TIME_MONTH_APR, NSC_TIME_MONTH_MAY, NSC_TIME_MONTH_JUN, NSC_TIME_MONTH_JUL, NSC_TIME_MONTH_AUG, NSC_TIME_MONTH_SEP, NSC_TIME_MONTH_OCT, NSC_TIME_MONTH_NOV, NSC_TIME_MONTH_DEC, NSC_TIME_MONTH__COUNT, }; /** * Array of short weekday names. */ static const char * const weekdays_short[NSC_TIME_WEEKDAY__COUNT] = { [NSC_TIME_WEEKDAY_SUN] = "Sun", [NSC_TIME_WEEKDAY_MON] = "Mon", [NSC_TIME_WEEKDAY_TUE] = "Tue", [NSC_TIME_WEEKDAY_WED] = "Wed", [NSC_TIME_WEEKDAY_THU] = "Thu", [NSC_TIME_WEEKDAY_FRI] = "Fri", [NSC_TIME_WEEKDAY_SAT] = "Sat" }; /** * Array of month names. */ static const char * const months[NSC_TIME_MONTH__COUNT] = { [NSC_TIME_MONTH_JAN] = "Jan", [NSC_TIME_MONTH_FEB] = "Feb", [NSC_TIME_MONTH_MAR] = "Mar", [NSC_TIME_MONTH_APR] = "Apr", [NSC_TIME_MONTH_MAY] = "May", [NSC_TIME_MONTH_JUN] = "Jun", [NSC_TIME_MONTH_JUL] = "Jul", [NSC_TIME_MONTH_AUG] = "Aug", [NSC_TIME_MONTH_SEP] = "Sep", [NSC_TIME_MONTH_OCT] = "Oct", [NSC_TIME_MONTH_NOV] = "Nov", [NSC_TIME_MONTH_DEC] = "Dec" }; /* exported interface documented in utils/time.h */ const char *rfc1123_date(time_t t) { static char ret[30]; struct tm *tm = gmtime(&t); snprintf(ret, sizeof ret, "%s, %02d %s %d %02d:%02d:%02d GMT", weekdays_short[tm->tm_wday], tm->tm_mday, months[tm->tm_mon], tm->tm_year + 1900, tm->tm_hour, tm->tm_min, tm->tm_sec); return ret; } /* exported function documented in utils/time.h */ int nsc_sntimet(char *str, size_t size, time_t *timep) { #ifndef HAVE_STRFTIME long long val; val = (long long)*timep; return snprintf(str, size, "%lld", val); #else struct tm *ltm; ltm = localtime(timep); if (ltm == NULL) { return -1; } return strftime(str, size, "%s", ltm); #endif } /* exported function documented in utils/time.h */ nserror nsc_snptimet(const char *str, size_t size, time_t *timep) { time_t time_out; #ifndef HAVE_STRPTIME char *rstr; if (size < 1) { return NSERROR_BAD_PARAMETER; } errno = 0; time_out = (time_t)strtoll(str, &rstr, 10); /* The conversion may have a range faliure or no digits were found */ if ((errno != 0) || (rstr == str)) { return NSERROR_BAD_PARAMETER; } #else struct tm ltm; if (size < 1) { return NSERROR_BAD_PARAMETER; } if (strptime(str, "%s", <m) == NULL) { return NSERROR_BAD_PARAMETER; } time_out = mktime(<m); #endif *timep = time_out; return NSERROR_OK; } #ifndef WITH_CURL /** * Array of long weekday names. */ static const char * const weekdays_long[NSC_TIME_WEEKDAY__COUNT] = { [NSC_TIME_WEEKDAY_SUN] = "Sunday", [NSC_TIME_WEEKDAY_MON] = "Monday", [NSC_TIME_WEEKDAY_TUE] = "Tuesday", [NSC_TIME_WEEKDAY_WED] = "Wednesday", [NSC_TIME_WEEKDAY_THU] = "Thursday", [NSC_TIME_WEEKDAY_FRI] = "Friday", [NSC_TIME_WEEKDAY_SAT] = "Saturday" }; /** * Timezone offsets in mins * * Order doesn't matter. */ enum nsc_time_zone_offsets { /* Timezones */ NSC_TIME_ZONE_OFFSET_IDLE = -12 * 60, NSC_TIME_ZONE_OFFSET_NZST = -12 * 60, NSC_TIME_ZONE_OFFSET_NZT = -12 * 60, NSC_TIME_ZONE_OFFSET_EAST = -10 * 60, NSC_TIME_ZONE_OFFSET_GST = -10 * 60, NSC_TIME_ZONE_OFFSET_JST = - 9 * 60, NSC_TIME_ZONE_OFFSET_CCT = - 8 * 60, NSC_TIME_ZONE_OFFSET_WAST = - 7 * 60, NSC_TIME_ZONE_OFFSET_EET = - 2 * 60, NSC_TIME_ZONE_OFFSET_CET = - 1 * 60, NSC_TIME_ZONE_OFFSET_FWT = - 1 * 60, NSC_TIME_ZONE_OFFSET_MET = - 1 * 60, NSC_TIME_ZONE_OFFSET_MEWT = - 1 * 60, NSC_TIME_ZONE_OFFSET_GMT = 0, NSC_TIME_ZONE_OFFSET_UTC = 0, NSC_TIME_ZONE_OFFSET_WET = 0, NSC_TIME_ZONE_OFFSET_WAT = 1 * 60, NSC_TIME_ZONE_OFFSET_AST = 4 * 60, NSC_TIME_ZONE_OFFSET_EST = 5 * 60, NSC_TIME_ZONE_OFFSET_CST = 6 * 60, NSC_TIME_ZONE_OFFSET_MST = 7 * 60, NSC_TIME_ZONE_OFFSET_PST = 8 * 60, NSC_TIME_ZONE_OFFSET_YST = 9 * 60, NSC_TIME_ZONE_OFFSET_AHST = 10 * 60, NSC_TIME_ZONE_OFFSET_CAT = 10 * 60, NSC_TIME_ZONE_OFFSET_HST = 10 * 60, NSC_TIME_ZONE_OFFSET_IDLW = 12 * 60, /* Daylight saving modified timezones */ NSC_TIME_ZONE_OFFSET_NZDT = NSC_TIME_ZONE_OFFSET_NZT - 60, NSC_TIME_ZONE_OFFSET_EADT = NSC_TIME_ZONE_OFFSET_EAST - 60, NSC_TIME_ZONE_OFFSET_WADT = NSC_TIME_ZONE_OFFSET_WAST - 60, NSC_TIME_ZONE_OFFSET_CEST = NSC_TIME_ZONE_OFFSET_CET - 60, NSC_TIME_ZONE_OFFSET_FST = NSC_TIME_ZONE_OFFSET_FWT - 60, NSC_TIME_ZONE_OFFSET_MEST = NSC_TIME_ZONE_OFFSET_MET - 60, NSC_TIME_ZONE_OFFSET_MESZ = NSC_TIME_ZONE_OFFSET_MET - 60, NSC_TIME_ZONE_OFFSET_BST = NSC_TIME_ZONE_OFFSET_GMT - 60, NSC_TIME_ZONE_OFFSET_ADT = NSC_TIME_ZONE_OFFSET_AST - 60, NSC_TIME_ZONE_OFFSET_EDT = NSC_TIME_ZONE_OFFSET_EST - 60, NSC_TIME_ZONE_OFFSET_CDT = NSC_TIME_ZONE_OFFSET_CST - 60, NSC_TIME_ZONE_OFFSET_MDT = NSC_TIME_ZONE_OFFSET_MST - 60, NSC_TIME_ZONE_OFFSET_PDT = NSC_TIME_ZONE_OFFSET_PST - 60, NSC_TIME_ZONE_OFFSET_YDT = NSC_TIME_ZONE_OFFSET_YST - 60, NSC_TIME_ZONE_OFFSET_HDT = NSC_TIME_ZONE_OFFSET_HST - 60, /* Military timezones */ NSC_TIME_ZONE_OFFSET_Y = -12 * 60, NSC_TIME_ZONE_OFFSET_X = -11 * 60, NSC_TIME_ZONE_OFFSET_W = -10 * 60, NSC_TIME_ZONE_OFFSET_V = - 9 * 60, NSC_TIME_ZONE_OFFSET_U = - 8 * 60, NSC_TIME_ZONE_OFFSET_T = - 7 * 60, NSC_TIME_ZONE_OFFSET_S = - 6 * 60, NSC_TIME_ZONE_OFFSET_R = - 5 * 60, NSC_TIME_ZONE_OFFSET_Q = - 4 * 60, NSC_TIME_ZONE_OFFSET_P = - 3 * 60, NSC_TIME_ZONE_OFFSET_O = - 2 * 60, NSC_TIME_ZONE_OFFSET_N = - 1 * 60, NSC_TIME_ZONE_OFFSET_Z = 0 * 60, NSC_TIME_ZONE_OFFSET_A = 1 * 60, NSC_TIME_ZONE_OFFSET_B = 2 * 60, NSC_TIME_ZONE_OFFSET_C = 3 * 60, NSC_TIME_ZONE_OFFSET_D = 4 * 60, NSC_TIME_ZONE_OFFSET_E = 5 * 60, NSC_TIME_ZONE_OFFSET_F = 6 * 60, NSC_TIME_ZONE_OFFSET_G = 7 * 60, NSC_TIME_ZONE_OFFSET_H = 8 * 60, NSC_TIME_ZONE_OFFSET_I = 9 * 60, NSC_TIME_ZONE_OFFSET_K = 10 * 60, NSC_TIME_ZONE_OFFSET_L = 11 * 60, NSC_TIME_ZONE_OFFSET_M = 12 * 60, }; /** * List of timezones. * * The order here is the order they appear in the `timezone_mins` and * `timezones` arrays. So there is value in putting the most common * timezones first. */ enum nsc_time_zones { /** "GMT" first since its the only one I've seen in the wild. -- tlsa */ NSC_TIME_ZONE_GMT, NSC_TIME_ZONE_IDLE, NSC_TIME_ZONE_NZST, NSC_TIME_ZONE_NZT, NSC_TIME_ZONE_EAST, NSC_TIME_ZONE_GST, NSC_TIME_ZONE_JST, NSC_TIME_ZONE_CCT, NSC_TIME_ZONE_WAST, NSC_TIME_ZONE_EET, NSC_TIME_ZONE_CET, NSC_TIME_ZONE_FWT, NSC_TIME_ZONE_MET, NSC_TIME_ZONE_MEWT, NSC_TIME_ZONE_UTC, NSC_TIME_ZONE_WET, NSC_TIME_ZONE_WAT, NSC_TIME_ZONE_AST, NSC_TIME_ZONE_EST, NSC_TIME_ZONE_CST, NSC_TIME_ZONE_MST, NSC_TIME_ZONE_PST, NSC_TIME_ZONE_YST, NSC_TIME_ZONE_AHST, NSC_TIME_ZONE_CAT, NSC_TIME_ZONE_HST, NSC_TIME_ZONE_IDLW, NSC_TIME_ZONE_NZDT, NSC_TIME_ZONE_EADT, NSC_TIME_ZONE_WADT, NSC_TIME_ZONE_CEST, NSC_TIME_ZONE_FST, NSC_TIME_ZONE_MEST, NSC_TIME_ZONE_MESZ, NSC_TIME_ZONE_BST, NSC_TIME_ZONE_ADT, NSC_TIME_ZONE_EDT, NSC_TIME_ZONE_CDT, NSC_TIME_ZONE_MDT, NSC_TIME_ZONE_PDT, NSC_TIME_ZONE_YDT, NSC_TIME_ZONE_HDT, NSC_TIME_ZONE_Y, NSC_TIME_ZONE_X, NSC_TIME_ZONE_W, NSC_TIME_ZONE_V, NSC_TIME_ZONE_U, NSC_TIME_ZONE_T, NSC_TIME_ZONE_S, NSC_TIME_ZONE_R, NSC_TIME_ZONE_Q, NSC_TIME_ZONE_P, NSC_TIME_ZONE_O, NSC_TIME_ZONE_N, NSC_TIME_ZONE_Z, NSC_TIME_ZONE_A, NSC_TIME_ZONE_B, NSC_TIME_ZONE_C, NSC_TIME_ZONE_D, NSC_TIME_ZONE_E, NSC_TIME_ZONE_F, NSC_TIME_ZONE_G, NSC_TIME_ZONE_H, NSC_TIME_ZONE_I, NSC_TIME_ZONE_K, NSC_TIME_ZONE_L, NSC_TIME_ZONE_M, NSC_TIME_ZONE__COUNT }; /** * Array of minute offsets for timezones. */ static const int16_t timezone_mins[NSC_TIME_ZONE__COUNT] = { [NSC_TIME_ZONE_IDLE] = NSC_TIME_ZONE_OFFSET_IDLE, [NSC_TIME_ZONE_NZST] = NSC_TIME_ZONE_OFFSET_NZST, [NSC_TIME_ZONE_NZT] = NSC_TIME_ZONE_OFFSET_NZT, [NSC_TIME_ZONE_EAST] = NSC_TIME_ZONE_OFFSET_EAST, [NSC_TIME_ZONE_GST] = NSC_TIME_ZONE_OFFSET_GST, [NSC_TIME_ZONE_JST] = NSC_TIME_ZONE_OFFSET_JST, [NSC_TIME_ZONE_CCT] = NSC_TIME_ZONE_OFFSET_CCT, [NSC_TIME_ZONE_WAST] = NSC_TIME_ZONE_OFFSET_WAST, [NSC_TIME_ZONE_EET] = NSC_TIME_ZONE_OFFSET_EET, [NSC_TIME_ZONE_CET] = NSC_TIME_ZONE_OFFSET_CET, [NSC_TIME_ZONE_FWT] = NSC_TIME_ZONE_OFFSET_FWT, [NSC_TIME_ZONE_MET] = NSC_TIME_ZONE_OFFSET_MET, [NSC_TIME_ZONE_MEWT] = NSC_TIME_ZONE_OFFSET_MEWT, [NSC_TIME_ZONE_GMT] = NSC_TIME_ZONE_OFFSET_GMT, [NSC_TIME_ZONE_UTC] = NSC_TIME_ZONE_OFFSET_UTC, [NSC_TIME_ZONE_WET] = NSC_TIME_ZONE_OFFSET_WET, [NSC_TIME_ZONE_WAT] = NSC_TIME_ZONE_OFFSET_WAT, [NSC_TIME_ZONE_AST] = NSC_TIME_ZONE_OFFSET_AST, [NSC_TIME_ZONE_EST] = NSC_TIME_ZONE_OFFSET_EST, [NSC_TIME_ZONE_CST] = NSC_TIME_ZONE_OFFSET_CST, [NSC_TIME_ZONE_MST] = NSC_TIME_ZONE_OFFSET_MST, [NSC_TIME_ZONE_PST] = NSC_TIME_ZONE_OFFSET_PST, [NSC_TIME_ZONE_YST] = NSC_TIME_ZONE_OFFSET_YST, [NSC_TIME_ZONE_AHST] = NSC_TIME_ZONE_OFFSET_AHST, [NSC_TIME_ZONE_CAT] = NSC_TIME_ZONE_OFFSET_CAT, [NSC_TIME_ZONE_HST] = NSC_TIME_ZONE_OFFSET_HST, [NSC_TIME_ZONE_IDLW] = NSC_TIME_ZONE_OFFSET_IDLW, [NSC_TIME_ZONE_NZDT] = NSC_TIME_ZONE_OFFSET_NZDT, [NSC_TIME_ZONE_EADT] = NSC_TIME_ZONE_OFFSET_EADT, [NSC_TIME_ZONE_WADT] = NSC_TIME_ZONE_OFFSET_WADT, [NSC_TIME_ZONE_CEST] = NSC_TIME_ZONE_OFFSET_CEST, [NSC_TIME_ZONE_FST] = NSC_TIME_ZONE_OFFSET_FST, [NSC_TIME_ZONE_MEST] = NSC_TIME_ZONE_OFFSET_MEST, [NSC_TIME_ZONE_MESZ] = NSC_TIME_ZONE_OFFSET_MESZ, [NSC_TIME_ZONE_BST] = NSC_TIME_ZONE_OFFSET_BST, [NSC_TIME_ZONE_ADT] = NSC_TIME_ZONE_OFFSET_ADT, [NSC_TIME_ZONE_EDT] = NSC_TIME_ZONE_OFFSET_EDT, [NSC_TIME_ZONE_CDT] = NSC_TIME_ZONE_OFFSET_CDT, [NSC_TIME_ZONE_MDT] = NSC_TIME_ZONE_OFFSET_MDT, [NSC_TIME_ZONE_PDT] = NSC_TIME_ZONE_OFFSET_PDT, [NSC_TIME_ZONE_YDT] = NSC_TIME_ZONE_OFFSET_YDT, [NSC_TIME_ZONE_HDT] = NSC_TIME_ZONE_OFFSET_HDT, [NSC_TIME_ZONE_Y] = NSC_TIME_ZONE_OFFSET_Y, [NSC_TIME_ZONE_X] = NSC_TIME_ZONE_OFFSET_X, [NSC_TIME_ZONE_W] = NSC_TIME_ZONE_OFFSET_W, [NSC_TIME_ZONE_V] = NSC_TIME_ZONE_OFFSET_V, [NSC_TIME_ZONE_U] = NSC_TIME_ZONE_OFFSET_U, [NSC_TIME_ZONE_T] = NSC_TIME_ZONE_OFFSET_T, [NSC_TIME_ZONE_S] = NSC_TIME_ZONE_OFFSET_S, [NSC_TIME_ZONE_R] = NSC_TIME_ZONE_OFFSET_R, [NSC_TIME_ZONE_Q] = NSC_TIME_ZONE_OFFSET_Q, [NSC_TIME_ZONE_P] = NSC_TIME_ZONE_OFFSET_P, [NSC_TIME_ZONE_O] = NSC_TIME_ZONE_OFFSET_O, [NSC_TIME_ZONE_N] = NSC_TIME_ZONE_OFFSET_N, [NSC_TIME_ZONE_Z] = NSC_TIME_ZONE_OFFSET_Z, [NSC_TIME_ZONE_A] = NSC_TIME_ZONE_OFFSET_A, [NSC_TIME_ZONE_B] = NSC_TIME_ZONE_OFFSET_B, [NSC_TIME_ZONE_C] = NSC_TIME_ZONE_OFFSET_C, [NSC_TIME_ZONE_D] = NSC_TIME_ZONE_OFFSET_D, [NSC_TIME_ZONE_E] = NSC_TIME_ZONE_OFFSET_E, [NSC_TIME_ZONE_F] = NSC_TIME_ZONE_OFFSET_F, [NSC_TIME_ZONE_G] = NSC_TIME_ZONE_OFFSET_G, [NSC_TIME_ZONE_H] = NSC_TIME_ZONE_OFFSET_H, [NSC_TIME_ZONE_I] = NSC_TIME_ZONE_OFFSET_I, [NSC_TIME_ZONE_K] = NSC_TIME_ZONE_OFFSET_K, [NSC_TIME_ZONE_L] = NSC_TIME_ZONE_OFFSET_L, [NSC_TIME_ZONE_M] = NSC_TIME_ZONE_OFFSET_M }; /** * Array of timezone names. Order does not matter. */ static const char * const timezones[NSC_TIME_ZONE__COUNT] = { [NSC_TIME_ZONE_IDLE] = "IDLE", [NSC_TIME_ZONE_NZST] = "NZST", [NSC_TIME_ZONE_NZT] = "NZT", [NSC_TIME_ZONE_EAST] = "EAST", [NSC_TIME_ZONE_GST] = "GST", [NSC_TIME_ZONE_JST] = "JST", [NSC_TIME_ZONE_CCT] = "CCT", [NSC_TIME_ZONE_WAST] = "WAST", [NSC_TIME_ZONE_EET] = "EET", [NSC_TIME_ZONE_CET] = "CET", [NSC_TIME_ZONE_FWT] = "FWT", [NSC_TIME_ZONE_MET] = "MET", [NSC_TIME_ZONE_MEWT] = "MEWT", [NSC_TIME_ZONE_GMT] = "GMT", [NSC_TIME_ZONE_UTC] = "UTC", [NSC_TIME_ZONE_WET] = "WET", [NSC_TIME_ZONE_WAT] = "WAT", [NSC_TIME_ZONE_AST] = "AST", [NSC_TIME_ZONE_EST] = "EST", [NSC_TIME_ZONE_CST] = "CST", [NSC_TIME_ZONE_MST] = "MST", [NSC_TIME_ZONE_PST] = "PST", [NSC_TIME_ZONE_YST] = "YST", [NSC_TIME_ZONE_AHST] = "AHST", [NSC_TIME_ZONE_CAT] = "CAT", [NSC_TIME_ZONE_HST] = "HST", [NSC_TIME_ZONE_IDLW] = "IDLW", [NSC_TIME_ZONE_NZDT] = "NZDT", [NSC_TIME_ZONE_EADT] = "EADT", [NSC_TIME_ZONE_WADT] = "WADT", [NSC_TIME_ZONE_CEST] = "CEST", [NSC_TIME_ZONE_FST] = "FST", [NSC_TIME_ZONE_MEST] = "MEST", [NSC_TIME_ZONE_MESZ] = "MESZ", [NSC_TIME_ZONE_BST] = "BST", [NSC_TIME_ZONE_ADT] = "ADT", [NSC_TIME_ZONE_EDT] = "EDT", [NSC_TIME_ZONE_CDT] = "CDT", [NSC_TIME_ZONE_MDT] = "MDT", [NSC_TIME_ZONE_PDT] = "PDT", [NSC_TIME_ZONE_YDT] = "YDT", [NSC_TIME_ZONE_HDT] = "HDT", [NSC_TIME_ZONE_Y] = "Y", [NSC_TIME_ZONE_X] = "X", [NSC_TIME_ZONE_W] = "W", [NSC_TIME_ZONE_V] = "V", [NSC_TIME_ZONE_U] = "U", [NSC_TIME_ZONE_T] = "T", [NSC_TIME_ZONE_S] = "S", [NSC_TIME_ZONE_R] = "R", [NSC_TIME_ZONE_Q] = "Q", [NSC_TIME_ZONE_P] = "P", [NSC_TIME_ZONE_O] = "O", [NSC_TIME_ZONE_N] = "N", [NSC_TIME_ZONE_Z] = "Z", [NSC_TIME_ZONE_A] = "A", [NSC_TIME_ZONE_B] = "B", [NSC_TIME_ZONE_C] = "C", [NSC_TIME_ZONE_D] = "D", [NSC_TIME_ZONE_E] = "E", [NSC_TIME_ZONE_F] = "F", [NSC_TIME_ZONE_G] = "G", [NSC_TIME_ZONE_H] = "H", [NSC_TIME_ZONE_I] = "I", [NSC_TIME_ZONE_K] = "K", [NSC_TIME_ZONE_L] = "L", [NSC_TIME_ZONE_M] = "M" }; /** * Flags for tracking the components of a date that have been parsed. */ enum nsc_date_component_flags { NSC_COMPONENT_FLAGS_NONE = 0, NSC_COMPONENT_FLAGS_HAVE_YEARS = (1 << 0), NSC_COMPONENT_FLAGS_HAVE_MONTHS = (1 << 1), NSC_COMPONENT_FLAGS_HAVE_DAYS = (1 << 2), NSC_COMPONENT_FLAGS_HAVE_HOURS = (1 << 3), NSC_COMPONENT_FLAGS_HAVE_MINS = (1 << 4), NSC_COMPONENT_FLAGS_HAVE_SECS = (1 << 5), NSC_COMPONENT_FLAGS_HAVE_TIMEZONE = (1 << 6), NSC_COMPONENT_FLAGS_HAVE_WEEKDAY = (1 << 7), NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD = NSC_COMPONENT_FLAGS_HAVE_YEARS | NSC_COMPONENT_FLAGS_HAVE_MONTHS | NSC_COMPONENT_FLAGS_HAVE_DAYS, NSC_COMPONENT_FLAGS__HAVE_HHMMSS = NSC_COMPONENT_FLAGS_HAVE_HOURS | NSC_COMPONENT_FLAGS_HAVE_MINS | NSC_COMPONENT_FLAGS_HAVE_SECS, NSC_COMPONENT_FLAGS__HAVE_ALL = NSC_COMPONENT_FLAGS_HAVE_YEARS | NSC_COMPONENT_FLAGS_HAVE_MONTHS | NSC_COMPONENT_FLAGS_HAVE_DAYS | NSC_COMPONENT_FLAGS_HAVE_HOURS | NSC_COMPONENT_FLAGS_HAVE_MINS | NSC_COMPONENT_FLAGS_HAVE_SECS | NSC_COMPONENT_FLAGS_HAVE_TIMEZONE }; /** * Context for date parsing. */ struct nsc_date_parse_ctx { char prev; /**< Used for handling neumenrical timezone */ uint8_t secs; uint8_t mins; uint8_t hours; uint8_t day; uint8_t month; uint16_t years; int16_t timezone_offset_mins; }; /** * Helper for testing whether any of the flags in mask are set. * * \param[in] flags Flags to to check. * \param[in] mask The set of flags to check for in `flags`. * \return true iff any flags in `mask` are set in `flags`, else false. */ static bool flags_chk( enum nsc_date_component_flags flags, enum nsc_date_component_flags mask) { return flags & mask; } /** * Helper for testing whether all of the flags in mask are set. * * \param[in] flags Flags to to check. * \param[in] mask The set of flags to check for in `flags`. * \return true iff all flags in `mask` are set in `flags`, else false. */ static bool flags_chk_all( enum nsc_date_component_flags flags, enum nsc_date_component_flags mask) { return (flags & mask) == mask; } /** * Test for a weekday name in a string (case insensitive). * * \param[in] str String to parse a weekday name in. * \param[in] len Number of consecutive alphabetical characters. * \param[in,out] flags Flags indicating which date components have been * found in `str` already. If a weekday component * is found, the weekday flag is set. * \return true iff weekday component is found, else false. */ static inline bool time__parse_weekday( const char *str, size_t len, enum nsc_date_component_flags *flags) { const char * const *names = (len == 3) ? weekdays_short : weekdays_long; if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_WEEKDAY)) { return false; } for (uint32_t i = 0; i < NSC_TIME_WEEKDAY__COUNT; i++) { if (ascii_strings_count_equal_caseless(names[i], str) == len) { *flags |= NSC_COMPONENT_FLAGS_HAVE_WEEKDAY; return true; } } return false; } /** * Attempt to parse a month name in a string (case insensitive). * * \param[in] str String to parse a month name in. * \param[in] len Number of consecutive alphabetical characters. * \param[in,out] flags Flags indicating which date components have been * found in `str` already. If a month component * is found, the month flag is set. * \param[in,out] ctx Current date parsing context. If month component * is found, the context's month value is set. * \return true iff month name component is found, else false. */ static inline bool time__parse_month( const char *str, size_t len, enum nsc_date_component_flags *flags, struct nsc_date_parse_ctx *ctx) { if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_MONTHS)) { return false; } for (uint32_t i = 0; i < NSC_TIME_MONTH__COUNT; i++) { if (ascii_strings_count_equal_caseless(months[i], str) == len) { *flags |= NSC_COMPONENT_FLAGS_HAVE_MONTHS; ctx->month = i; return true; } } return false; } /** * Attempt to parse a timezone name in a string (case insensitive). * * \param[in] str String to parse a timezone name in. * \param[in] len Number of consecutive alphabetical characters. * \param[in,out] flags Flags indicating which date components have been * found in `str` already. If a timezone component * is found, the timezone flag is set. * \param[in,out] ctx Current date parsing context. If timezone component * is found, the context's timezone value is set. * \return true iff timezone name component is found, else false. */ static inline bool time__parse_timezone( const char *str, size_t len, enum nsc_date_component_flags *flags, struct nsc_date_parse_ctx *ctx) { if (flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_TIMEZONE)) { return false; } for (uint32_t i = 0; i < NSC_TIME_ZONE__COUNT; i++) { if (ascii_strings_count_equal_caseless( timezones[i], str) == len) { *flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE; ctx->timezone_offset_mins = timezone_mins[i]; return true; } } return false; } /** * Attempt to parse an "hh:mm:ss" time from a string. * * \param[in] str String to parse a time in. * \param[in,out] len The number of characters until the first non-digit. * Iff a time component is found, updated to the number * of comsumend characters. * \param[in,out] flags Flags indicating which date components have been * found in `str` already. If a time component * is found, the hours, mins and secs flags are set. * \param[in,out] ctx Current date parsing context. If time component * is found, the context's time values are set. * \return true iff time component is found, else false. */ static inline bool time__parse_hh_mm_ss( const char *str, size_t *len, enum nsc_date_component_flags *flags, struct nsc_date_parse_ctx *ctx) { size_t l; if (*len != 2 || flags_chk(*flags, NSC_COMPONENT_FLAGS__HAVE_HHMMSS)) { return false; } l = *len + ascii_count_digit_or_colon(str + *len); if (l == 8) { int h, m, s, count; count = sscanf(str, "%02d:%02d:%02d", &h, &m, &s); if (count == 3) { ctx->hours = h; ctx->mins = m; ctx->secs = s; *flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS; *len = l; return true; } } else if (l == 5) { int h, m, count; count = sscanf(str, "%02d:%02d", &h, &m); if (count == 2) { ctx->hours = h; ctx->mins = m; ctx->secs = 0; *flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS; *len = l; return true; } } return false; } /** * Attempt to parse a number from a date string. * * How the number is treated depends on various things: * * - its character length, * - its value, * - which date components have already been parsed * * \param[in] str String to parse a time in. * \param[in] len The number of characters until the first non-digit. * \param[in,out] flags Flags indicating which date components have been * found in `str` already. If any component is found, * their flags are set. * \param[in,out] ctx Current date parsing context. If any component * is found, the appropriate context values are set. * \return true iff a component is found, else false. */ static inline bool time__parse_number( const char *str, size_t len, enum nsc_date_component_flags *flags, struct nsc_date_parse_ctx *ctx) { int value; if (len != ascii_string_to_int(str, &value)) { return false; } switch (len) { case 8: if (!flags_chk(*flags, NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD)) { ctx->years = value / 10000; ctx->month = (value % 10000) / 100 - 1; ctx->day = value % 100 - 1; *flags |= NSC_COMPONENT_FLAGS__HAVE_YYYYMMDD; return true; } break; case 4: if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_TIMEZONE)) { if (ascii_is_sign(ctx->prev) && value <= 1400) { ctx->timezone_offset_mins = value / 100 * 60 + value % 100; if (ctx->prev == '+') { ctx->timezone_offset_mins *= -1; } *flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE; return true; } } if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_YEARS)) { ctx->years = value; *flags |= NSC_COMPONENT_FLAGS_HAVE_YEARS; return true; } break; case 2: case 1: if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_DAYS) && value > 0 && value <= 31) { ctx->day = value - 1; *flags |= NSC_COMPONENT_FLAGS_HAVE_DAYS; return true; } if (!flags_chk(*flags, NSC_COMPONENT_FLAGS_HAVE_YEARS)) { ctx->years = (value > 70) ? value + 1900 : value + 2000; *flags |= NSC_COMPONENT_FLAGS_HAVE_YEARS; return true; } break; default: break; } return false; } /** * Get number of leap days up until end of given year. * * \param[in] year Year to count leap years up to. * \return Number of leap days up to end of `year`. */ static inline int time__get_leap_days(int year) { return (year / 4) - (year / 100) + (year / 400); } /** * Helper to convert a date string context to a time_t. * * \param[in] ctx Current date parsing context. * \param[in] flags Flags indicating which date components have been set. * \param[out] time Returns the number of seconds since 1 Jan 1970 00:00 UTC. * \return NSERROR_OK on success, appropriate error otherwise. */ static nserror time__ctx_to_time_t( const struct nsc_date_parse_ctx *ctx, enum nsc_date_component_flags flags, time_t *time) { enum { NSC_MONTH_DAYS_JAN = 31, NSC_MONTH_DAYS_FEB = 28, /**< Leap years handled separatly */ NSC_MONTH_DAYS_MAR = 31, NSC_MONTH_DAYS_APR = 30, NSC_MONTH_DAYS_MAY = 31, NSC_MONTH_DAYS_JUN = 30, NSC_MONTH_DAYS_JUL = 31, NSC_MONTH_DAYS_AUG = 31, NSC_MONTH_DAYS_SEP = 30, NSC_MONTH_DAYS_OCT = 31, NSC_MONTH_DAYS_NOV = 30, NSC_MONTH_DAYS_DEC = 31 }; enum { NSC_MONTH_OFF_JAN = 0, NSC_MONTH_OFF_FEB = NSC_MONTH_OFF_JAN + NSC_MONTH_DAYS_JAN, NSC_MONTH_OFF_MAR = NSC_MONTH_OFF_FEB + NSC_MONTH_DAYS_FEB, NSC_MONTH_OFF_APR = NSC_MONTH_OFF_MAR + NSC_MONTH_DAYS_MAR, NSC_MONTH_OFF_MAY = NSC_MONTH_OFF_APR + NSC_MONTH_DAYS_APR, NSC_MONTH_OFF_JUN = NSC_MONTH_OFF_MAY + NSC_MONTH_DAYS_MAY, NSC_MONTH_OFF_JUL = NSC_MONTH_OFF_JUN + NSC_MONTH_DAYS_JUN, NSC_MONTH_OFF_AUG = NSC_MONTH_OFF_JUL + NSC_MONTH_DAYS_JUL, NSC_MONTH_OFF_SEP = NSC_MONTH_OFF_AUG + NSC_MONTH_DAYS_AUG, NSC_MONTH_OFF_OCT = NSC_MONTH_OFF_SEP + NSC_MONTH_DAYS_SEP, NSC_MONTH_OFF_NOV = NSC_MONTH_OFF_OCT + NSC_MONTH_DAYS_OCT, NSC_MONTH_OFF_DEC = NSC_MONTH_OFF_NOV + NSC_MONTH_DAYS_NOV }; static const uint16_t month_offsets[NSC_TIME_MONTH__COUNT] = { [NSC_TIME_MONTH_JAN] = NSC_MONTH_OFF_JAN, [NSC_TIME_MONTH_FEB] = NSC_MONTH_OFF_FEB, [NSC_TIME_MONTH_MAR] = NSC_MONTH_OFF_MAR, [NSC_TIME_MONTH_APR] = NSC_MONTH_OFF_APR, [NSC_TIME_MONTH_MAY] = NSC_MONTH_OFF_MAY, [NSC_TIME_MONTH_JUN] = NSC_MONTH_OFF_JUN, [NSC_TIME_MONTH_JUL] = NSC_MONTH_OFF_JUL, [NSC_TIME_MONTH_AUG] = NSC_MONTH_OFF_AUG, [NSC_TIME_MONTH_SEP] = NSC_MONTH_OFF_SEP, [NSC_TIME_MONTH_OCT] = NSC_MONTH_OFF_OCT, [NSC_TIME_MONTH_NOV] = NSC_MONTH_OFF_NOV, [NSC_TIME_MONTH_DEC] = NSC_MONTH_OFF_DEC }; int year_days = (ctx->years - 1970) * 365; int month_days = month_offsets[ctx->month]; int year = (ctx->month < NSC_TIME_MONTH_FEB) ? ctx->years - 1 : ctx->years; int leap_days = time__get_leap_days(year) - time__get_leap_days(1969); int total_days = year_days + month_days + ctx->day + leap_days; int mins = (int)ctx->mins + (int)ctx->timezone_offset_mins; *time = (((((time_t)(total_days)) * 24) + ctx->hours) * 60 + mins) * 60 + ctx->secs; return NSERROR_OK; } /** * Parse a date string to a `time_t`. * * \param[in] str String to parse. * \param[out] time Returns the number of seconds since 1 Jan 1970 00:00 UTC. * \return `NSERROR_OK` on success, else * `NSERROR_INVALID` if the string parsing failed, * appropriate error otherwise. */ static nserror time__get_date(const char *str, time_t *time) { enum nsc_date_component_flags flags = NSC_COMPONENT_FLAGS_NONE; struct nsc_date_parse_ctx ctx = { .prev = '\0', .secs = 0, .mins = 0, .hours = 0, .day = 0, .month = 0, .years = 0, .timezone_offset_mins = 0 }; if (str == NULL || time == NULL) { return NSERROR_BAD_PARAMETER; } /* Parse */ while (*str != '\0' && !flags_chk_all(flags, NSC_COMPONENT_FLAGS__HAVE_ALL)) { size_t len = 1; if (ascii_is_alpha(*str)) { len += ascii_count_alpha(str + 1); if (!time__parse_weekday(str, len, &flags) && !time__parse_month(str, len, &flags, &ctx) && !time__parse_timezone(str, len, &flags, &ctx)) { return NSERROR_INVALID; } } else if (ascii_is_digit(*str)) { len += ascii_count_digit(str + 1); if (!time__parse_hh_mm_ss(str, &len, &flags, &ctx) && !time__parse_number(str, len, &flags, &ctx)) { return NSERROR_INVALID; } } ctx.prev = *str; str += len; } /* The initial values of 0 are used if hours, mins, secs, and timezone * are not found */ flags |= NSC_COMPONENT_FLAGS__HAVE_HHMMSS; flags |= NSC_COMPONENT_FLAGS_HAVE_TIMEZONE; /* Validate */ if (!flags_chk_all(flags, NSC_COMPONENT_FLAGS__HAVE_ALL)) { return NSERROR_INVALID; } if (ctx.secs > 60 || ctx.mins > 59 || ctx.hours > 23 || ctx.day > 31 || ctx.month > 11) { return NSERROR_INVALID; } /* Convert */ return time__ctx_to_time_t(&ctx, flags, time); } /* exported function documented in utils/time.h */ nserror nsc_strntimet(const char *str, size_t size, time_t *timep) { return time__get_date(str, timep); } # else /* exported function documented in utils/time.h */ nserror nsc_strntimet(const char *str, size_t size, time_t *timep) { time_t result; if (str == NULL || timep == NULL) { return NSERROR_BAD_PARAMETER; } result = curl_getdate(str, NULL); if (result == -1) { return NSERROR_INVALID; } *timep = result; return NSERROR_OK; } #endif