From f23405f667e7514cff93ebc83cb6d1f7ab9005c0 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Sun, 7 Aug 2016 17:31:28 +0100 Subject: Time: Add date string to time_t parser. --- utils/time.c | 813 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 810 insertions(+), 3 deletions(-) (limited to 'utils') diff --git a/utils/time.c b/utils/time.c index 6d53b16e1..14f4004b9 100644 --- a/utils/time.c +++ b/utils/time.c @@ -25,17 +25,19 @@ * \brief Implementation of time operations. */ -#include #include -#include +#include +#include +#ifdef WITH_CURL #include +#endif +#include "utils/ascii.h" #include "utils/errors.h" #include "utils/time.h" - /** * Weekdays * @@ -181,11 +183,814 @@ nserror nsc_snptimet(const char *str, size_t size, time_t *timep) } +#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` array. + * So there is value in putting the most common timezones first. + */ +enum nsc_time_zones { + 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_GMT, + 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) { @@ -196,3 +1001,5 @@ nserror nsc_strntimet(const char *str, size_t size, time_t *timep) return NSERROR_OK; } + +#endif -- cgit v1.2.3