#include "time.h"
#include <ctype.h>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#include <panda/endian.h>
namespace panda { namespace time {
static const char FTZ_MAGIC[] = "TZif";
static const size_t FTZ_MAX_TIMES = 1200;
static const size_t FTZ_MAX_TYPES = 256;
static const size_t FTZ_MAX_CHARS = 50; /* Maximum number of abbreviation characters */
static const size_t FTZ_MAX_LEAPS = 50; /* Maximum number of leap second corrections */
#pragma pack(push,1)
struct ftz_head {
char tzh_magic[4]; /* TZ_MAGIC */
char tzh_version[1]; /* '\0' or '2' as of 2005 */
char tzh_reserved[15]; /* reserved--must be zero */
uint32_t tzh_ttisgmtcnt; /* coded number of trans. time flags */
uint32_t tzh_ttisstdcnt; /* coded number of trans. time flags */
uint32_t tzh_leapcnt; /* coded number of leap seconds */
uint32_t tzh_timecnt; /* coded number of transition times */
uint32_t tzh_typecnt; /* coded number of local time types */
uint32_t tzh_charcnt; /* coded number of abbr. chars */
};
typedef int32_t ftz_transtimeV1;
typedef int64_t ftz_transtimeV2;
typedef uint8_t ftz_ilocaltype;
typedef uint8_t ftz_abbrev_offset;
typedef uint8_t ftz_isstd;
typedef uint8_t ftz_isgmt;
struct ftz_localtype {
int32_t offset;
uint8_t isdst;
ftz_abbrev_offset abbrev_offset;
};
struct ftz_leapsecV1 {
ftz_transtimeV1 time;
uint32_t correction;
};
struct ftz_leapsecV2 {
ftz_transtimeV2 time;
uint32_t correction;
};
#pragma pack(pop)
/*
** . . .followed by. . .
**
** tzh_timecnt (char [4])s coded transition times a la time(2)
** tzh_timecnt (unsigned char)s types of local time starting at above
** tzh_typecnt repetitions of
** one (char [4]) coded UTC offset in seconds
** one (unsigned char) used to set tm_isdst
** one (unsigned char) that's an abbreviation list index
** tzh_charcnt (char)s '\0'-terminated zone abbreviations
** tzh_leapcnt repetitions of
** one (char [4]) coded leap second transition times
** one (char [4]) total correction after above
** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
** time is standard time, if FALSE,
** transition time is wall clock time
** if absent, transition times are
** assumed to be wall clock time
** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition
** time is UTC, if FALSE,
** transition time is local time
** if absent, transition times are
** assumed to be local time
*/
/*
** If tzh_version is '2' or greater, the above is followed by a second instance
** of tzhead and a second instance of the data in which each coded transition
** time uses 8 rather than 4 chars,
** then a POSIX-TZ-environment-variable-style string for use in handling
** instants after the last transition time stored in the file
** (with nothing between the newlines if there is no POSIX representation for
** such instants).
*/
enum class ParseResult { OK, ABSENT, ERROR };
static ParseResult tzparse_rule_abbrev (const char*& str, char* dest) {
const char* st = str;
switch (*str) {
case ':': return ParseResult::ERROR;
case '<':
str++; st = str;
while (*str && *str != '>') str++;
if (*str != '>') return ParseResult::ERROR;
break;
default:
char c;
while ((c = *str) && !isdigit(c) && c != ',' && c != '+' && c != '-') str++;
}
size_t len = str - st;
if (*str == '>') str++;
if (!len) return ParseResult::ABSENT;
if (len < ZONE_ABBR_MIN) return ParseResult::ERROR;
strncpy(dest, st, len);
dest[len] = '\0';
return ParseResult::OK;
}
static ParseResult tzparse_rule_time (const char*& str, int32_t* dest) {
const char* st = str;
*dest = - (int32_t) strtol(st, (char**)&str, 10) * 3600;
if (str == st) return ParseResult::ABSENT;
int sign = (*dest >= 0 ? 1 : -1);
if (*str == ':') {
str++; st = str;
*dest += sign * (int32_t) strtol(st, (char**)&str, 10) * 60;
if (str == st) return ParseResult::ERROR;
if (*str == ':') {
str++; st = str;
*dest += sign * (int32_t) strtol(st, (char**)&str, 10);
if (str == st) return ParseResult::ERROR;
}
}
return ParseResult::OK;
}
static ParseResult tzparse_rule_switch (const char*& str, Timezone::Rule::Zone::Switch* swtype, datetime* swdate) {
std::memset(swdate, 0, sizeof(*swdate));
const char* st = str;
if (*str == 'M') {
str++; st = str;
*swtype = Timezone::Rule::Zone::Switch::DATE;
swdate->mon = (ptime_t) strtol(st, (char**)&str, 10) - 1;
if (st == str || swdate->mon < 0 || swdate->mon > 11 || *str != '.') return ParseResult::ERROR;
str++; st = str;
swdate->yday = (int32_t) strtol(st, (char**)&str, 10); // yday holds week number
if (st == str || swdate->yday < 1 || swdate->yday > 5 || *str != '.') return ParseResult::ERROR;
str++; st = str;
swdate->wday = (int32_t) strtol(st, (char**)&str, 10);
if (st == str || swdate->wday < 0 || swdate->wday > 6) return ParseResult::ERROR;
}
else if (*str == 'J') {
*swtype = Timezone::Rule::Zone::Switch::JDAY;
str++; st = str;
swdate->yday = (int32_t) strtol(st, (char**)&str, 10);
if (st == str || swdate->yday < 1 || swdate->yday > 365) return ParseResult::ERROR;
} else {
*swtype = Timezone::Rule::Zone::Switch::DAY;
swdate->yday = (int32_t) strtol(st, (char**)&str, 10);
if (st == str || swdate->yday < 0 || swdate->yday > 365) return ParseResult::ERROR;
}
if (*str == '/') {
str++;
int32_t when;
if (tzparse_rule_time(str, &when) != ParseResult::OK) return ParseResult::ERROR;
when = -when; // revert reverse behaviour of parsing rule time
if (when < -604799 || when > 604799) return ParseResult::ERROR;
int sign = when >= 0 ? 1 : -1;
when *= sign;
swdate->hour = when / 3600;
when %= 3600;
swdate->min = when / 60;
swdate->sec = when % 60;
swdate->hour *= sign;
swdate->min *= sign;
swdate->sec *= sign;
} else {
swdate->hour = 2;
swdate->min = 0;
swdate->sec = 0;
}
return ParseResult::OK;
}
bool tzparse_rule (const string_view& sv, Timezone::Rule* rule) {
if (sv.length() > 1000) return false;
auto buf = (char*)alloca(sizeof(char)*(sv.length()+1)); // null-terminate
std::memcpy(buf, sv.data(), sv.length());
buf[sv.length()] = 0;
const char* rulestr = buf;
if (tzparse_rule_abbrev(rulestr, rule->outer.abbrev) != ParseResult::OK) return false;
if (tzparse_rule_time(rulestr, &rule->outer.gmt_offset) != ParseResult::OK) return false;
rule->outer.isdst = 0;
rule->hasdst = 0;
ParseResult result;
if ((result = tzparse_rule_abbrev(rulestr, rule->inner.abbrev)) == ParseResult::ERROR) return false;
if (result == ParseResult::ABSENT) return *rulestr == '\0';
if ((result = tzparse_rule_time(rulestr, &rule->inner.gmt_offset)) == ParseResult::ERROR) return false;
if (result == ParseResult::ABSENT) rule->inner.gmt_offset = rule->outer.gmt_offset + 3600;
if (*rulestr == ',') {
rulestr++;
rule->hasdst = 1;
rule->inner.isdst = 1;
if (tzparse_rule_switch(rulestr, &rule->outer.type, &rule->outer.end) != ParseResult::OK) return false;
if (*rulestr != ',') return false;
rulestr++;
if (tzparse_rule_switch(rulestr, &rule->inner.type, &rule->inner.end) != ParseResult::OK) return false;
if (rule->outer.type != Timezone::Rule::Zone::Switch::DATE || rule->inner.type != Timezone::Rule::Zone::Switch::DATE) {
//fprintf(stderr, "ptime: tz switch rules other than Mm.w.d (i.e. 'n' or 'Jn') are not supported (will consider no DST in this zone)\n");
rule->hasdst = 0;
}
else if (rule->outer.end.mon > rule->inner.end.mon) {
std::swap(rule->outer, rule->inner);
}
}
return *rulestr == '\0';
}
#undef PTIME_TZPARSE_V1
#undef PTIME_TZPARSE_V2
#define PTIME_TZPARSE_V1
#include <panda/time/tzparse_format.icc>
#undef PTIME_TZPARSE_V1
#define PTIME_TZPARSE_V2
#include <panda/time/tzparse_format.icc>
bool tzparse (const string_view& content, Timezone* zone) {
const char* ptr = content.data();
const char* end = ptr + content.length();
ftz_head head;
int version;
int bodyV1_size = tzparse_headerV1(ptr, end, head, &version);
if (bodyV1_size == -1) return false;
bool result;
if (version >= 2) {
ptr += bodyV1_size;
if (tzparse_headerV2(ptr, end, head, &version) == -1) return false;
result = tzparse_bodyV2(ptr, end, head, zone);
} else {
result = tzparse_bodyV1(ptr, end, head, zone);
}
return result;
}
}};