diff --git a/src/box/sql/mem.c b/src/box/sql/mem.c index 35e52fe6867ab4df4ad8e2ba40c5fc8d6df5b618..2d369d704fa9219ad49c6e3c3abb3f786aa862d5 100644 --- a/src/box/sql/mem.c +++ b/src/box/sql/mem.c @@ -864,7 +864,7 @@ str_to_datetime(struct Mem *mem) { assert(mem->type == MEM_TYPE_STR); struct datetime dt; - if (datetime_parse_full(&dt, mem->z, mem->n, 0) <= 0) + if (datetime_parse_full(&dt, mem->z, mem->n, NULL, 0) <= 0) return -1; mem_set_datetime(mem, &dt); return 0; diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c index 07f9988a1f219759f66fd9b734a7f0ecbfcf4767..62fd5da0fa1b7e00d41bc4614eb62f7c9d92358a 100644 --- a/src/lib/core/datetime.c +++ b/src/lib/core/datetime.c @@ -80,10 +80,7 @@ datetime_strftime(const struct datetime *date, char *buf, size_t len, return tnt_strftime(buf, len, fmt, &tm); } -/** - * Create datetime structure using given tnt_tm fieldsÑŽ - */ -static bool +bool tm_to_datetime(struct tnt_tm *tm, struct datetime *date) { assert(tm != NULL); @@ -224,9 +221,42 @@ datetime_to_string(const struct datetime *date, char *buf, ssize_t len) return sz; } +static inline int64_t +dt_epoch(dt_t dt) +{ + return ((int64_t)dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY; +} + +/** Common timezone suffix parser */ +static inline ssize_t +parse_tz_suffix(const char *str, size_t len, time_t base, + int16_t *tzindex, int32_t *offset) +{ + /* 1st attempt: decode as MSK */ + const struct date_time_zone *zone; + long gmtoff = 0; + ssize_t l = timezone_epoch_lookup(str, len, base, &zone, &gmtoff); + if (l < 0) + return l; + if (l > 0) { + assert(zone != NULL); + *offset = gmtoff / 60; + *tzindex = timezone_index(zone); + assert(l <= (ssize_t)len); + return l; + } + + /* 2nd attempt: decode as +03:00 */ + *tzindex = 0; + l = dt_parse_iso_zone_lenient(str, len, offset); + assert(l <= (ssize_t)len); + + return l; +} + ssize_t datetime_parse_full(struct datetime *date, const char *str, size_t len, - int32_t offset) + const char *tzsuffix, int32_t offset) { size_t n; dt_t dt; @@ -260,6 +290,19 @@ datetime_parse_full(struct datetime *date, const char *str, size_t len, if (len <= 0) goto exit; + /* now we have parsed enough of date literal, and we are + * ready to consume timezone suffix, if overridden + */ + time_t base = dt_epoch(dt) + sec_of_day - offset * 60; + ssize_t l; + if (tzsuffix != NULL) { + l = parse_tz_suffix(tzsuffix, strlen(tzsuffix), base, + &tzindex, &offset); + if (l < 0) + return l; + goto exit; + } + if (*str == ' ') { str++; len--; @@ -267,30 +310,13 @@ datetime_parse_full(struct datetime *date, const char *str, size_t len, if (len <= 0) goto exit; - /* 1st attempt: decode as MSK */ - const struct date_time_zone *zone; - ssize_t l = timezone_lookup(str, len, &zone); + l = parse_tz_suffix(str, len, base, &tzindex, &offset); if (l < 0) return l; - if (l > 0) { - assert(zone != NULL); - assert(((TZ_AMBIGUOUS | TZ_NYI) & timezone_flags(zone)) == 0); - offset = timezone_offset(zone); - tzindex = timezone_index(zone); - str += l; - len -= l; - if (len <= 0) - goto exit; - } - - /* 2nd attempt: decode as +03:00 */ - n = dt_parse_iso_zone_lenient(str, len, &offset); - str += n; + str += l; exit: - date->epoch = - ((int64_t)dt_rdn(dt) - DT_EPOCH_1970_OFFSET) * SECS_PER_DAY + - sec_of_day - offset * 60; + date->epoch = dt_epoch(dt) + sec_of_day - offset * 60; date->nsec = nanosecond; date->tzoffset = offset; date->tzindex = tzindex; @@ -299,19 +325,15 @@ datetime_parse_full(struct datetime *date, const char *str, size_t len, } ssize_t -datetime_parse_tz(const char *str, size_t len, int16_t *tzoffset, +datetime_parse_tz(const char *str, size_t len, time_t base, int16_t *tzoffset, int16_t *tzindex) { - const struct date_time_zone *zone; - ssize_t l = timezone_lookup(str, len, &zone); + int32_t offset = 0; + ssize_t l = parse_tz_suffix(str, len, base, tzindex, &offset); if (l <= 0) return l; - - assert(l <= (ssize_t)len); - assert(zone != NULL); - assert(((TZ_AMBIGUOUS | TZ_NYI) & timezone_flags(zone)) == 0); - *tzoffset = timezone_offset(zone); - *tzindex = timezone_index(zone); + assert(offset <= INT16_MAX); + *tzoffset = offset; return l; } diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h index 9b7a8a087d010c879f1fbf28b090a20643e12064..ca5dbee1b44b92d56671814fdb1269e719ca94d2 100644 --- a/src/lib/core/datetime.h +++ b/src/lib/core/datetime.h @@ -33,7 +33,7 @@ struct tnt_tm; #endif /** Required size of datetime_to_string string buffer */ -#define DT_TO_STRING_BUFSIZE 48 +#define DT_TO_STRING_BUFSIZE 64 /** * Required buffer size to store the string representation of any possible @@ -158,15 +158,22 @@ datetime_ev_now(struct datetime *now); void datetime_to_tm(const struct datetime *date, struct tnt_tm *tm); +/** + * Create @sa datetime structure using given @sa tnt_tm fields. + */ +bool +tm_to_datetime(struct tnt_tm *tm, struct datetime *date); + /** * Parse datetime text in ISO-8601 given format, and construct output * datetime value * @param date output datetime value * @param str input text in relaxed ISO-8601 format (0-terminated) * @param len length of str buffer - * @param offset timezone offset, if you need to interpret - * date/time value as local. Pass 0 if you need to interpret it as UTC, - * or apply offset from string. + * @param tzsuffix timezone override, if you need to interpret + * date/time value as local. Pass NULL if you need to interpret it + * as UTC, or you provide meaningful suffix in the literal to be + * parsed. * @retval Upon successful completion returns length of accepted * prefix substring. It's ok if there is some unaccepted trailer. * Returns 0 only if text is not recognizable as date/time string. @@ -175,7 +182,7 @@ datetime_to_tm(const struct datetime *date, struct tnt_tm *tm); */ ssize_t datetime_parse_full(struct datetime *date, const char *str, size_t len, - int32_t offset); + const char *tzsuffix, int32_t offset); /** Parse timezone suffix * @param str input text in relaxed ISO-8601 format (0-terminated) @@ -188,7 +195,7 @@ datetime_parse_full(struct datetime *date, const char *str, size_t len, * @sa datetime_parse_full() */ ssize_t -datetime_parse_tz(const char *str, size_t len, int16_t *tzoffset, +datetime_parse_tz(const char *str, size_t len, time_t base, int16_t *tzoffset, int16_t *tzindex); /** diff --git a/src/lib/tzcode/localtime.c b/src/lib/tzcode/localtime.c index 7f66f449b4d8c16fa5bdb486c910435a637f18cb..7e654d812ea5affe2d9526973fb40f0c18ed92b1 100644 --- a/src/lib/tzcode/localtime.c +++ b/src/lib/tzcode/localtime.c @@ -1507,7 +1507,7 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, #if NETBSD_INSPIRED struct tnt_tm * -localtime_rz(struct state *sp, time_t const *timep, struct tnt_tm *tmp) +tnt_localtime_rz(struct state *sp, time_t const *timep, struct tnt_tm *tmp) { return localsub(sp, timep, 0, tmp); } diff --git a/src/lib/tzcode/strptime.c b/src/lib/tzcode/strptime.c index ae9ec6a7f4d2d68e95e448bd7eef8379b534560c..a8ac38231f12e054bd81ba5117d37ef729f8588d 100644 --- a/src/lib/tzcode/strptime.c +++ b/src/lib/tzcode/strptime.c @@ -551,13 +551,10 @@ tnt_strptime(const char *__restrict buf, const char *__restrict fmt, zonestr[cp - buf] = '\0'; const struct date_time_zone *zone; - size_t n = timezone_lookup(zonestr, cp - buf, - &zone); + size_t n = timezone_tm_lookup(zonestr, cp - buf, + &zone, tm); if (n <= 0) return NULL; - tm->tm_gmtoff = timezone_offset(zone); - tm->tm_tzindex = timezone_index(zone); - tm->tm_isdst = false; buf += cp - buf; } diff --git a/src/lib/tzcode/timezone.c b/src/lib/tzcode/timezone.c index 94b73b63faf40928c075ed8402b4a2289619c233..c4cd0a5a2dc8e90e580cce4f45076ad608a8e801 100644 --- a/src/lib/tzcode/timezone.c +++ b/src/lib/tzcode/timezone.c @@ -8,8 +8,59 @@ #include <stddef.h> #include "datetime.h" #include "timezone.h" +#include "tzcode.h" #include "trivia/util.h" +/** + * Array of zone descriptors, placed in their natural id order, used for + * translation from zone id to unique zone name and their attributes. + */ +static struct date_time_zone zones_raw[] = { +#define ZONE_ABBREV(id, offset, name, flags) {name, id, flags, offset}, +#define ZONE_UNIQUE(id, name) {name, id, TZ_OLSON, 0}, +#define ZONE_ALIAS(id, alias, name) {alias, id, TZ_OLSON|TZ_ALIAS, 0}, +#include "timezones.h" +#undef ZONE_ALIAS +#undef ZONE_UNIQUE +#undef ZONE_ABBREV +}; + +#define ZONES_MAX 1024 +static_assert(ZONES_MAX >= lengthof(zones_raw), "please increase ZONES_MAX"); + +/** + * Sorted array of zone descriptors, whether it's abbreviation, full + * zone name or [backward-compatible] link name. + */ +static struct date_time_zone zones_sorted[lengthof(zones_raw)]; +static struct date_time_zone zones_unsorted[ZONES_MAX]; + +static int +compare_zones(const void *a, const void *b) +{ + return strcasecmp(((const struct date_time_zone *) a)->name, + ((const struct date_time_zone*) b)->name); +} + +static void __attribute__((constructor)) +sort_array(void) +{ + size_t i; + /* 1st save zones in id order for stringization */ + for (i = 0; i < lengthof(zones_raw); i++) { + size_t id = zones_raw[i].id; + assert(id < lengthof(zones_unsorted)); + /* save to unsorted array if it's not an alias */ + if ((zones_raw[i].flags & TZ_ALIAS) == 0) + zones_unsorted[id] = zones_raw[i]; + } + /* 2nd copy raw to be sorted and prepare them for bsearch */ + assert(sizeof(zones_sorted) == sizeof(zones_raw)); + memcpy(zones_sorted, zones_raw, sizeof(zones_raw)); + qsort(zones_sorted, lengthof(zones_sorted), sizeof(zones_sorted[0]), + compare_zones); +} + int16_t timezone_offset(const struct date_time_zone *zone) { @@ -28,52 +79,11 @@ timezone_flags(const struct date_time_zone *zone) return zone->flags; } -static const char * zone_names[] = { -#define ZONE_ABBREV(id, offset, name, flags) [id] = name, -#define ZONE_UNIQUE(id, name) [id] = name, -#define ZONE_ALIAS(id, alias, name) -#include "timezones.h" -#undef ZONE_ALIAS -#undef ZONE_UNIQUE -#undef ZONE_ABBREV -}; - const char* timezone_name(int64_t index) { - assert((size_t)index < lengthof(zone_names)); - return zone_names[index]; -} - -static struct date_time_zone zone_abbrevs[] = { -#define ZONE_ABBREV(id, offset, name, flags) { name, id, flags, offset }, -#define ZONE_UNIQUE(id, name) { name, id, TZ_OLSON, 0 }, -#define ZONE_ALIAS(id, alias, name) { alias, id, TZ_OLSON|TZ_ALIAS, 0 }, -#include "timezones.h" -#undef ZONE_ALIAS -#undef ZONE_UNIQUE -#undef ZONE_ABBREV -}; - -static int -compare_abbrevs(const void *a, const void *b) -{ - return strcasecmp(((const struct date_time_zone *) a)->name, - ((const struct date_time_zone*) b)->name); -} - -static inline struct date_time_zone * -sort_abbrevs_singleton(void) -{ - static struct date_time_zone *sorted = NULL; - if (sorted != NULL) - return sorted; - - qsort(zone_abbrevs, lengthof(zone_abbrevs), - sizeof(struct date_time_zone), compare_abbrevs); - sorted = zone_abbrevs; - - return sorted; + assert((size_t)index < lengthof(zones_unsorted)); + return zones_unsorted[index].name; } /** @@ -99,18 +109,18 @@ char_span_alpha(const char *src, size_t len) return n; } -ssize_t -timezone_lookup(const char *str, size_t len, const struct date_time_zone **zone) +static inline ssize_t +timezone_raw_lookup(const char *str, size_t len, + const struct date_time_zone **zone) { len = char_span_alpha(str, len); if (len == 0) return 0; - struct date_time_zone *sorted = sort_abbrevs_singleton(); struct date_time_zone wrap = {.name = str}; struct date_time_zone *found = (struct date_time_zone *) - bsearch(&wrap, sorted, lengthof(zone_abbrevs), - sizeof(struct date_time_zone), compare_abbrevs); + bsearch(&wrap, zones_sorted, lengthof(zones_sorted), + sizeof(zones_sorted[0]), compare_zones); if (found != NULL) { /* lua assumes that single bit is set, not both */ @@ -123,3 +133,69 @@ timezone_lookup(const char *str, size_t len, const struct date_time_zone **zone) } return -1; } + +ssize_t +timezone_tm_lookup(const char *str, size_t len, + const struct date_time_zone **zone, + struct tnt_tm *tm) +{ + ssize_t rc = timezone_raw_lookup(str, len, zone); + if (rc <= 0) + return rc; + + const struct date_time_zone *found = *zone; + if ((found->flags & TZ_OLSON) == 0) { + tm->tm_gmtoff = found->offset * 60; + tm->tm_tzindex = found->id; + tm->tm_isdst = false; + return rc; + } + timezone_t tz = tzalloc(str); // FIXME - cache loaded + if (tz == NULL) + return 0; + struct datetime date = {.epoch = 0}; + if (tm_to_datetime(tm, &date) == false) + goto exit_0; + time_t epoch = (int64_t)date.epoch; + struct tnt_tm * result = tnt_localtime_rz(tz, &epoch, tm); + if (result == NULL) + goto exit_0; + tzfree(tz); + return rc; +exit_0: + if (tz != NULL) + tzfree(tz); + return 0; +} + +ssize_t +timezone_epoch_lookup(const char *str, size_t len, time_t base, + const struct date_time_zone **zone, + long *gmtoff) +{ + ssize_t rc = timezone_raw_lookup(str, len, zone); + if (rc <= 0) + return rc; + + const struct date_time_zone *found = *zone; + if ((found->flags & TZ_OLSON) == 0) { + assert((TZ_ERROR_MASK & found->flags) == 0); + *gmtoff = found->offset * 60; + return rc; + } + timezone_t tz = NULL; + tz = tzalloc(str); // FIXME - cache loaded + if (tz == NULL) + return 0; + struct tnt_tm tm = {.tm_epoch = 0}; + struct tnt_tm * result = tnt_localtime_rz(tz, &base, &tm); + if (result == NULL) + goto exit_0; + *gmtoff = result->tm_gmtoff; + tzfree(tz); + return rc; +exit_0: + if (tz != NULL) + tzfree(tz); + return 0; +} diff --git a/src/lib/tzcode/timezone.h b/src/lib/tzcode/timezone.h index b2b984e3523efa871d209f7a206c94ae65dc6df3..b8eaf8e3d9557d1429847b11eba1f536449e0327 100644 --- a/src/lib/tzcode/timezone.h +++ b/src/lib/tzcode/timezone.h @@ -11,6 +11,8 @@ extern "C" { #endif +struct tnt_tm; + enum { TZ_UTC = 0x01, TZ_RFC = 0x02, @@ -19,6 +21,8 @@ enum { TZ_NYI = 0x10, TZ_OLSON = 0x20, TZ_ALIAS = 0x40, + + TZ_ERROR_MASK = TZ_AMBIGUOUS | TZ_NYI, }; /** @@ -40,14 +44,29 @@ struct date_time_zone { * with attributes of this symbol * @param[in] s input string to parse * @param[in] len length of input string - * @param[out] zone return zone structure, if found + * @param[in] base use base date for timezone parameters resolution + * @param[out] zone return found zone structure + * @param[out] gmtoff return gmtoff for the found zone * @retval positive value - length of accepted string, * negative value - string looks legit, but is unknown or * unsupported at the moment and should generate exception, * 0 - means string is bogus, and should be ignored. */ ssize_t -timezone_lookup(const char *s, size_t len, const struct date_time_zone **zone); +timezone_epoch_lookup(const char *s, size_t len, time_t base, + const struct date_time_zone **zone, + long *gmtoff); + +/** + * Parse timezone string like @sa timezone_epoch_lookup, but use + * @struct tnt_tm for passing base date and returning resolved + * tm_gmtoff or tm_isdst. + */ +ssize_t +timezone_tm_lookup(const char *str, size_t len, + const struct date_time_zone **zone, + struct tnt_tm *tm); + /** Return offset in minutes for given zone */ int16_t timezone_offset(const struct date_time_zone *zone); diff --git a/src/lib/tzcode/tzcode.h b/src/lib/tzcode/tzcode.h index 8e752c6325414cc7b6b87a337604ab31f156ee9d..6a9c3146d405de48a41909eed567b8b3d7b6d92b 100644 --- a/src/lib/tzcode/tzcode.h +++ b/src/lib/tzcode/tzcode.h @@ -73,6 +73,9 @@ timezone_t tzalloc(const char *); /** Free loaded timezone definition */ void tzfree(timezone_t); +struct tnt_tm * +tnt_localtime_rz(struct state *sp, time_t const *timep, struct tnt_tm *tmp); + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua index ae10b56cf6a5a8957aeb0b63b05092d15cd51a82..f5c53932de39c5107b90442526490c96be59f89b 100644 --- a/src/lua/datetime.lua +++ b/src/lua/datetime.lua @@ -68,9 +68,10 @@ size_t tnt_datetime_to_string(const struct datetime * date, char *buf, size_t tnt_datetime_strftime(const struct datetime *date, char *buf, uint32_t len, const char *fmt); ssize_t tnt_datetime_parse_full(struct datetime *date, const char *str, - size_t len, int32_t offset); -ssize_t tnt_datetime_parse_tz(const char *str, size_t len, int16_t *tzoffset, - int16_t *tzindex); + size_t len, const char *tzsuffix, + int32_t offset); +ssize_t tnt_datetime_parse_tz(const char *str, size_t len, time_t base, + int16_t *tzoffset, int16_t *tzindex); size_t tnt_datetime_strptime(struct datetime *date, const char *buf, const char *fmt); void tnt_datetime_now(struct datetime *now); @@ -109,7 +110,7 @@ local math_floor = math.floor local DAYS_EPOCH_OFFSET = 719163 local SECS_PER_DAY = 86400 local SECS_EPOCH_OFFSET = DAYS_EPOCH_OFFSET * SECS_PER_DAY -local TOSTRING_BUFSIZE = 48 +local TOSTRING_BUFSIZE = 64 local IVAL_TOSTRING_BUFSIZE = 96 local STRFTIME_BUFSIZE = 128 @@ -441,16 +442,20 @@ local function parse_tzoffset(str) return offset[0] end +local function epoch_from_dt(dt) + return (dt - DAYS_EPOCH_OFFSET) * SECS_PER_DAY +end + --[[ Parse timezone name similar way as datetime_parse_full parse full literal. ]] -local function parse_tzname(tzname) +local function parse_tzname(base_epoch, tzname) check_str(tzname, 'parse_tzname()') local ptzindex = date_int16_stash_take() local ptzoffset = date_int16_stash_take() - local len = builtin.tnt_datetime_parse_tz(tzname, #tzname, ptzoffset, - ptzindex) + local len = builtin.tnt_datetime_parse_tz(tzname, #tzname, base_epoch, + ptzoffset, ptzindex) if len > 0 then local tzoffset, tzindex = ptzoffset[0], ptzindex[0] date_int16_stash_put(ptzoffset) @@ -486,8 +491,7 @@ local function datetime_new_dt(dt, secs, nanosecs, offset, tzindex) nanosecs = nanosecs or 0 offset = offset or 0 tzindex = tzindex or 0 - local epoch = (dt - DAYS_EPOCH_OFFSET) * SECS_PER_DAY - return datetime_new_raw(epoch + secs - offset * 60, nanosecs, + return datetime_new_raw(epoch_from_dt(dt) + secs - offset * 60, nanosecs, offset, tzindex) end @@ -593,12 +597,6 @@ local function datetime_new(obj) check_range(offset, -720, 840, 'tzoffset') end - local tzindex = 0 - local tzname = obj.tz - if tzname ~= nil then - offset, tzindex = parse_tzname(tzname) - end - -- .year, .month, .day if ymd then y = y or 1970 @@ -616,6 +614,12 @@ local function datetime_new(obj) dt = dt_from_ymd_checked(y, M, d) end + local tzindex = 0 + local tzname = obj.tz + if tzname ~= nil then + offset, tzindex = parse_tzname(epoch_from_dt(dt), tzname) + end + -- .hour, .minute, .second local secs = 0 if hms then @@ -841,12 +845,11 @@ end date [T] time [ ] time_zone Returns constructed datetime object and length of accepted string. ]] -local function datetime_parse_full(str, tzoffset, tzindex) +local function datetime_parse_full(str, tzname, offset) check_str(str, 'datetime.parse()') local date = ffi.new(datetime_t) - local len = builtin.tnt_datetime_parse_full(date, str, #str, tzoffset) + local len = builtin.tnt_datetime_parse_full(date, str, #str, tzname, offset) if len > 0 then - date.tzindex = tzindex and tzindex or date.tzindex return date, tonumber(len) elseif len == -builtin.TZ_NYI then error(("could not parse '%s' - nyi timezone"):format(str)) @@ -873,26 +876,30 @@ end local function datetime_parse_from(str, obj) check_str(str, 'datetime.parse()') local fmt = '' - local offset - local tzindex + local tzoffset + local tzname if obj ~= nil then check_table(obj, 'datetime.parse()') fmt = obj.format - offset = obj.tzoffset + tzoffset = obj.tzoffset + tzname = obj.tz end check_str_or_nil(fmt, 'datetime.parse()') - if offset ~= nil then - offset = get_timezone(offset, 'tzoffset') + local offset = 0 + if tzoffset ~= nil then + offset = get_timezone(tzoffset, 'tzoffset') check_range(offset, -720, 840, 'tzoffset') end - if obj and obj.tz ~= nil then - offset, tzindex = parse_tzname(obj.tz) + + if tzname ~= nil then + check_str(tzname, 'datetime.parse()') end if not fmt or fmt == '' or fmt == 'iso8601' or fmt == 'rfc3339' then - return datetime_parse_full(str, offset or 0, tzindex) + -- Effect of .tz overrides .tzoffset + return datetime_parse_full(str, tzname, offset) else return datetime_parse_format(str, fmt) end @@ -1057,9 +1064,6 @@ local function datetime_set(self, obj) offset = offset or self.tzoffset local tzname = obj.tz - if tzname ~= nil then - offset, self.tzindex = parse_tzname(tzname) - end local ts = obj.timestamp if ts ~= nil then @@ -1081,6 +1085,9 @@ local function datetime_set(self, obj) 'if nsec, usec, or msecs provided', 2) end + if tzname ~= nil then + offset, self.tzindex = parse_tzname(sec_int, tzname) + end self.epoch = utc_secs(sec_int, offset) self.nsec = nsec self.tzoffset = offset @@ -1099,6 +1106,10 @@ local function datetime_set(self, obj) datetime_ymd_update(self, y, M, d) end + if tzname ~= nil then + offset, self.tzindex = parse_tzname(self.epoch, tzname) + end + -- .hour, .minute, .second if hms then datetime_hms_update(self, h or h0, m or m0, sec or sec0) diff --git a/src/lua/tnt_datetime.c b/src/lua/tnt_datetime.c index 2b0e32702907febb8d10f11a17006a758f5b1480..f243d8e3c5ee62764523e39e4f64741c78017085 100644 --- a/src/lua/tnt_datetime.c +++ b/src/lua/tnt_datetime.c @@ -35,16 +35,16 @@ tnt_datetime_to_string(const struct datetime *date, char *buf, ssize_t len) ssize_t tnt_datetime_parse_full(struct datetime *date, const char *str, size_t len, - int32_t offset) + const char *tzsuffix, int32_t offset) { - return datetime_parse_full(date, str, len, offset); + return datetime_parse_full(date, str, len, tzsuffix, offset); } ssize_t -tnt_datetime_parse_tz(const char *str, size_t len, int16_t *tzoffset, - int16_t *tzindex) +tnt_datetime_parse_tz(const char *str, size_t len, time_t base_date, + int16_t *tzoffset, int16_t *tzindex) { - return datetime_parse_tz(str, len, tzoffset, tzindex); + return datetime_parse_tz(str, len, base_date, tzoffset, tzindex); } struct datetime * diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua index 48e4e8f7dd3b56dc8c909b929c7d6f9c4e5f301f..08988cf84c126adafc84f8683bf0e3bf7ef7540b 100755 --- a/test/app-tap/datetime.test.lua +++ b/test/app-tap/datetime.test.lua @@ -552,7 +552,7 @@ test:test("Check parsing of dates with invalid attributes", function(test) end) test:test("Parsing of timezone abbrevs", function(test) - test:plan(195) + test:plan(220) local zone_abbrevs = { -- military A = 1*60, B = 2*60, C = 3*60, @@ -574,6 +574,13 @@ test:test("Parsing of timezone abbrevs", function(test) AMDT = 5 * 60, BDST = 1 * 60, IRKT = 8 * 60, KST = 9 * 60, PDT = -7 * 60, WET = 0 * 60, HOVDST = 8 * 60, CHODST = 9 * 60, + + -- Olson + ['Europe/Moscow'] = 180, + ['Africa/Abidjan'] = 0, + ['America/Argentina/Buenos_Aires'] = -180, + ['Asia/Krasnoyarsk'] = 420, + ['Pacific/Fiji'] = 720, } local exp_pattern = '^2020%-02%-10T00:00' local base_date = '2020-02-10T0000 ' @@ -590,7 +597,7 @@ test:test("Parsing of timezone abbrevs", function(test) end) test:test("Parsing of timezone names (tzindex)", function(test) - test:plan(273) + test:plan(308) local zone_abbrevs = { -- military A = 1, B = 2, C = 3, @@ -613,6 +620,13 @@ test:test("Parsing of timezone names (tzindex)", function(test) AMDT = 336, BDST = 344, IRKT = 409, KST = 226, PDT = 264, WET = 314, HOVDST = 664, CHODST = 656, + + -- Olson + ['Europe/Moscow'] = 947, + ['Africa/Abidjan'] = 672, + ['America/Argentina/Buenos_Aires'] = 694, + ['Asia/Krasnoyarsk'] = 861, + ['Pacific/Fiji'] = 984, } local exp_pattern = '^2020%-02%-10T00:00' local base_date = '2020-02-10T0000 ' @@ -620,6 +634,7 @@ test:test("Parsing of timezone names (tzindex)", function(test) for zone, index in pairs(zone_abbrevs) do local date_text = base_date .. zone local date, len = date.parse(date_text) + print(zone, index) test:isnt(date, nil, 'parse ' .. zone) test:is(date.tzindex, index, 'expected tzindex') test:is(date.tz, zone, 'expected timezone name') @@ -636,27 +651,18 @@ local function error_ambiguous(s) return ("could not parse '%s' - ambiguous timezone"):format(s) end -local function error_nyi(s) - return ("could not parse '%s' - nyi timezone"):format(s) -end - local function error_generic(s) return ("could not parse '%s'"):format(s) end test:test("Parsing of timezone names (errors)", function(test) - test:plan(13) + test:plan(9) local zones_arratic = { -- ambiguous AT = error_ambiguous, BT = error_ambiguous, ACT = error_ambiguous, BST = error_ambiguous, GST = error_ambiguous, WAT = error_ambiguous, AZOST = error_ambiguous, - -- not yet implemented - ['Africa/Abidjan'] = error_nyi, - ['America/Argentina/Buenos_Aires'] = error_nyi, - ['Asia/Krasnoyarsk'] = error_nyi, - ['Pacific/Fiji'] = error_nyi, -- generic errors ['XXX'] = error_generic, ['A-_'] = error_generic, diff --git a/test/fuzz/datetime_parse_full_fuzzer.c b/test/fuzz/datetime_parse_full_fuzzer.c index 0c6bbd7006e6a7f0e55894c9d93930d136109db3..de4d7a61f7c818e6de1b15afc5b176531ac5c468 100644 --- a/test/fuzz/datetime_parse_full_fuzzer.c +++ b/test/fuzz/datetime_parse_full_fuzzer.c @@ -15,7 +15,7 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) memcpy(buf, data, size); buf[size] = '\0'; struct datetime date_expected; - datetime_parse_full(&date_expected, buf, size, 0); + datetime_parse_full(&date_expected, buf, size, NULL, 0); free(buf); return 0; diff --git a/test/unit/datetime.c b/test/unit/datetime.c index 684f8ef71b518e61fd8d51703a763b6ee9e7c4a7..1dd7c59ac95fe6ef36448ed87d24943076ad5922 100644 --- a/test/unit/datetime.c +++ b/test/unit/datetime.c @@ -106,12 +106,13 @@ datetime_test(void) struct datetime date_expected; plan(497); - datetime_parse_full(&date_expected, sample, sizeof(sample) - 1, 0); + datetime_parse_full(&date_expected, sample, sizeof(sample) - 1, + NULL, 0); for (index = 0; index < lengthof(tests); index++) { struct datetime date; size_t len = datetime_parse_full(&date, tests[index].str, - tests[index].len, 0); + tests[index].len, NULL, 0); is(len > 0, true, "correct parse_datetime return value " "for '%s'", tests[index].str); is(date.epoch, date_expected.epoch, @@ -133,7 +134,7 @@ datetime_test(void) is(date.epoch, date_strp.epoch, "reversible seconds via datetime_strptime for '%s'", buff); struct datetime date_parsed; - len = datetime_parse_full(&date_parsed, buff, len, 0); + len = datetime_parse_full(&date_parsed, buff, len, NULL, 0); is(len > 0, true, "correct datetime_parse_full return value " "for '%s'", buff); is(date.epoch, date_parsed.epoch,