diff --git a/.gitignore b/.gitignore index 4ecb117..5ceb6b7 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ build *.o example +test/test diff --git a/Makefile b/Makefile index 0ebc0da..31bf5a6 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ CC=gcc -CFLAGS=-I. +CFLAGS=-I. -std=gnu2x -Wall -Wextra -Werror DEPS = zones.h UTZ_DATA_DIR = vendor/tzdata @@ -35,3 +35,9 @@ example/example: utz.o zones.o examples/example.o clean: rm -f zones.h zones.c whitelist.txt utz.o zones.o examples/example.o example + +test: test/test + ./test/test + +test/test: utz.o zones.o test/test.o + $(CC) $(CFLAGS) -o $@ $^ -I. diff --git a/README.md b/README.md index d20c22e..f0045e5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,18 @@ packing all possible syntax of the source IANA tz database. Instead a subset corresponding to the what is needed to correctly parse most zones is implemented. +## Updating timezone database + +1. Install python requirements: + `pip3 install -r requirements.txt` +2. If necessary, update vendor files in vendor/android, vendor/wikipedia, and vendor/tzdata +3. Compile timezone links. To do that you need a [GeoNames](https://www.geonames.org/) username: + `./utils/compile_tzlinks.py -u ` +4. Compile whitelisted timezones from the Android file: + `./utils/compile_whitelist.py` +5. Finally, generate the database: + `./utils/generate_zones.py -d vendor/tzdata -w whitelist.txt -i majormetros` + ## Links [zic man page and IANA tz database format documentation](https://linux.die.net/man/8/zic) diff --git a/examples/example.c b/examples/example.c index 455250b..6ce4023 100644 --- a/examples/example.c +++ b/examples/example.c @@ -8,22 +8,23 @@ #include "zones.h" #include -void main() { - printf("Total library db size: %d B\n", sizeof(zone_rules) + sizeof(zone_abrevs) + sizeof(zone_defns) + sizeof(zone_names)); +int main() { + printf("Total library db size: %d B\n", sizeof(utz_zone_rules) + sizeof(utz_zone_abrevs) + sizeof(utz_zone_defns) + sizeof(utz_zone_names)); - udatetime_t dt = {0}; - dt.date.year = 17; + utz_datetime_t dt = {0}; + dt.date.year = 2017; dt.date.month = 9; dt.date.dayofmonth = 26; dt.time.hour = 1; dt.time.minute = 0; dt.time.second = 0; - uzone_t active_zone; - get_zone_by_name("San Francisco", &active_zone); - uoffset_t offset; - char c = get_current_offset(&active_zone, &dt, &offset); + utz_zone_t active_zone; + utz_get_zone_by_name("San Francisco", &active_zone); + utz_offset_t offset; + char c = utz_get_current_offset(&active_zone, &dt, &offset); printf("%s, current offset: %d.%d\n", active_zone.name, offset.hours, offset.minutes / 60); printf(active_zone.abrev_formatter, c); printf("\n"); + return 0; } diff --git a/majormetros b/majormetros index 9ce399f..95cb414 100644 --- a/majormetros +++ b/majormetros @@ -5,16 +5,19 @@ Link Asia/Kolkata Asia/Delhi Link Asia/Kolkata Asia/Mumbai Link Asia/Tokyo Asia/Osaka Link Asia/Shanghai Asia/Wuhan -Link Asia/Chongqing Asia/Chengdu +Link Asia/Shanghai Asia/Chengdu +Link Asia/Shanghai Asia/Chongqing Link Asia/Shanghai Asia/Tianjin Link Asia/Shanghai Asia/Hangzhou -Link Asia/Chongqing Asia/Xi'an +Link Asia/Shanghai Asia/Xi'an Link Asia/Shanghai Asia/Changzhou Link America/Sao_Paulo America/Rio_de_Janeiro +Link Asia/Shanghai Asia/Shantou Link Asia/Shanghai Asia/Nanjing Link Europe/Berlin Europe/Rhine-Ruhr Link Asia/Shanghai Asia/Jinan Link Asia/Kolkata Asia/Bangalore +Link Asia/Shanghai Asia/Harbin Link Asia/Karachi Asia/Lahore Link Asia/Shanghai Asia/Zhengzhou Link Asia/Shanghai Asia/Qingdao @@ -24,7 +27,6 @@ Link Asia/Kolkata Asia/Hyderabad Link Asia/Shanghai Asia/Shenyang Link Asia/Shanghai Asia/Wenzhou Link Asia/Shanghai Asia/Nanchang -Link America/Chicago America/DallasFort_Worth Link America/Chicago America/Houston Link Asia/Kolkata Asia/Ahmedabad Link America/New_York America/Washington,_D.C. diff --git a/requirements.txt b/requirements.txt index 0419167..0e8bff1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,13 @@ -BeautifulSoup -click -geopy -tzwhere +appdirs==1.4.4 +beautifulsoup4==4.14.3 +click==8.3.1 +fissix==24.4.24 +geographiclib==2.1 +geopy==2.4.1 +modernize==0.8.0 +numpy==2.4.1 +pytz==2025.2 +shapely==2.1.2 +soupsieve==2.8.3 +typing_extensions==4.15.0 +tzwhere==3.0.3 diff --git a/test/test.c b/test/test.c new file mode 100644 index 0000000..065ac49 --- /dev/null +++ b/test/test.c @@ -0,0 +1,566 @@ +#include +#include +#include +#include +#include +#include +#include + +#define TEST(v) do { if (!(v)) {\ + fprintf(stderr, "test failed at line %d\n", __LINE__);\ + exit(1);\ + } } while(false) + +static void test_leap(void) { + TEST(utz_is_leap_year(2000)); + TEST(utz_is_leap_year(2004)); + TEST(utz_is_leap_year(2008)); + TEST(!utz_is_leap_year(2025)); + TEST(!utz_is_leap_year(2026)); + TEST(!utz_is_leap_year(2100)); + TEST(utz_is_leap_year(2104)); + TEST(utz_is_leap_year(2204)); + TEST(!utz_is_leap_year(2200)); +} + +static void test_dayofweek(void) { + TEST(utz_dayofweek(2026, 1, 22) == UTZ_THURSDAY); + TEST(utz_dayofweek(2101, 4, 26) == UTZ_TUESDAY); + TEST(utz_dayofweek(2054, 5, 17) == UTZ_SUNDAY); + TEST(utz_dayofweek(2053, 12, 12) == UTZ_FRIDAY); +} + +static void test_cmp(void) { + utz_datetime_t dt1 = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 18 + } + }; + utz_datetime_t dt2 = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + TEST(utz_udatetime_cmp(&dt1, &dt1) == 0); + TEST(utz_udatetime_cmp(&dt1, &dt2) < 0); + dt2 = dt1; + dt2.time.minute -= 1; + TEST(utz_udatetime_cmp(&dt1, &dt2) > 0); + + dt2 = dt1; + dt2.time.hour += 1; + TEST(utz_udatetime_cmp(&dt1, &dt2) < 0); + + dt2 = dt1; + dt2.time.hour -= 1; + TEST(utz_udatetime_cmp(&dt1, &dt2) > 0); + + dt2 = dt1; + dt2.date = utz_date_init(2026, 1, 23); + TEST(utz_udatetime_cmp(&dt1, &dt2) < 0); + + dt2.date = utz_date_init(2026, 1, 21); + TEST(utz_udatetime_cmp(&dt1, &dt2) > 0); + + dt2 = dt1; + dt2.date = utz_date_init(2026, 2, 22); + TEST(utz_udatetime_cmp(&dt1, &dt2) < 0); + + dt2.date = utz_date_init(2026, 0, 22); + TEST(utz_udatetime_cmp(&dt1, &dt2) > 0); + + dt2 = dt1; + dt2.date = utz_date_init(2027, 1, 22); + TEST(utz_udatetime_cmp(&dt1, &dt2) < 0); + + dt2.date = utz_date_init(2025, 1, 22); + TEST(utz_udatetime_cmp(&dt1, &dt2) > 0); +} + +static void test_offset(void) { + TEST(utz_next_dayofweek_offset(UTZ_WEDNESDAY, UTZ_FRIDAY) == 2); + TEST(utz_next_dayofweek_offset(UTZ_TUESDAY, UTZ_TUESDAY) == 0); + TEST(utz_next_dayofweek_offset(UTZ_WEDNESDAY, UTZ_TUESDAY) == 6); +} + +static void test_berlin(void) { + utz_zone_t zone; + // Error handling + TEST(!utz_get_zone_by_name("Unknown", &zone)); + + // Look up by name + TEST(utz_get_zone_by_name("Berlin", &zone)); + TEST(strcmp(zone.name, "Berlin") == 0); + TEST(zone.rules_len == 2); + TEST(strcmp(zone.abrev_formatter, "CE%sT") == 0); + + // Winter time + utz_datetime_t dt = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + utz_offset_t offset; + const char *s = utz_get_current_offset(&zone, &dt, &offset); + TEST(*s == 0); + TEST(offset.hours == 1); + TEST(offset.minutes == 0); + + // Winter time, right before switching to summer time + dt.date = utz_date_init(2026, 3, 29); + dt.time.hour = 0; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(*s == 0); + TEST(offset.hours == 1); + TEST(offset.minutes == 0); + + // Summer time, right after switching from winter time + dt.date = utz_date_init(2026, 3, 29); + dt.time.hour = 1; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == 2); + TEST(offset.minutes == 0); + + // Summer time, right before switching to winter time + dt.date = utz_date_init(2026, 10, 25); + dt.time.hour = 0; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == 2); + TEST(offset.minutes == 0); + + // Winter time, right after switching from summer time + dt.time.hour = 1; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(*s == 0); + TEST(offset.hours == 1); + TEST(offset.minutes == 0); +} + +static void test_st_johns(void) { + // Timezone with negative fractional offset + utz_zone_t zone; + TEST(utz_get_zone_by_name("St Johns", &zone)); + TEST(strcmp(zone.name, "St Johns") == 0); + // Offset -3:30, represented as {-4, 30} + TEST(zone.offset.hours == -4); + TEST(zone.offset.minutes == 30); +} + +static void test_new_york(void) { + utz_zone_t zone; + // Look up by name + TEST(utz_get_zone_by_name("New York", &zone)); + TEST(strcmp(zone.name, "New York") == 0); + TEST(zone.rules_len == 2); + TEST(strcmp(zone.abrev_formatter, "E%sT") == 0); + + // Winter time + utz_datetime_t dt = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + utz_offset_t offset; + const char *s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == -5); + TEST(offset.minutes == 0); + + // Winter time, right before switching to summer time + // Switch at 2:00 local time - 7:00 UTC + dt.date = utz_date_init(2026, 3, 8); + dt.time.hour = 6; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == -5); + TEST(offset.minutes == 0); + + // Summer time, right after switching from winter time + dt.time.hour = 7; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "D") == 0); + TEST(offset.hours == -4); + TEST(offset.minutes == 0); + + // Summer time, right before switching to winter time + // Switch at 2:00 local time - 6:00 UTC + dt.date = utz_date_init(2026, 11, 1); + dt.time.hour = 5; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "D") == 0); + TEST(offset.hours == -4); + TEST(offset.minutes == 0); + + // Winter time, right after switching from summer time + dt.time.hour = 6; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == -5); + TEST(offset.minutes == 0); +} + +static void test_auckland(void) { + utz_zone_t zone; + // Look up by name + TEST(utz_get_zone_by_name("Auckland", &zone)); + TEST(strcmp(zone.name, "Auckland") == 0); + TEST(zone.rules_len == 2); + TEST(strcmp(zone.abrev_formatter, "NZ%sT") == 0); + + // Summer time + utz_datetime_t dt = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + utz_offset_t offset; + const char *s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "D") == 0); + TEST(offset.hours == 13); + TEST(offset.minutes == 0); + + // Summer time, right before switching to winter time + // Switch at 3:00 local time - 14:00 UTC previous day + dt.date = utz_date_init(2026, 4, 4); + dt.time.hour = 13; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "D") == 0); + TEST(offset.hours == 13); + TEST(offset.minutes == 0); + + // Winter time, right after switching from summer time + dt.time.hour = 14; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == 12); + TEST(offset.minutes == 0); + + // Winter time, right before switching to summer time + // Switch at 2:00 local time - 14:00 UTC previous day + dt.date = utz_date_init(2026, 9, 26); + dt.time.hour = 13; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == 12); + TEST(offset.minutes == 0); + + // Summer time, right after switching from winter time + dt.time.hour = 14; + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "D") == 0); + TEST(offset.hours == 13); + TEST(offset.minutes == 0); +} + +static void test_datetime_adjust(void) { + utz_offset_t off; + utz_datetime_t dt, res; + + // Positive offset (same day) + dt = (utz_datetime_t){ + .date = utz_date_init(2026, 1, 22), + .time = { .hour = 10, .minute = 30, .second = 0 } + }; + off = (utz_offset_t){ .hours = 2, .minutes = 15 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 12); + TEST(res.time.minute == 45); + TEST(res.date.dayofmonth == 22); + + // Negative offset (same day) + off = (utz_offset_t){ .hours = -4, .minutes = 50 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 7); + TEST(res.time.minute == 20); + TEST(res.date.dayofmonth == 22); + + + // Minute overflow -> next hour + off = (utz_offset_t){ .hours = 0, .minutes = 45 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 11); + TEST(res.time.minute == 15); + + // Day increment (cross midnight) + dt.time.hour = 23; + dt.time.minute = 50; + off = (utz_offset_t){ .hours = 0, .minutes = 15 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 0); + TEST(res.time.minute == 5); + TEST(res.date.dayofmonth == 23); + + // Day decrement (negative offset) + dt = (utz_datetime_t){ + .date = utz_date_init(2026, 1, 22), + .time = { .hour = 0, .minute = 10, .second = 0 } + }; + off = (utz_offset_t){ .hours = -1, .minutes = 30 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 23); + TEST(res.time.minute == 40); + TEST(res.date.dayofmonth == 21); + + // Month rollover (Jan -> Feb) + dt = (utz_datetime_t){ + .date = utz_date_init(2026, 1, 31), + .time = { .hour = 23, .minute = 0, .second = 0 } + }; + off = (utz_offset_t){ .hours = 2, .minutes = 0 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.time.hour == 1); + TEST(res.date.month == 2); + TEST(res.date.dayofmonth == 1); + + // Year rollover (Dec -> Jan) + dt = (utz_datetime_t){ + .date = utz_date_init(2026, 12, 31), + .time = { .hour = 23, .minute = 30, .second = 0 } + }; + off = (utz_offset_t){ .hours = 1, .minutes = 0 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.date.year == 2027); + TEST(res.date.month == 1); + TEST(res.date.dayofmonth == 1); + TEST(res.time.hour == 0); + TEST(res.time.minute == 30); + + // Leap year: Feb 28 -> Feb 29 + dt = (utz_datetime_t){ + .date = utz_date_init(2028, 2, 28), + .time = { .hour = 23, .minute = 30, .second = 0 } + }; + off = (utz_offset_t){ .hours = 1, .minutes = 0 }; + + res = utz_udatetime_add(&dt, &off); + TEST(res.date.month == 2); + TEST(res.date.dayofmonth == 29); + TEST(res.time.hour == 0); + + // Leap year: Feb 29 -> Mar 1 + dt.date = utz_date_init(2028, 2, 29); + dt.time.hour = 23; + dt.time.minute = 0; + + off = (utz_offset_t){ .hours = 2, .minutes = 0 }; + res = utz_udatetime_add(&dt, &off); + + TEST(res.date.month == 3); + TEST(res.date.dayofmonth == 1); + TEST(res.time.hour == 1); + + // Leap year: Mar 1 -> Feb 29 + dt.date = utz_date_init(2028, 3, 1); + dt.time.hour = 1; + dt.time.minute = 0; + + off = (utz_offset_t){ .hours = -2, .minutes = 0 }; + res = utz_udatetime_add(&dt, &off); + + TEST(res.date.month == 2); + TEST(res.date.dayofmonth == 29); + TEST(res.time.hour == 23); + + // Normal year: Mar 1 -> Feb 28 + dt.date = utz_date_init(2027, 3, 1); + dt.time.hour = 1; + dt.time.minute = 0; + + off = (utz_offset_t){ .hours = -2, .minutes = 0 }; + res = utz_udatetime_add(&dt, &off); + + TEST(res.date.month == 2); + TEST(res.date.dayofmonth == 28); + TEST(res.time.hour == 23); +} + +// No daylight saving time +static void test_brazzaville(void) { + utz_zone_t zone; + // Look up by name + TEST(utz_get_zone_by_name("Brazzaville", &zone)); + TEST(strcmp(zone.name, "Brazzaville") == 0); + TEST(zone.rules_len == 0); + TEST(strcmp(zone.abrev_formatter, "WAT") == 0); + + utz_datetime_t dt = { + .date = utz_date_init(2026, 1, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + utz_offset_t offset; + const char *s = utz_get_current_offset(&zone, &dt, &offset); + TEST(*s == 0); + TEST(offset.hours == 1); + TEST(offset.minutes == 0); +} + +static void test_default(void) { + utz_datetime_t dt = { + .date = utz_date_init(2026, 6, 22), + .time = { + .hour = 12, + .minute = 22, + .second = 19 + } + }; + utz_offset_t offset; + const char *s = utz_get_current_offset(&utz_zone_default, &dt, &offset); + TEST(*s == 0); + TEST(offset.hours == 0); + TEST(offset.minutes == 0); + + utz_zone_t zone; + TEST(utz_get_zone_by_name("Helsinki", &zone)); + s = utz_get_current_offset(&zone, &dt, &offset); + TEST(strcmp(s, "S") == 0); + TEST(offset.hours == 3); + TEST(offset.minutes == 0); +} + +static void test_checked_fns(void) { + utz_date_t date; + TEST(!utz_date_init_checked(1999, 1, 22, &date)); // year is too low + TEST(!utz_date_init_checked(2300, 1, 22, &date)); // year is too high + TEST(!utz_date_init_checked(2026, 13, 22, &date)); // month + TEST(!utz_date_init_checked(2026, 1, 32, &date)); // day + TEST(!utz_date_init_checked(2026, 1, 0, &date)); // day + TEST(!utz_date_init_checked(2026, 2, 30, &date)); // day + TEST(!utz_date_init_checked(2026, 2, 29, &date)); // day + TEST(utz_date_init_checked(2026, 2, 28, &date)); // day ok + TEST(utz_date_init_checked(2028, 2, 29, &date)); // day ok +} + +static void test_init_offset(void) { + utz_offset_t off; + + off = utz_offset_init(false, 1, 30); + TEST(off.hours == 1); + TEST(off.minutes == 30); + + off = utz_offset_init(false, 0, 75); + TEST(off.hours == 1); + TEST(off.minutes == 15); + + off = utz_offset_init(true, 1, 20); + TEST(off.hours == -2); + TEST(off.minutes == 40); + + off = utz_offset_init(true, 0, 75); + TEST(off.hours == -2); + TEST(off.minutes == 45); +} + +static void test_neg_offset(void) { + utz_offset_t off, neg; + + off.hours = 1; + off.minutes = 20; + neg = utz_offset_neg(&off); + TEST(neg.hours == -2); + TEST(neg.minutes == 40); + + off.hours = -1; + off.minutes = 20; + neg = utz_offset_neg(&off); + TEST(neg.hours == 0); + TEST(neg.minutes == 40); + + off.hours = 1; + off.minutes = 0; + neg = utz_offset_neg(&off); + TEST(neg.hours == -1); + TEST(neg.minutes == 0); + + off.hours = -2; + off.minutes = 0; + neg = utz_offset_neg(&off); + TEST(neg.hours == 2); + TEST(neg.minutes == 0); + + off.hours = 0; + off.minutes = 15; + neg = utz_offset_neg(&off); + TEST(neg.hours == -1); + TEST(neg.minutes == 45); + + off.hours = -1; + off.minutes = 15; + neg = utz_offset_neg(&off); + TEST(neg.hours == 0); + TEST(neg.minutes == 45); +} + +static void test_offset_cmp(void) { + utz_offset_t off1, off2; + + off1 = utz_offset_init(false, 0, 30); + off2 = utz_offset_init(false, 1, 30); + TEST(utz_offset_cmp(&off1, &off2) < 0); + + off1 = utz_offset_init(false, 0, 30); + off2 = utz_offset_init(false, 0, 20); + TEST(utz_offset_cmp(&off1, &off2) > 0); + + off1 = utz_offset_init(false, 0, 30); + off2 = utz_offset_init(false, 0, 30); + TEST(utz_offset_cmp(&off1, &off2) == 0); + + off1 = utz_offset_init(true, 0, 30); + off2 = utz_offset_init(true, 1, 30); + TEST(utz_offset_cmp(&off1, &off2) > 0); + + off1 = utz_offset_init(true, 0, 30); + off2 = utz_offset_init(true, 0, 20); + TEST(utz_offset_cmp(&off1, &off2) < 0); + + off1 = utz_offset_init(true, 1, 30); + off2 = utz_offset_init(true, 2, 30); + TEST(utz_offset_cmp(&off1, &off2) > 0); +} + +int main(void) { + test_leap(); + test_dayofweek(); + test_cmp(); + test_offset(); + test_datetime_adjust(); + test_berlin(); + test_st_johns(); + test_new_york(); + test_auckland(); + test_brazzaville(); + test_default(); + test_checked_fns(); + test_init_offset(); + test_neg_offset(); + test_offset_cmp(); + return 0; +} diff --git a/types.h b/types.h new file mode 100644 index 0000000..331b886 --- /dev/null +++ b/types.h @@ -0,0 +1,165 @@ +#pragma once +#include +#include +#include +#include + +typedef enum utz_dayofweek_t { + UTZ_MONDAY = 1, + UTZ_TUESDAY, + UTZ_WEDNESDAY, + UTZ_THURSDAY, + UTZ_FRIDAY, + UTZ_SATURDAY, + UTZ_SUNDAY, +} utz_dayofweek_t; + +/**************************************************************************/ +/* struct definitions */ +/**************************************************************************/ + +// reverse for big endian comparisons via raw? +/** @struct utz_time_t + * @brief time type + * + * @var utz_time_t::second 0-59 + * @var utz_time_t::minute 0-59 + * @var utz_time_t::hour 0-23 + * @var utz_time_t::padding unused space to pad to 4 bytes + * @var utz_time_t::raw for comparisons and conversions + */ +typedef struct utz_time_t { + uint8_t hour; + uint8_t minute; + uint8_t second; +} utz_time_t; + +// reverse for big endian comparisons via raw? +/** @struct utz_date_t + * @brief date type + * + * @var utz_date_t::dayofweek day of week (monday = 1, sunday = 7) + * @var utz_date_t::dayofmonth 01-31 + * @var utz_date_t::month 01-12 + * @var utz_date_t::year 2000-2255 + * @var utz_date_t::padding unused space to pad to 4 bytes + * @var utz_date_t::raw for comparisons and conversions + */ +typedef struct utz_date_t { + uint16_t year; // 2000-2255 + uint8_t month; // 01-12 + uint8_t dayofmonth; // 01-31 + uint8_t dayofweek; +} utz_date_t; + + +/** @brief datetime type */ +typedef struct utz_datetime_t { + union { + utz_date_t date; + struct { + uint16_t year; + uint8_t month; + uint8_t dayofmonth; + uint8_t dayofweek; + }; + }; + union { + utz_time_t time; + struct { + uint8_t hour; + uint8_t minute; + uint8_t second; + }; + }; +} utz_datetime_t; + +static_assert(offsetof(utz_datetime_t, year) == offsetof(utz_datetime_t, date.year)); +static_assert(offsetof(utz_datetime_t, month) == offsetof(utz_datetime_t, date.month)); +static_assert(offsetof(utz_datetime_t, dayofmonth) == offsetof(utz_datetime_t, date.dayofmonth)); +static_assert(offsetof(utz_datetime_t, dayofweek) == offsetof(utz_datetime_t, date.dayofweek)); +static_assert(offsetof(utz_datetime_t, hour) == offsetof(utz_datetime_t, time.hour)); +static_assert(offsetof(utz_datetime_t, minute) == offsetof(utz_datetime_t, time.minute)); +static_assert(offsetof(utz_datetime_t, second) == offsetof(utz_datetime_t, time.second)); + +/** @brief timezone offset type */ +typedef struct utz_offset_t { + uint8_t minutes; // 0 to 59 + int8_t hours; // -12 to +12 +} utz_offset_t; + +/** @struct uzone_packed_t + * @brief packed timezone type + * + * @var uzone_packed_t::offset_inc_minutes signed minutes in increments of OFFSET_INCREMENT + * @var uzone_packed_t::rules_idx index into rules array for start of corresponding rules + * @var uzone_packed_t::rules_len number of rule entries + * @var uzone_packed_t::abrev_formatter abreviation formatter + */ +typedef struct uzone_packed_t { + int8_t offset_inc_minutes; + uint8_t rules_idx; + uint8_t rules_len; + uint16_t abrev_formatter; +} uzone_packed_t; + +/** @struct utz_short_year_t + * @brief type-safe short year. + * + * @var utz_short_year_t::y years since 2000 + */ +typedef struct utz_short_year_t { + uint8_t y; +} utz_short_year_t; + +static_assert(sizeof(utz_short_year_t) == 1); + +/** @struct utz_rule_packed_t + * @brief packed rule type, rules for daylight savings time + * + * There are 3 possible formats for on - the specifier for the day when the rule takes effect: + * 1) the default format is "dayOfWeek >= dayOfMonth" + * 2) unless on_dayofweek is 0, in which case the format is "dayOfMonth" + * 3) unless on_dayofmonth is 0, in which case the format is "last dayOfWeek" + * + * @var utz_rule_packed_t::from_year years since 2000 + * @var utz_rule_packed_t::to_year years since 2000 + * @var utz_rule_packed_t::on_dayofweek day of week (monday = 1, sunday = 7) + * @var utz_rule_packed_t::on_dayofmonth day of month + * @var utz_rule_packed_t::at_is_local_time is time of day in local time, if not utc + * @var utz_rule_packed_t::at_hours time of day, hours + * @var utz_rule_packed_t::at_inc_minutes time of day, minutes, in OFFSET_INCREMENT minute increments + * @var utz_rule_packed_t::letter (-,S,D) sorry Troll, Antarctica + * @var utz_rule_packed_t::in_month month (1-12) + * @var utz_rule_packed_t::offset_hours (0-3) + */ +typedef struct utz_rule_packed_t { + utz_short_year_t from_year; + utz_short_year_t to_year; + uint8_t on_dayofweek:3; + uint8_t on_dayofmonth:5; + uint8_t at_is_local_time:1; + uint8_t at_hours:5; + uint8_t at_inc_minutes:2; + uint8_t letter:2; + uint8_t in_month:4; + uint8_t offset_hours:2; +} utz_rule_packed_t; + +/** @brief unpacked zone type */ +typedef struct utz_zone_t { + const char* name; + utz_offset_t offset; + const utz_rule_packed_t* rules; + uint8_t rules_len; + const char* abrev_formatter; + const uzone_packed_t* src; +} utz_zone_t; + +/** @brief unpacked rule type, rules for daylight savings time */ +typedef struct utz_rule_t { + utz_datetime_t datetime; + bool is_local_time; + const char *letters; + uint8_t offset_hours; +} utz_rule_t; diff --git a/utils/compile_tzlinks.py b/utils/compile_tzlinks.py index 7bc3869..c337538 100755 --- a/utils/compile_tzlinks.py +++ b/utils/compile_tzlinks.py @@ -1,51 +1,63 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Create tz database links for major metropolian areas that don't exist in the IANA db eV Quirk """ import unicodedata +import argparse -from BeautifulSoup import BeautifulSoup -from geopy.geocoders import Nominatim -from tzwhere import tzwhere +from bs4 import BeautifulSoup +import geopy +import sys def main(): - geocoder = Nominatim() - tz = tzwhere.tzwhere() + parser = argparse.ArgumentParser(prog="compile_tzlinks.py", description="Create tz database links for major metropolian areas that don't exist in the IANA db") + parser.add_argument('-u', '--user', required=True, help="GeoNames username (register on https://www.geonames.org/ and enable free web service)") + parser.add_argument('-t', '--timeout', required=False, default=30, type=int, help="network timeout (seconds)") + args = parser.parse_args() + geopy.geocoders.options.default_timeout = args.timeout + geocoder = geopy.geocoders.GeoNames(username=args.user) links = [] with open('vendor/wikipedia/majormetros.html') as f: - soup = BeautifulSoup(f) + soup = BeautifulSoup(f, features="html.parser") table = soup.find('table', {'class': "sortable wikitable"}) - for row in table.findAll('tr'): - columns = row.findAll('td') + for row in table.find_all('tr'): + columns = row.find_all('td') if columns: metro = columns[1].find('a').text country = columns[2].find('a').text - location = geocoder.geocode('%s, %s' % (metro, country)) - if not location: # try just searching for just the metro - location = geocoder.geocode('%s' % metro) - if location: - zone = tz.tzNameAt(location.latitude, location.longitude) - if zone: - metro = unicodedata.normalize('NFD', metro).encode('ascii', 'ignore') - metro = metro.replace(' ', '_') - if zone.split('/')[-1] not in metro: - alias = zone.split('/')[:-1] - alias.append(metro) - links.append(('Link', zone, '/'.join(alias))) + print(f"{metro}, {country}") + try: + location = geocoder.geocode('%s, %s' % (metro, country)) + if not location: # try just searching for just the metro + location = geocoder.geocode('%s' % metro) + if location: + zone = geocoder.reverse_timezone((location.latitude, location.longitude)).pytz_timezone.zone + if zone: + metro = unicodedata.normalize('NFD', metro).encode('ascii', 'ignore').decode('ascii') + metro = metro.replace(' ', '_') + if zone.split('/')[-1] not in metro: + alias = zone.split('/')[:-1] + alias.append(metro) + links.append(('Link', zone, '/'.join(alias))) + else: + print("%s, %s already present as %s" % (metro, country, zone)) else: - print "%s, %s already present as %s" % (metro, country, zone) + print("couldn't find zone for: %s, %s" % (metro, country)) else: - print "couldn't find zone for: %s, %s" % (metro, country) - else: - print "couldn't find location for: %s, %s" % (metro, country) + print("couldn't find location for: %s, %s" % (metro, country)) + except geopy.exc.GeopyError as e: + print(f"geopy error: {e}") + return 1 with open('majormetros', 'w') as f: f.write('\n'.join(['\t'.join(entry) for entry in links])) + return 0 if __name__ == '__main__': - main() + ret = main() + sys.exit(ret) diff --git a/utils/compile_whitelist.py b/utils/compile_whitelist.py index d3d68da..5331965 100755 --- a/utils/compile_whitelist.py +++ b/utils/compile_whitelist.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Small script to generate default zone whitelist from android and wikipedia info eV Quirk @@ -18,6 +18,8 @@ def main(): zones.add(line.split('\t')[1].strip()) zones.add(line.split('\t')[2].strip()) + zones.add('Etc/Universal') + with open('whitelist.txt', 'w') as f: f.write('\n'.join(sorted(zones))) diff --git a/utils/example_strip_historical.py b/utils/example_strip_historical.py index b002c56..2d87826 100755 --- a/utils/example_strip_historical.py +++ b/utils/example_strip_historical.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Sample script to strip historical zones and rules eV Quirk diff --git a/utils/generate_zones.py b/utils/generate_zones.py index 430d5c1..179ad0e 100755 --- a/utils/generate_zones.py +++ b/utils/generate_zones.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Micro timezone generator eV Quirk @@ -9,7 +9,7 @@ from utz import TimeZoneDatabase -DEFAULT_REGIONS = "africa,asia,australasia,backward,europe,northamerica,pacificnew,southamerica" +DEFAULT_REGIONS = "africa,asia,australasia,backward,europe,northamerica,pacificnew,southamerica,etcetera" CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) H_NAME = 'zones.h' C_NAME = 'zones.c' @@ -18,7 +18,7 @@ @click.option('--dir', '-d', default=os.environ.get('UTZ_DATA_DIR', 'tzdata'), help='Path to tzdata dir.') @click.option('--region', '-r', default=os.environ.get('UTZ_REGIONS', DEFAULT_REGIONS).split(','), help='Region files included from tzdata dir.', multiple=True) -@click.option('--include', '-i', default=filter(None, os.environ.get('UTZ_INCLUDES', '').split(',')), +@click.option('--include', '-i', default=[_f for _f in os.environ.get('UTZ_INCLUDES', '').split(',') if _f], help='Additional tz database formated files included (not from tzdata dir).', multiple=True) @click.option('--whitelist', '-w', default=os.environ.get('UTZ_WHITELIST', 'whitelist.txt'), diff --git a/utils/utz.py b/utils/utz.py index 6256650..f59ff8b 100644 --- a/utils/utz.py +++ b/utils/utz.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ Library for parsing IANA timezone files and performing transformations @@ -8,7 +8,6 @@ from collections import OrderedDict from datetime import date from functools import total_ordering -from sets import Set CURRENT_YEAR = date.today().year MAX_FMT_LEN = 5 @@ -81,6 +80,9 @@ def parse_h_m(time): h, m = time.split(':') h = int(h) m = int(m) + if h < 0: + m = 60 - m + h -= 1 else: h = int(time) return z, h, m @@ -157,7 +159,7 @@ def pack(self): l = 2 # see utz.h for struct definitions - return "{%3d, %3d, %d, %2d, %2d, %2d, %d, %d, %2d, %d}, // %s" % ( + return "{{%3d}, {%3d}, %d, %2d, %2d, %2d, %d, %d, %2d, %d}, // %s" % ( _from, # years since 2000 to, # years since 2000 on_u, # day of week (mon=1) unless 0, then assume format is "dayOfMonth" @@ -186,7 +188,7 @@ def __lt__(self, other): def pack(self, rule_groups, rule_group_starts, formatters): if self.until is not None: - print self # FIXME warnings + print(self) # FIXME warnings _, h, m = parse_h_m(self.gmtoff) @@ -262,7 +264,7 @@ def dump(self, f): def strip_historical(self): """ Strip out historical rules and zones """ - rule_group_names = Set() + rule_group_names = set() filtered_rules = [] for rule in self.rules: @@ -325,7 +327,7 @@ def pack(self, h_filename, included_aliases=None): self.rules = rules h_filename = h_filename.upper().replace('.', '_') - h_buf = ['#ifndef _%s' % h_filename, '#define _%s' % h_filename, ''] + h_buf = ['#ifndef _%s' % h_filename, '#define _%s' % h_filename, '', '#include "types.h"', ''] c_buf = ['#include "utz.h"', ''] rule_groups = self.rule_groups() rule_group_starts = self._pack_rules(rule_groups, c_buf, h_buf) @@ -347,9 +349,9 @@ def _pack_rules(self, rule_groups, c_buf, h_buf): for rule in sorted(group, key=lambda x: MONTHS.index(x._in)): c_buf.append(rule.pack()) idx = idx + 1 - c_buf[c_buf.index('PLACEHOLDER')] = 'const urule_packed_t zone_rules[%d] = {' % idx + c_buf[c_buf.index('PLACEHOLDER')] = 'const utz_rule_packed_t utz_zone_rules[%d] = {' % idx c_buf.append('};') - h_buf.append('const urule_packed_t zone_rules[%d];' % idx) + h_buf.append('extern const utz_rule_packed_t utz_zone_rules[%d];' % idx) return group_idx @@ -368,8 +370,6 @@ def _pack_zones(self, rule_groups, rule_group_starts, c_buf, h_buf): max_char = 0 for orig_fmt, packed_fmt in packed_formatters.items(): packed_formatters[orig_fmt]['start'] = total_char - if '%' in packed_fmt['fmt']: - packed_fmt['fmt'] = packed_fmt['fmt'] % '%c' c_buf.append("'%s','\\0'," % "','".join([c for c in packed_fmt['fmt']])) total_char += len(packed_fmt['fmt']) + 1 if len(packed_fmt['fmt']) > max_char: @@ -377,8 +377,8 @@ def _pack_zones(self, rule_groups, rule_group_starts, c_buf, h_buf): c_buf.append('};') c_buf.append('') - c_buf[c_buf.index('PLACEHOLDER')] = 'const char zone_abrevs[%d] = {' % total_char - h_buf.extend(['const char zone_abrevs[%d];' % total_char, '']) + c_buf[c_buf.index('PLACEHOLDER')] = 'const char utz_zone_abrevs[%d] = {' % total_char + h_buf.extend(['extern const char utz_zone_abrevs[%d];' % total_char, '']) h_buf.extend(['#define MAX_ABREV_FORMATTER_LEN %d' % max_char, '']) for zone in sorted(self.zones): @@ -387,15 +387,15 @@ def _pack_zones(self, rule_groups, rule_group_starts, c_buf, h_buf): packed_zones[packed_zone] = [zone] else: packed_zones[packed_zone].append(zone) - zone_indexes[zone.name] = packed_zones.keys().index(packed_zone) + zone_indexes[zone.name] = list(packed_zones.keys()).index(packed_zone) - c_buf.append('const uzone_packed_t zone_defns[%d] = {' % len(packed_zones)) + c_buf.append('const uzone_packed_t utz_zone_defns[%d] = {' % len(packed_zones)) for packed_zone, srcs in packed_zones.items(): for src_zone in srcs: c_buf.append('// ' + src_zone._src) c_buf.append(packed_zone) c_buf.append('};') - h_buf.append('const uzone_packed_t zone_defns[%d];' % len(packed_zones)) + h_buf.append('extern const uzone_packed_t utz_zone_defns[%d];' % len(packed_zones)) return zone_indexes @@ -424,7 +424,7 @@ def _pack_links(self, zone_indexes, c_buf, h_buf, included_aliases=None): name = name.replace("-", '') name = name.replace(".", '') name = name.replace(",", '') - h_buf.append(("#define UTZ_" + name.upper() + ' '*(max_len+4-len(name)) + '&zone_defns[%3d]') % index) + h_buf.append(("#define UTZ_" + name.upper() + ' '*(max_len+4-len(name)) + '&utz_zone_defns[%3d]') % index) name = orig_name.replace('_', ' ') for c in name: if c == "'": @@ -436,7 +436,9 @@ def _pack_links(self, zone_indexes, c_buf, h_buf, included_aliases=None): if len(char) > max_char: max_char = len(char) c_buf.append(("%" + str(max_len*5) + "s, %3d, // %s") % ( "'%s'" % "','".join(char), index, name)) - c_buf[c_buf.index('PLACEHOLDER')] = 'const unsigned char zone_names[%d] = {' % total_char + total_char += 1 + c_buf.append(" " * (max_len * 5 + 4) + "0") + c_buf[c_buf.index('PLACEHOLDER')] = 'const char utz_zone_names[%d] = {' % total_char c_buf.append('};') - h_buf.extend(['', '#define NUM_ZONE_NAMES %d' % len(aliases), '#define MAX_ZONE_NAME_LEN %d' % max_char, '']) - h_buf.append('const unsigned char zone_names[%d];' % total_char) + h_buf.extend(['', '#define UTZ_NUM_ZONE_NAMES %d' % len(aliases), '#define UTZ_MAX_ZONE_NAME_LEN %d' % max_char, '']) + h_buf.append('extern const char utz_zone_names[%d];' % total_char) diff --git a/utz.c b/utz.c index c71fecb..49247f2 100644 --- a/utz.c +++ b/utz.c @@ -5,11 +5,27 @@ */ #include +#include +#include #include "utz.h" #include "zones.h" -const char days_of_week[] = { +#define RULE_IS_VALID(r) ((r).letters != 0) +#define OFFSET_INCREMENT 15 // Minutes + +#define MAX_CURRENT_RULES 4 + 1 + +#define DAYS_IN_LEAP_YEAR 366 + +#ifdef UTZ_GLOBAL_COUNTERS +static uint8_t utz_i, utz_j; +static uint16_t utz_k; +#endif + +#if 0 +/** @brief lookup table name of the days of week */ +static const char days_of_week[] = { 'M','o','n','d','a','y','\0', 'T','u','e','s','d','a','y','\0', 'W','e','d','n','e','s','d','a','y','\0', @@ -19,7 +35,8 @@ const char days_of_week[] = { 'S','u','n','d','a','y','\0', }; -const char months_of_year[] = { +/** @brief lookup table name of the months of year */ +static const char months_of_year[] = { 'J','a','n','u','a','r','y','\0', 'F','e','b','r','u','a','r','y','\0', 'M','a','r','c','h','\0', @@ -32,85 +49,142 @@ const char months_of_year[] = { 'N','o','v','e','m','b','e','r','\0', 'D','e','c','e','m','b','e','r','\0', }; +#endif + +const utz_zone_t utz_zone_default = { + .name = "Universal", + .abrev_formatter = "UTC", + .offset = { + .hours = 0, + .minutes = 0 + }, + .rules = NULL, + .rules_len = 0, + .src = NULL, +}; + +const size_t utz_num_zone_names = UTZ_NUM_ZONE_NAMES; + +/** @brief unpack rule + * + * @param rule_in pointer to packed rule + * @param cur_year year: 1 <= y <= 255 (2001 - 2255) + * @param rule_out pointer for the output unpacked rule + * @return void + */ +static void unpack_rule(const utz_rule_packed_t* rule_in, utz_short_year_t cur_year, utz_rule_t* rule_out); + +/** @brief unpack rules that are active in the current year + * + * Note this assumes no two rules are active on the same day + * + * @param rules_in pointer to packed rules + * @param num_rules the number of rules in the array + * @param cur_year year: 1 <= y <= 255 (2001 - 2255) + * @param rules_out pointer for the output unpacked rules + * @return void + */ +static void unpack_rules(const utz_rule_packed_t* rules_in, uint8_t num_rules, utz_short_year_t cur_year, utz_rule_t* rules_out); + +/** @brief get the rule that applies at datetime + * + * @param zone timezone which rules apply to + * @param rules pointer to rules + * @param datetime the datetime to check rules for + * @return a pointer the the rule that applies (or NULL if there are no rules) + */ +static const utz_rule_t* get_active_rule(const utz_zone_t *zone, const utz_rule_t* rules, const utz_datetime_t* datetime); + +/** @brief unpack timezone + * + * @param name the name of the timezone + * @param zone_in pointer to input packed zone + * @param zone_in pointer to output unpacked zone + * @return void + */ +static void unpack_zone(const uzone_packed_t* zone_in, const char* name, utz_zone_t* zone_out); + +static const uzone_packed_t* last_zone = NULL; +static uint16_t last_year = 0; -const uzone_packed_t* last_zone; -uint8_t last_year; -urule_t cached_rules[MAX_CURRENT_RULES]; +/** @brief cached rules for the zone and year from the last call of utz_get_current_offset */ +static utz_rule_t cached_rules[MAX_CURRENT_RULES]; -uint8_t ustrneq(const char* string1, const char* string2, uint8_t n) { +static bool ustrneq(const char* string1, const char* string2, uint8_t n) { #ifndef UTZ_GLOBAL_COUNTERS uint8_t utz_i; #endif for (utz_i = 0; utz_i < n && string1[utz_i] != '\0' && string2[utz_i] != '\0'; utz_i++) { if (string1[utz_i] != string2[utz_i]) { - return UFALSE; + return false; } } - return UTRUE; + return true; } -#define ustrncpy(dest, src, n) ustrnreplace(dest, src, 0, 0, n) -char* ustrnreplace(char* dest, const char* src, char c, char* replacement, uint8_t n) { -#ifndef UTZ_GLOBAL_COUNTERS - uint8_t utz_i, utz_j; -#endif - for (utz_i = utz_j = 0; utz_i < n; (utz_i++)) { - if (replacement != 0 && src[utz_i] == c) { - while(*replacement != '\0') { - dest[utz_j++]= *(replacement++); - } - } else { - dest[utz_j++] = src[utz_i]; - } - if (src[utz_i] == '\0') { - break; - } - } - return dest; -} +static uint8_t days_in_month(uint16_t y, uint8_t m); +utz_dayofweek_t utz_dayofweek(uint16_t y, uint8_t m, uint8_t d) { + static const uint8_t dayofweek_table[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + y -= m < 3; + d = ((y + y / 4 - y / 100 + UTZ_YEAR_OFFSET / 400 + dayofweek_table[m - 1] + d) % 7); + if (d == 0) { return 7; } else { return d; } +} -uint8_t bin_to_bcd(uint8_t value) { - return ((value / 10) << 4) | (value % 10); +utz_dayofweek_t dayofweek_short(utz_short_year_t y, uint8_t m, uint8_t d) { + return utz_dayofweek(UTZ_LONG_YEAR(y), m, d); } -uint8_t bcd_to_bin(uint8_t value) { - return (value & 0x0F) + ((value >> 4) * 10); +bool utz_dayofweek_checked(uint16_t y, uint8_t m, uint8_t d, utz_dayofweek_t* dow_out) { + if (y < UTZ_YEAR_OFFSET || y > UTZ_YEAR_OFFSET + 255) { + return false; + } + if (m < 1 || m > 12) { + return false; + } + if (d < 1 || d > days_in_month(y, m)) { + return false; + } + *dow_out = utz_dayofweek(y, m, d); + return true; } -uint8_t dayofweek(uint8_t y, uint8_t m, uint8_t d) { - static const uint8_t dayofweek_table[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; - y -= m < 3; - d = ((UYEAR_TO_YEAR(y) + (UYEAR_TO_YEAR(y)/4) - (UYEAR_TO_YEAR(y)/100) + UYEAR_OFFSET/400 + dayofweek_table[m-1] + d) % 7); - if (d == 0) { return 7; } else { return d; } +bool utz_is_leap_year(uint16_t y) { + return y % 4 == 0 && (y % 100 != 0 || y % 400 == 0); } -uint8_t is_leap_year(uint8_t y) { -#if UYEAR_OFFSET == 2000 - if (y & 0x03 && y != 100 || y != 200) { +bool is_leap_year_short(utz_short_year_t y) { +#if UTZ_YEAR_OFFSET == 2000 + return y.y % 4 == 0 && y.y != 100 && y.y != 200; #else - if ((((UYEAR_TO_YEAR(y) % 4) == 0) && ((UYEAR_TO_YEAR(y) % 100) != 0)) || ((UYEAR_TO_YEAR(y) % 400) == 0)) { + return utz_is_leap_year(UTZ_LONG_YEAR(y)); #endif - return UTRUE; +} + +static const uint8_t days_in_month_nonleap[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +static uint8_t days_in_month(uint16_t y, uint8_t m) { + if (m == 2 && utz_is_leap_year(y)) { + return days_in_month_nonleap[m] + 1; + } else { + return days_in_month_nonleap[m]; } - return UFALSE; } -uint8_t days_in_month(uint8_t y, uint8_t m) { - static const uint8_t days_in_month_nonleap[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; - if (m == 2 && is_leap_year(y)) { +static uint8_t days_in_month_short(utz_short_year_t y, uint8_t m) { + if (m == 2 && is_leap_year_short(y)) { return days_in_month_nonleap[m] + 1; } else { return days_in_month_nonleap[m]; } } -uint8_t next_dayofweek_offset(uint8_t dayofweek_of_cur, uint8_t dayofweek) { +uint8_t utz_next_dayofweek_offset(utz_dayofweek_t dayofweek_of_cur, utz_dayofweek_t dayofweek) { return (7 + dayofweek - dayofweek_of_cur) % 7; } -int16_t udatetime_cmp(udatetime_t* dt1, udatetime_t* dt2) { - int16_t ret; +int utz_udatetime_cmp(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + int ret; ret = dt1->date.year - dt2->date.year; if(ret != 0) { return ret; } ret = dt1->date.month - dt2->date.month; if(ret != 0) { return ret; } ret = dt1->date.dayofmonth - dt2->date.dayofmonth; if(ret != 0) { return ret; } @@ -120,71 +194,148 @@ int16_t udatetime_cmp(udatetime_t* dt1, udatetime_t* dt2) { return 0; } -int32_t udatetime_unix(udatetime_t* dt) { +int utz_offset_cmp(const utz_offset_t* dt1, const utz_offset_t* dt2) { + int ret; + ret = dt1->hours - dt2->hours; + if (ret != 0) { + return ret; + } + return dt1->minutes - dt2->minutes; } -void unpack_rule(const urule_packed_t* rule_in, uint8_t cur_year, urule_t* rule_out) { - static const char letter_lut[3] = {'-', 'S', 'D'}; +static void udate_dec(utz_date_t *date) { + date->dayofweek -= 1; + if (date->dayofweek == 0) { + date->dayofweek = 7; + } + date->dayofmonth -= 1; + if (date->dayofmonth == 0) { + if (date->month == 1) { + date->year -= 1; + date->month = 12; + date->dayofmonth = 31; + } else { + date->month -= 1; + date->dayofmonth = days_in_month(date->year, date->month); + } + } +} - uint8_t dayofweek_of_first_dayofmonth; - uint8_t first_dayofweek; - uint8_t dayofweek_of_dayofmonth; +static void udate_inc(utz_date_t *date) { + date->dayofweek += 1; + if (date->dayofweek == 8) { + date->dayofweek = 1; + } + date->dayofmonth += 1; + if (date->dayofmonth == days_in_month(date->year, date->month) + 1) { + date->dayofmonth = 1; + date->month += 1; + if (date->month == 13) { + date->month = 1; + date->year += 1; + } + } +} - rule_out->date.year = cur_year; - rule_out->date.month = 0; - rule_out->date.month += rule_in->in_month; +utz_datetime_t utz_udatetime_add(const utz_datetime_t* dt, const utz_offset_t *offset) { + utz_datetime_t r = *dt; + + int8_t h = dt->time.hour; // 0..23 + uint8_t m = dt->time.minute; // 0..59 + + m += offset->minutes; // can't overflow, offset->minutes is guaranteed to be 0..59 + if (m >= 60) { + m -= 60; + h += 1; + } + h += offset->hours; // can't overflow, offset->hours is guaranteed to be -12..12 + if (h >= 24) { + h -= 24; + udate_inc(&r.date); + } else if (h < 0) { + h += 24; + udate_dec(&r.date); + } + r.time.hour = (uint8_t)h; + r.time.minute = m; + return r; +} + +utz_offset_t utz_offset_neg(const utz_offset_t *offset) { + utz_offset_t neg_offset; + + neg_offset.hours = -offset->hours; + if (offset->minutes == 0) { + neg_offset.minutes = 0; + } else { + neg_offset.minutes = 60 - offset->minutes; + neg_offset.hours -= 1; + } + return neg_offset; +} + +utz_datetime_t utz_udatetime_sub(const utz_datetime_t* dt, const utz_offset_t *offset) { + utz_offset_t neg_offset = utz_offset_neg(offset); + + return utz_udatetime_add(dt, &neg_offset); +} + +static void unpack_rule(const utz_rule_packed_t* rule_in, utz_short_year_t cur_year, utz_rule_t* rule_out) { + static const char * const letter_lut[3] = {"", "S", "D"}; + + utz_dayofweek_t dayofweek_of_first_dayofmonth; + utz_dayofweek_t first_dayofweek; + utz_dayofweek_t dayofweek_of_dayofmonth; + + rule_out->datetime.date.year = UTZ_LONG_YEAR(cur_year); + rule_out->datetime.date.month = 0; + rule_out->datetime.date.month += rule_in->in_month; if (rule_in->on_dayofweek == 0) { // format is DOM e.g. 22 - rule_out->date.dayofmonth = rule_in->on_dayofmonth; + rule_out->datetime.date.dayofmonth = rule_in->on_dayofmonth; } else if (rule_in->on_dayofmonth == 0) { // format is lastDOW e.g. lastSun - dayofweek_of_first_dayofmonth = dayofweek(cur_year, rule_in->in_month, 1); - first_dayofweek = next_dayofweek_offset(dayofweek_of_first_dayofmonth, rule_in->on_dayofweek); - rule_out->date.dayofmonth = 1 + (7*3) + first_dayofweek; - if (rule_out->date.dayofmonth + 7 <= days_in_month(cur_year, rule_in->in_month)) { - rule_out->date.dayofmonth += 7; + dayofweek_of_first_dayofmonth = dayofweek_short(cur_year, rule_in->in_month, 1); + first_dayofweek = utz_next_dayofweek_offset(dayofweek_of_first_dayofmonth, rule_in->on_dayofweek); + rule_out->datetime.date.dayofmonth = 1 + (7*3) + first_dayofweek; + if (rule_out->datetime.date.dayofmonth + 7 <= days_in_month_short(cur_year, rule_in->in_month)) { + rule_out->datetime.date.dayofmonth += 7; } } else { // format is DOW >= DOM e.g. Sun>=22 - dayofweek_of_dayofmonth = dayofweek(cur_year, rule_in->in_month, rule_in->on_dayofmonth); - rule_out->date.dayofmonth = rule_in->on_dayofmonth + next_dayofweek_offset( + dayofweek_of_dayofmonth = dayofweek_short(cur_year, rule_in->in_month, rule_in->on_dayofmonth); + rule_out->datetime.date.dayofmonth = rule_in->on_dayofmonth + utz_next_dayofweek_offset( dayofweek_of_dayofmonth, rule_in->on_dayofweek ); } - rule_out->time.hour = 0; - rule_out->time.hour += rule_in->at_hours; - rule_out->time.minute = 0; - rule_out->time.minute += rule_in->at_inc_minutes * OFFSET_INCREMENT; - rule_out->is_local_time = 0; - rule_out->is_local_time += rule_in->at_is_local_time; + rule_out->datetime.time.hour = 0; + rule_out->datetime.time.hour += rule_in->at_hours; + rule_out->datetime.time.minute = 0; + rule_out->datetime.time.minute += rule_in->at_inc_minutes * OFFSET_INCREMENT; + rule_out->is_local_time = rule_in->at_is_local_time != 0; - rule_out->letter = letter_lut[rule_in->letter]; + rule_out->letters = letter_lut[rule_in->letter]; rule_out->offset_hours = 0; rule_out->offset_hours += rule_in->offset_hours; } -void rulecpy(urule_t* dest, urule_t* src) { -#ifndef UTZ_GLOBAL_COUNTERS - uint8_t utz_i; -#endif - for (utz_i = 0; utz_i < sizeof(urule_t); utz_i++) { - ((uint8_t*)dest)[utz_i] = ((uint8_t*)src)[utz_i]; - } -} - -void unpack_rules(const urule_packed_t* rules_in, uint8_t num_rules, uint8_t cur_year, urule_t* rules_out) { +static void unpack_rules(const utz_rule_packed_t* rules_in, uint8_t num_rules, utz_short_year_t cur_year, utz_rule_t* rules_out) { #ifndef UTZ_GLOBAL_COUNTERS uint8_t utz_i; #endif uint8_t l = 0; uint8_t current_rule_count = 1; + if (num_rules == 0) { + return; + } + for (utz_i = 0; utz_i < num_rules && current_rule_count < MAX_CURRENT_RULES; utz_i++) { // First lets find the "last" rule of the previous year, for simplification // this assumes that multiple rules don't apply to the same month and // that the offset would not change between years (just the day of effect) - if (cur_year >= rules_in[utz_i].from_year && cur_year <= rules_in[utz_i].to_year) { + if (cur_year.y >= rules_in[utz_i].from_year.y && cur_year.y <= rules_in[utz_i].to_year.y) { if (rules_in[utz_i].in_month > rules_in[l].in_month) { l = utz_i; } @@ -194,79 +345,200 @@ void unpack_rules(const urule_packed_t* rules_in, uint8_t num_rules, uint8_t cur unpack_rule(&rules_in[l], cur_year, rules_out); // We override the "last" rule time of effect to be the start of the current year - rules_out->date.year = cur_year; - rules_out->date.month = 1; - rules_out->date.dayofmonth = 1; - for (utz_i = 0; utz_i < sizeof(utime_t); utz_i++) { - ((char*)&rules_out->time)[utz_i] = 0; - } + rules_out->datetime.date.year = UTZ_LONG_YEAR(cur_year); + rules_out->datetime.date.month = 1; + rules_out->datetime.date.dayofmonth = 1; + memset(&rules_out->datetime.time, 0, sizeof(utz_time_t)); } -urule_t* get_active_rule(urule_t* rules, udatetime_t* datetime) { +static const utz_rule_t* get_active_rule(const utz_zone_t* zone, const utz_rule_t* rules, const utz_datetime_t* datetime) { #ifndef UTZ_GLOBAL_COUNTERS int8_t utz_i = 0; #endif + if (zone->rules_len == 0) { + return NULL; + } + // Rules are guaranteed to have year set to be equal to datetime's year (done in unpack_rule). for (utz_i = 1; utz_i < MAX_CURRENT_RULES; utz_i++) { - if (!RULE_IS_VALID(rules[utz_i]) || udatetime_cmp(datetime, &(rules[utz_i].datetime)) < 0) { - return &rules[utz_i-1]; + const utz_rule_t *rule = rules + utz_i; + if (!RULE_IS_VALID(*rule)) { + return rule - 1; + } + if (!rule->is_local_time) { + // Rule start is UTC time, simply comprare + if (utz_udatetime_cmp(datetime, &rule->datetime) < 0) { + return rule - 1; + } + } else { + // Rule start is local time of the zone, regardless of the rule + utz_offset_t offset = zone->offset; + utz_datetime_t local = utz_udatetime_add(datetime, &offset); + if (utz_udatetime_cmp(&local, &rule->datetime) < 0) { + return rule - 1; + } } } return &rules[MAX_CURRENT_RULES-1]; } -char get_current_offset(uzone_t* zone, udatetime_t* datetime, uoffset_t* offset) { - urule_t* rule; +const char *utz_get_current_offset(const utz_zone_t* zone, const utz_datetime_t* datetime, utz_offset_t* offset) { if (last_zone != zone->src || last_year != datetime->date.year) { - unpack_rules(zone->rules, zone->rules_len, datetime->date.year, cached_rules); + unpack_rules(zone->rules, zone->rules_len, UTZ_SHORT_YEAR(datetime->date.year), cached_rules); last_zone = zone->src; last_year = datetime->date.year; } offset->minutes = zone->offset.minutes; offset->hours = zone->offset.hours; - rule = get_active_rule(cached_rules, datetime); - offset->hours += rule->offset_hours; - return rule->letter; + const utz_rule_t *rule = get_active_rule(zone, cached_rules, datetime); + if (rule) { + offset->hours += rule->offset_hours; + return rule->letters; + } else { + return ""; + } } -void unpack_zone(const uzone_packed_t* zone_in, const char* name, uzone_t* zone_out) { +static void unpack_zone(const uzone_packed_t* zone_in, const char* name, utz_zone_t* zone_out) { zone_out->src = zone_in; zone_out->name = name; - zone_out->offset.minutes = (zone_in->offset_inc_minutes % (60 / OFFSET_INCREMENT)) * OFFSET_INCREMENT; + int8_t minutes = (zone_in->offset_inc_minutes % (60 / OFFSET_INCREMENT)) * OFFSET_INCREMENT; zone_out->offset.hours = zone_in->offset_inc_minutes / (60 / OFFSET_INCREMENT); - zone_out->rules = &(zone_rules[zone_in->rules_idx]); + if (minutes < 0) { + minutes += 60; + zone_out->offset.hours -= 1; + } + zone_out->offset.minutes = minutes; + zone_out->rules = &(utz_zone_rules[zone_in->rules_idx]); zone_out->rules_len = zone_in->rules_len; - zone_out->abrev_formatter = &zone_abrevs[zone_in->abrev_formatter]; + zone_out->abrev_formatter = &utz_zone_abrevs[zone_in->abrev_formatter]; } -uint8_t get_next(const char** list) { +/** @brief advance to the next item in utz_zone_names and return + * + * @param list pointer + * @return index into the utz_zone_defns array for the item before advancement + */ +static uint8_t advance_and_get_defn_idx(const char** list) { do { (*list)++; - } while (*(*list) != '\0'); - return (uint8_t) *(++(*list)); + } while (**list); + ++(*list); + return **list; } -const char* get_index(const char* list, uint8_t i) { - while(i-->0) { - get_next(&list); +const char *utz_next_zone_name(const char *prev) { + do { + ++prev; + } while (*prev); + prev += 2; + if (*prev) { + return prev; + } else { + return NULL; } - return list; } -void get_zone_by_name(char* name, uzone_t* zone_out) { +bool utz_get_zone_by_name(const char* name, utz_zone_t* zone_out) { #ifndef UTZ_GLOBAL_COUNTERS uint16_t utz_k; #endif - const char* zone = zone_names; - for (utz_k = 0; utz_k < NUM_ZONE_NAMES; utz_k++) { - if (ustrneq(zone, name, MAX_ZONE_NAME_LEN)) { - unpack_zone(&zone_defns[get_next(&zone)], name, zone_out); - break; + const char* zone = utz_zone_names; + for (utz_k = 0; utz_k < UTZ_NUM_ZONE_NAMES; utz_k++) { + if (ustrneq(zone, name, UTZ_MAX_ZONE_NAME_LEN)) { + const char *name = zone; + unpack_zone(&utz_zone_defns[advance_and_get_defn_idx(&zone)], name, zone_out); + return true; } else { - get_next(&zone); + advance_and_get_defn_idx(&zone); } zone++; } + return false; +} + +bool utz_udatetime_eq(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + return utz_udatetime_cmp(dt1, dt2) == 0; +} + +bool utz_udatetime_lt(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + return utz_udatetime_cmp(dt1, dt2) < 0; +} + +bool utz_udatetime_le(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + return utz_udatetime_cmp(dt1, dt2) <= 0; +} + +bool utz_udatetime_gt(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + return utz_udatetime_cmp(dt1, dt2) > 0; +} + +bool utz_udatetime_ge(const utz_datetime_t* dt1, const utz_datetime_t* dt2) { + return utz_udatetime_cmp(dt1, dt2) >= 0; +} + +utz_date_t utz_date_init(uint16_t year, uint8_t month, uint8_t day) { + utz_dayofweek_t day_of_week = utz_dayofweek(year, month, day); + return (utz_date_t){ + .year = year, + .month = month, + .dayofmonth = day, + .dayofweek = day_of_week + }; +} + +bool utz_date_init_checked(uint16_t year, uint8_t month, uint8_t day, utz_date_t *date_out) { + if (year < UTZ_YEAR_OFFSET || year > UTZ_YEAR_OFFSET + 255) { + return false; + } + utz_dayofweek_t dow = UTZ_MONDAY; + if (!utz_dayofweek_checked(year, month, day, &dow)) { + return false; + } + date_out->year = year; + date_out->month = month; + date_out->dayofmonth = day; + date_out->dayofweek = dow; + return true; +} + +bool utz_time_init_checked(uint8_t hour, uint8_t minute, uint8_t second, utz_time_t* time_out) { + if (hour >= 24) { + return false; + } + if (minute >= 60) { + return false; + } + // no leap seconds + if (second >= 60) { + return false; + } + time_out->hour = hour; + time_out->minute = minute; + time_out->second = second; + return true; +} + +utz_offset_t utz_offset_init(bool negative, uint8_t hours, uint8_t minutes) { + hours += minutes / 60; + minutes %= 60; + + if (!negative) { + return (utz_offset_t){ + .hours = hours, + .minutes = minutes, + }; + } else if (minutes == 0) { + return (utz_offset_t){ + .hours = -hours, + .minutes = 0, + }; + } else { + return (utz_offset_t){ + .hours = -hours - 1, + .minutes = 60 - minutes + }; + } } diff --git a/utz.h b/utz.h index 47b243b..6d5879f 100644 --- a/utz.h +++ b/utz.h @@ -8,182 +8,83 @@ #define _UTZ_H #include +#include + +#include "types.h" /**************************************************************************/ /* constants */ /**************************************************************************/ -#define UTRUE 1 -#define UFALSE 0 - -#define UYEAR_OFFSET 2000 -#define UYEAR_OFFSET_SEC 946684800 -#define UYEAR_FROM_YEAR(y) (y - UYEAR_OFFSET) -#define UYEAR_TO_YEAR(y) (y + UYEAR_OFFSET) - -#define OFFSET_INCREMENT 15 // Minutes - -#define MAX_CURRENT_RULES 4 + 1 // Fuck Morocco +#define UTZ_YEAR_OFFSET 2000 +#define UTZ_SHORT_YEAR(y) ((utz_short_year_t){(y) - UTZ_YEAR_OFFSET}) +#define UTZ_LONG_YEAR(_y) ((_y).y + UTZ_YEAR_OFFSET) -#define DAYS_IN_LEAP_YEAR 366 - -#define RULE_IS_VALID(r) ((r).letter != 0) +extern const size_t utz_num_zone_names; /**************************************************************************/ -/* struct definitions */ +/* datetime functions */ /**************************************************************************/ -// reverse for big endian comparisons via raw? -/** @struct utime_t - * @brief time type - * - * @var utime_t::second 0-59 or 0-0x59 in bcd mode - * @var utime_t::minute 0-59 or 0-0x59 in bcd mode - * @var utime_t::hour 0-23 or 0-0x23 in bcd mode - * @var utime_t::padding unused space to pad to 4 bytes - * @var utime_t::raw for comparisons and conversions - */ -typedef struct utime_t { - uint8_t hour; - uint8_t minute; - uint8_t second; -} utime_t; - -// reverse for big endian comparisons via raw? -/** @struct udate_t - * @brief date type - * - * @var udate_t::dayofweek day of week (monday = 1, sunday = 7) - * @var udate_t::dayofmonth 01-31 or 0x01-0x31 in bcd mode - * @var udate_t::month 01-12 or 0x01-0x12 in bcd mode - * @var udate_t::year 00-99 or 0x00-0x99 in bcd mode - * @var udate_t::padding unused space to pad to 4 bytes - * @var udate_t::raw for comparisons and conversions - */ -typedef struct udate_t { - uint8_t year; // 00-99 or 0x00-0x99 in bcd mode (offset 2000 ???) - uint8_t month; // 01-12 or 0x01-0x12 in bcd mode - uint8_t dayofmonth; // 01-31 or 0x01-0x31 in bcd mode - uint8_t dayofweek; -} udate_t; - - -/** @brief datetime type */ -typedef struct udatetime_t { - udate_t date; - utime_t time; -} udatetime_t; - -/** @brief timezone offset type */ -typedef struct uoffset_t { - uint8_t minutes; // 0 to 59 - int8_t hours; // -12 to +12 -} uoffset_t; - -/** @struct uzone_packed_t - * @brief packed timezone type - * - * @var uzone_packed_t::offset_inc_minutes signed minutes in increments of OFFSET_INCREMENT - * @var uzone_packed_t::rules_idx index into rules array for start of corresponding rules - * @var uzone_packed_t::rules_len number of rule entries - * @var uzone_packed_t::abrev_formatter abreviation formatter +/** @brief populates utz_date_t structure, sets day of the week. + * @param y year 2000 <= y <= 2255 + * @param m month: 1 <= m <= 12 + * @param d day: 1 <= d <= 31 */ -typedef struct uzone_packed_t { - int8_t offset_inc_minutes; - uint8_t rules_idx; - uint8_t rules_len; - uint16_t abrev_formatter; -} uzone_packed_t; - -/** @struct urule_packed_t - * @brief packed rule type, rules for daylight savings time - * - * There are 3 possible formats for on - the specifier for the day when the rule takes effect: - * 1) the default format is "dayOfWeek >= dayOfMonth" - * 2) unless on_dayofweek is 0, in which case the format is "dayOfMonth" - * 3) unless on_dayofmonth is 0, in which case the format is "last dayOfWeek" - * - * @var urule_packed_t::from_year years since 2000 - * @var urule_packed_t::to_year years since 2000 - * @var urule_packed_t::on_dayofweek day of week (monday = 1, sunday = 7) - * @var urule_packed_t::on_dayofmonth day of month - * @var urule_packed_t::at_is_local_time is time of day in local time, if not utc - * @var urule_packed_t::at_hours time of day, hours - * @var urule_packed_t::at_inc_minutes time of day, minutes, in OFFSET_INCREMENT minute increments - * @var urule_packed_t::letter (-,S,D) sorry Troll, Antarctica - * @var urule_packed_t::in_month month (1-12) - * @var urule_packed_t::offset_hours (0-3) +utz_date_t utz_date_init(uint16_t year, uint8_t month, uint8_t day); + +/** @brief populates utz_date_t structure, checking input. + * @param y year 2000 <= y <= 2255 + * @param m month: 1 <= m <= 12 + * @param d day: 1 <= d <= 31 + * @param[out] date_out result + * @return true on success */ -typedef struct urule_packed_t { - uint8_t from_year; - uint8_t to_year; - uint8_t on_dayofweek:3; - uint8_t on_dayofmonth:5; - uint8_t at_is_local_time:1; - uint8_t at_hours:5; - uint8_t at_inc_minutes:2; - uint8_t letter:2; - uint8_t in_month:4; - uint8_t offset_hours:2; -} urule_packed_t; - -/** @brief unpacked zone type */ -typedef struct uzone_t { - const char* name; - uoffset_t offset; - const urule_packed_t* rules; - uint8_t rules_len; - const char* abrev_formatter; - const uzone_packed_t* src; -} uzone_t; - -/** @brief unpacked rule type, rules for daylight savings time */ -typedef struct urule_t { - union { - udatetime_t datetime; - struct { - udate_t date; - utime_t time; - }; - }; - uint8_t is_local_time; - char letter; - uint8_t offset_hours; -} urule_t; +bool utz_date_init_checked(uint16_t year, uint8_t month, uint8_t day, utz_date_t* date_out); -/**************************************************************************/ -/* datetime functions */ -/**************************************************************************/ +/** @brief populates utz_time_t structure, checking input. + * @param[out] time_out result + * @return true on success + */ +bool utz_time_init_checked(uint8_t hour, uint8_t minute, uint8_t second, utz_time_t* time_out); -/** @brief convert a binary formatted udate_t or utime_t to bcd format via a pointer to the raw field - * - * @param pointer to the raw field of a udate_t or utime_t - * @return void +/** @brief initializes utz_offset_t + * @param negative time offset sign, true for negative. + * @return normalized offset */ -uint8_t bin_to_bcd(uint8_t value); +utz_offset_t utz_offset_init(bool negative, uint8_t hours, uint8_t minutes); -/** @brief convert a bcd formatted udate_t or utime_t to binary format via a pointer to the raw field +/** @brief negates an offset * - * @param pointer to the raw field of a udate_t or utime_t - * @return void + * @return negative offset */ -uint8_t bcd_to_bin(uint8_t value); +utz_offset_t utz_offset_neg(const utz_offset_t *offset); /** @brief returns the day of the week for the given year/month/day * - * @param y year: 1 <= y <= 255 (2001 - 2255) + * @param y year 2000 <= y <= 2255 * @param m month: 1 <= m <= 12 * @param d day: 1 <= d <= 31 * @return day of week (Monday = 1, Sunday = 7) */ -uint8_t dayofweek(uint8_t y, uint8_t m, uint8_t d); +utz_dayofweek_t utz_dayofweek(uint16_t y, uint8_t m, uint8_t d); + +/** @brief returns the day of the week for the given year/month/day, checks input + * + * @param y year: 2000 <= y <= 2255 + * @param m month: 1 <= m <= 12 + * @param d day: 1 <= d <= 31 + * @param[out] dow_out resulting day of week (Monday = 1, Sunday = 7) + * @return true on success + */ +bool utz_dayofweek_checked(uint16_t y, uint8_t m, uint8_t d, utz_dayofweek_t* dow_out); /** @brief returns true if the year is a leap year * - * @param y year: 1 <= y <= 255 (2001 - 2255) + * @param y year * @brief true if the year is a leap year */ -uint8_t is_leap_year(uint8_t y); +bool utz_is_leap_year(uint16_t y); /** @brief returns days needed to get from the "current" day to the desired day of the week. * @@ -191,7 +92,7 @@ uint8_t is_leap_year(uint8_t y); * @param dayofweek the desired day of the week: 1 <= dayofweek <= 7 (Monday = 1, Sunday = 7) * @return number of days */ -uint8_t next_dayofweek_offset(uint8_t dayofweek_of_cur, uint8_t dayofweek); +uint8_t utz_next_dayofweek_offset(utz_dayofweek_t dayofweek_of_cur, utz_dayofweek_t dayofweek); /** @brief returns *dt1 == *dt2 * @@ -199,7 +100,7 @@ uint8_t next_dayofweek_offset(uint8_t dayofweek_of_cur, uint8_t dayofweek); * @param dt1 pointer to the second datetime * @return *dt1 == *dt2 */ -uint8_t udatetime_eq(udatetime_t* dt1, udatetime_t* dt2); +bool utz_udatetime_eq(const utz_datetime_t* dt1, const utz_datetime_t* dt2); /** @brief returns *dt1 < *dt2 * @@ -207,7 +108,7 @@ uint8_t udatetime_eq(udatetime_t* dt1, udatetime_t* dt2); * @param dt1 pointer to the second datetime * @return *dt1 < *dt2 */ -uint8_t udatetime_lt(udatetime_t* dt1, udatetime_t* dt2); +bool utz_udatetime_lt(const utz_datetime_t* dt1, const utz_datetime_t* dt2); /** @brief returns *dt1 <= *dt2 * @@ -215,7 +116,7 @@ uint8_t udatetime_lt(udatetime_t* dt1, udatetime_t* dt2); * @param dt1 pointer to the second datetime * @return *dt1 <= *dt2 */ -uint8_t udatetime_le(udatetime_t* dt1, udatetime_t* dt2); +bool utz_udatetime_le(const utz_datetime_t* dt1, const utz_datetime_t* dt2); /** @brief returns *dt1 > *dt2 * @@ -223,7 +124,7 @@ uint8_t udatetime_le(udatetime_t* dt1, udatetime_t* dt2); * @param dt1 pointer to the second datetime * @return *dt1 > *dt2 */ -uint8_t udatetime_gt(udatetime_t* dt1, udatetime_t* dt2); +bool utz_udatetime_gt(const utz_datetime_t* dt1, const utz_datetime_t* dt2); /** @brief returns *dt1 >= *dt2 * @@ -231,110 +132,75 @@ uint8_t udatetime_gt(udatetime_t* dt1, udatetime_t* dt2); * @param dt1 pointer to the second datetime * @return *dt1 >= *dt2 */ -uint8_t udatetime_ge(udatetime_t* dt1, udatetime_t* dt2); +bool utz_udatetime_ge(const utz_datetime_t* dt1, const utz_datetime_t* dt2); -/**************************************************************************/ -/* zone rule functions */ -/**************************************************************************/ - -/** @brief unpack rule +/** @brief compare two datetime objects * - * @param rule_in pointer to packed rule - * @param cur_year year: 1 <= y <= 255 (2001 - 2255) - * @param rule_out pointer for the output unpacked rule - * @return void + * @return value less than, equal to, or greater than zero - comparison result */ -void unpack_rule(const urule_packed_t* rule_in, uint8_t cur_year, urule_t* rule_out); +int utz_udatetime_cmp(const utz_datetime_t* dt1, const utz_datetime_t* dt2); -/** @brief unpack rules that are active in the current year +/** @brief compare two offset objects * - * Note this assumes no two rules are active on the same day - * - * @param rules_in pointer to packed rules - * @param num_rules the number of rules in the array - * @param cur_year year: 1 <= y <= 255 (2001 - 2255) - * @param rules_out pointer for the output unpacked rules - * @return void + * @return value less than, equal to, or greater than zero - comparison result */ -void unpack_rules(const urule_packed_t* rules_in, uint8_t num_rules, uint8_t cur_year, urule_t* rules_out); +int utz_offset_cmp(const utz_offset_t* dt1, const utz_offset_t* dt2); -/** @brief get the rule that applies at datetime +/** @brief add given offset to a datetime * - * @param rules pointer to rules - * @param datetime the datetime to check rules for - * @return a pointer the the rule that applies + * @param offset must adhere to utz_offset_t contract, otherwise undefined behaviour. + * @return adjusted datetime */ -urule_t* get_active_rule(urule_t* rules, udatetime_t* datetime); +utz_datetime_t utz_udatetime_add(const utz_datetime_t* dt, const utz_offset_t *offset); -/** @brief get the offset for zone at datetime, taking into account daylight savings time rules +/** @brief subtract given offset to a datetime * - * @param zone pointer to zone - * @param datetime the datetime to check rules for - * @param offset offset for zone at datetime - * @return abbreviation letter + * @param offset must adhere to utz_offset_t contract, otherwise undefined behaviour. + * @return adjusted datetime */ -char get_current_offset(uzone_t* zone, udatetime_t* datetime, uoffset_t* offset); +utz_datetime_t utz_udatetime_sub(const utz_datetime_t* dt, const utz_offset_t *offset); -/** @brief unpack timezone - * - * @param name the name of the timezone - * @param zone_in pointer to input packed zone - * @param zone_in pointer to output unpacked zone - * @return void - */ -void unpack_zone(const uzone_packed_t* zone_in, const char* name, uzone_t* zone_out); +/**************************************************************************/ +/* zone rule functions */ +/**************************************************************************/ -/** @brief advance pointer to list and returns index to the the prev item +/** @brief get the offset for zone at datetime, taking into account daylight savings time rules * - * @param list pointer - * @return index into the array for the item before advancement + * @param zone pointer to zone + * @param datetime the datetime (UTC) to check rules for + * @param offset offset for zone at datetime + * @return abbreviation letters string */ -uint8_t get_next(const char** list); +const char *utz_get_current_offset(const utz_zone_t* zone, const utz_datetime_t* datetime, utz_offset_t* offset); /** @brief lookup a zone via zone_names * * @param name the name of the zone to find * @param zone_out pointer for zone found - * @return void + * @return true if zone was found, false otherwise */ -void get_zone_by_name(char* name, uzone_t* zone_out); +bool utz_get_zone_by_name(const char* name, utz_zone_t* zone_out); -int16_t udatetime_cmp(udatetime_t* dt1, udatetime_t* dt2); +/** @brief iterate through timezone names. + * + * @param prev pointer to a timezone name in the utz_zone_names table. + * @return pointer to the next timezone name in the table or NULL if no names left. + */ +const char *utz_next_zone_name(const char *prev); #ifdef UTZ_MKTIME -uint32_t umktime(udatetime_t* dt); -#endif -/**************************************************************************/ -/* globals */ -/**************************************************************************/ - -/** @brief cached rules for the zone and year from the last call of get_current_offset */ -extern urule_t cached_rules[MAX_CURRENT_RULES]; - -/** @brief lookup table name of the days of week */ -extern const uint8_t _days_of_week_idx[]; -extern const char _days_of_week[]; -extern const uint8_t _months_of_year_idx[]; -extern const char _months_of_year[]; - -//FIXME -const char* get_index(const char* list, uint8_t i); - -#ifdef UTZ_GLOBAL_COUNTERS -static uint8_t utz_i, utz_j; -static uint16_t utz_k; +uint32_t utz_mktime(const utz_datetime_t* dt); #endif - -#define days_of_week(n) (&_days_of_week[_days_of_week_idx[n]]) -#define months_of_year(n) (&_months_of_year[_months_of_year_idx[n]]) - /**************************************************************************/ /* zones */ /**************************************************************************/ -extern const urule_packed_t zone_rules[]; -extern const uzone_packed_t zone_defns[]; -extern const char zone_abrevs[]; -extern const unsigned char zone_names[]; +/** @brief default timezone, UTC. */ +extern const utz_zone_t utz_zone_default; + +extern const utz_rule_packed_t utz_zone_rules[]; +extern const uzone_packed_t utz_zone_defns[]; +extern const char utz_zone_abrevs[]; +extern const char utz_zone_names[]; #endif /* _UTZ_H */ diff --git a/whitelist.txt b/whitelist.txt index 9201cd7..9a21d16 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -15,7 +15,6 @@ America/Caracas America/Chicago America/Chihuahua America/Costa_Rica -America/DallasFort_Worth America/Denver America/Godthab America/Guadalajara @@ -57,6 +56,7 @@ Asia/Delhi Asia/Dubai Asia/Guangzhou Asia/Hangzhou +Asia/Harbin Asia/Hong_Kong Asia/Hyderabad Asia/Irkutsk @@ -83,6 +83,7 @@ Asia/Qingdao Asia/Rangoon Asia/Seoul Asia/Shanghai +Asia/Shantou Asia/Shenyang Asia/Shenzhen Asia/Surat @@ -108,6 +109,7 @@ Australia/Darwin Australia/Hobart Australia/Perth Australia/Sydney +Etc/Universal Europe/Amsterdam Europe/Ankara Europe/Athens diff --git a/zones.c b/zones.c index ea66148..da7bdc0 100644 --- a/zones.c +++ b/zones.c @@ -1,105 +1,95 @@ #include "utz.h" -const urule_packed_t zone_rules[50] = { -{ 8, 255, 7, 1, 1, 2, 0, 1, 4, 0}, // AN 2008 max - Apr Sun>=1 2:00s 0 S -{ 8, 255, 7, 1, 1, 2, 0, 2, 10, 1}, // AN 2008 max - Oct Sun>=1 2:00s 1:00 D -{ 8, 255, 7, 1, 1, 2, 0, 1, 4, 0}, // AS 2008 max - Apr Sun>=1 2:00s 0 S -{ 8, 255, 7, 1, 1, 2, 0, 2, 10, 1}, // AS 2008 max - Oct Sun>=1 2:00s 1:00 D -{ 8, 255, 7, 1, 1, 2, 0, 1, 4, 0}, // AT 2008 max - Apr Sun>=1 2:00s 0 S -{ 1, 255, 7, 1, 1, 2, 0, 2, 10, 1}, // AT 2001 max - Oct Sun>=1 2:00s 1:00 D -{ 16, 22, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2016 2022 - Feb Sun>=15 0:00 0 - -{ 23, 23, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2023 only - Feb Sun>=22 0:00 0 - -{ 24, 25, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2024 2025 - Feb Sun>=15 0:00 0 - -{ 26, 26, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2026 only - Feb Sun>=22 0:00 0 - -{ 27, 33, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2027 2033 - Feb Sun>=15 0:00 0 - -{ 34, 34, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2034 only - Feb Sun>=22 0:00 0 - -{ 35, 36, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2035 2036 - Feb Sun>=15 0:00 0 - -{ 37, 37, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2037 only - Feb Sun>=22 0:00 0 - -{ 38, 255, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2038 max - Feb Sun>=15 0:00 0 - -{ 8, 255, 7, 15, 1, 0, 0, 1, 10, 1}, // Brazil 2008 max - Oct Sun>=15 0:00 1:00 S -{ 7, 255, 7, 8, 1, 2, 0, 2, 3, 1}, // Canada 2007 max - Mar Sun>=8 2:00 1:00 D -{ 7, 255, 7, 1, 1, 1, 0, 1, 11, 0}, // Canada 2007 max - Nov Sun>=1 2:00 0 S -{ 16, 255, 7, 9, 0, 3, 0, 0, 5, 0}, // Chile 2016 max - May Sun>=9 3:00u 0 - -{ 16, 255, 7, 9, 0, 4, 0, 1, 8, 1}, // Chile 2016 max - Aug Sun>=9 4:00u 1:00 S -{ 0, 255, 7, 0, 0, 1, 0, 1, 3, 1}, // EU 1981 max - Mar lastSun 1:00u 1:00 S -{ 0, 255, 7, 0, 0, 1, 0, 0, 10, 0}, // EU 1996 max - Oct lastSun 1:00u 0 - -{ 17, 19, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2017 2019 - Mar 22 0:00 1:00 D -{ 20, 20, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2020 only - Mar 21 0:00 1:00 D -{ 21, 23, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2021 2023 - Mar 22 0:00 1:00 D -{ 24, 24, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2024 only - Mar 21 0:00 1:00 D -{ 25, 27, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2025 2027 - Mar 22 0:00 1:00 D -{ 28, 29, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2028 2029 - Mar 21 0:00 1:00 D -{ 30, 31, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2030 2031 - Mar 22 0:00 1:00 D -{ 32, 33, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2032 2033 - Mar 21 0:00 1:00 D -{ 34, 35, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2034 2035 - Mar 22 0:00 1:00 D -{ 36, 255, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2036 max - Mar 21 0:00 1:00 D -{ 17, 19, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2017 2019 - Sep 22 0:00 0 S -{ 20, 20, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2020 only - Sep 21 0:00 0 S -{ 21, 23, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2021 2023 - Sep 22 0:00 0 S -{ 24, 24, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2024 only - Sep 21 0:00 0 S -{ 25, 27, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2025 2027 - Sep 22 0:00 0 S -{ 28, 29, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2028 2029 - Sep 21 0:00 0 S -{ 30, 31, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2030 2031 - Sep 22 0:00 0 S -{ 32, 33, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2032 2033 - Sep 21 0:00 0 S -{ 34, 35, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2034 2035 - Sep 22 0:00 0 S -{ 36, 255, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2036 max - Sep 21 0:00 0 S -{ 2, 255, 7, 1, 1, 2, 0, 2, 4, 1}, // Mexico 2002 max - Apr Sun>=1 2:00 1:00 D -{ 2, 255, 7, 0, 1, 1, 0, 1, 10, 0}, // Mexico 2002 max - Oct lastSun 2:00 0 S -{ 8, 255, 7, 1, 1, 2, 0, 1, 4, 0}, // NZ 2008 max - Apr Sun>=1 2:00s 0 S -{ 7, 255, 7, 0, 1, 2, 0, 2, 9, 1}, // NZ 2007 max - Sep lastSun 2:00s 1:00 D -{ 7, 255, 7, 8, 1, 2, 0, 2, 3, 1}, // US 2007 max - Mar Sun>=8 2:00 1:00 D -{ 7, 255, 7, 1, 1, 1, 0, 1, 11, 0}, // US 2007 max - Nov Sun>=1 2:00 0 S -{ 13, 255, 5, 23, 1, 2, 0, 2, 3, 1}, // Zion 2013 max - Mar Fri>=23 2:00 1:00 D -{ 13, 255, 7, 0, 1, 1, 0, 1, 10, 0}, // Zion 2013 max - Oct lastSun 2:00 0 S +const utz_rule_packed_t utz_zone_rules[39] = { +{{ 8}, {255}, 7, 1, 1, 2, 0, 1, 4, 0}, // AN 2008 max - Apr Sun>=1 2:00s 0 S +{{ 8}, {255}, 7, 1, 1, 2, 0, 2, 10, 1}, // AN 2008 max - Oct Sun>=1 2:00s 1:00 D +{{ 8}, {255}, 7, 1, 1, 2, 0, 1, 4, 0}, // AS 2008 max - Apr Sun>=1 2:00s 0 S +{{ 8}, {255}, 7, 1, 1, 2, 0, 2, 10, 1}, // AS 2008 max - Oct Sun>=1 2:00s 1:00 D +{{ 8}, {255}, 7, 1, 1, 2, 0, 1, 4, 0}, // AT 2008 max - Apr Sun>=1 2:00s 0 S +{{ 1}, {255}, 7, 1, 1, 2, 0, 2, 10, 1}, // AT 2001 max - Oct Sun>=1 2:00s 1:00 D +{{ 26}, { 26}, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2026 only - Feb Sun>=22 0:00 0 - +{{ 27}, { 33}, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2027 2033 - Feb Sun>=15 0:00 0 - +{{ 34}, { 34}, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2034 only - Feb Sun>=22 0:00 0 - +{{ 35}, { 36}, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2035 2036 - Feb Sun>=15 0:00 0 - +{{ 37}, { 37}, 6, 21, 1, 23, 0, 0, 2, 0}, // Brazil 2037 only - Feb Sun>=22 0:00 0 - +{{ 38}, {255}, 6, 14, 1, 23, 0, 0, 2, 0}, // Brazil 2038 max - Feb Sun>=15 0:00 0 - +{{ 8}, {255}, 7, 15, 1, 0, 0, 1, 10, 1}, // Brazil 2008 max - Oct Sun>=15 0:00 1:00 S +{{ 7}, {255}, 7, 8, 1, 2, 0, 2, 3, 1}, // Canada 2007 max - Mar Sun>=8 2:00 1:00 D +{{ 7}, {255}, 7, 1, 1, 1, 0, 1, 11, 0}, // Canada 2007 max - Nov Sun>=1 2:00 0 S +{{ 16}, {255}, 7, 9, 0, 3, 0, 0, 5, 0}, // Chile 2016 max - May Sun>=9 3:00u 0 - +{{ 16}, {255}, 7, 9, 0, 4, 0, 1, 8, 1}, // Chile 2016 max - Aug Sun>=9 4:00u 1:00 S +{{ 0}, {255}, 7, 0, 0, 1, 0, 1, 3, 1}, // EU 1981 max - Mar lastSun 1:00u 1:00 S +{{ 0}, {255}, 7, 0, 0, 1, 0, 0, 10, 0}, // EU 1996 max - Oct lastSun 1:00u 0 - +{{ 25}, { 27}, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2025 2027 - Mar 22 0:00 1:00 D +{{ 28}, { 29}, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2028 2029 - Mar 21 0:00 1:00 D +{{ 30}, { 31}, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2030 2031 - Mar 22 0:00 1:00 D +{{ 32}, { 33}, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2032 2033 - Mar 21 0:00 1:00 D +{{ 34}, { 35}, 0, 22, 1, 0, 0, 2, 3, 1}, // Iran 2034 2035 - Mar 22 0:00 1:00 D +{{ 36}, {255}, 0, 21, 1, 0, 0, 2, 3, 1}, // Iran 2036 max - Mar 21 0:00 1:00 D +{{ 25}, { 27}, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2025 2027 - Sep 22 0:00 0 S +{{ 28}, { 29}, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2028 2029 - Sep 21 0:00 0 S +{{ 30}, { 31}, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2030 2031 - Sep 22 0:00 0 S +{{ 32}, { 33}, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2032 2033 - Sep 21 0:00 0 S +{{ 34}, { 35}, 0, 21, 1, 23, 0, 1, 9, 0}, // Iran 2034 2035 - Sep 22 0:00 0 S +{{ 36}, {255}, 0, 20, 1, 23, 0, 1, 9, 0}, // Iran 2036 max - Sep 21 0:00 0 S +{{ 2}, {255}, 7, 1, 1, 2, 0, 2, 4, 1}, // Mexico 2002 max - Apr Sun>=1 2:00 1:00 D +{{ 2}, {255}, 7, 0, 1, 1, 0, 1, 10, 0}, // Mexico 2002 max - Oct lastSun 2:00 0 S +{{ 8}, {255}, 7, 1, 1, 2, 0, 1, 4, 0}, // NZ 2008 max - Apr Sun>=1 2:00s 0 S +{{ 7}, {255}, 7, 0, 1, 2, 0, 2, 9, 1}, // NZ 2007 max - Sep lastSun 2:00s 1:00 D +{{ 7}, {255}, 7, 8, 1, 2, 0, 2, 3, 1}, // US 2007 max - Mar Sun>=8 2:00 1:00 D +{{ 7}, {255}, 7, 1, 1, 1, 0, 1, 11, 0}, // US 2007 max - Nov Sun>=1 2:00 0 S +{{ 13}, {255}, 5, 23, 1, 2, 0, 2, 3, 1}, // Zion 2013 max - Mar Fri>=23 2:00 1:00 D +{{ 13}, {255}, 7, 0, 1, 1, 0, 1, 10, 0}, // Zion 2013 max - Oct lastSun 2:00 0 S }; -const char zone_abrevs[209] = { +const char utz_zone_abrevs[213] = { 'E','E','S','T','\0', 'W','A','T','\0', 'C','A','T','\0', 'E','A','T','\0', -'A','K','%','c','T','\0', +'A','K','%','s','T','\0', 'A','R','S','T','\0', -'C','%','c','T','\0', -'M','%','c','T','\0', -'A','%','c','T','\0', -'P','%','c','T','\0', +'C','%','s','T','\0', +'M','%','s','T','\0', +'A','%','s','T','\0', +'P','%','s','T','\0', 'A','M','T','\0', -'E','%','c','T','\0', +'E','%','s','T','\0', 'M','S','T','\0', 'C','S','T','\0', -'C','L','%','c','T','\0', -'B','R','%','c','T','\0', -'N','%','c','T','\0', +'C','L','%','s','T','\0', +'B','R','%','s','T','\0', +'N','%','s','T','\0', 'I','C','T','\0', 'G','S','T','\0', 'H','K','S','T','\0', -'I','%','c','T','\0', +'I','%','s','T','\0', 'P','K','S','T','\0', 'N','P','T','\0', 'I','S','T','\0', 'A','S','T','\0', 'K','S','T','\0', -'I','R','%','c','T','\0', +'I','R','%','s','T','\0', 'J','S','T','\0', 'M','M','T','\0', -'A','C','%','c','T','\0', +'A','C','%','s','T','\0', 'A','E','S','T','\0', 'A','C','S','T','\0', -'A','E','%','c','T','\0', +'A','E','%','s','T','\0', 'A','W','S','T','\0', -'C','E','%','c','T','\0', -'E','E','%','c','T','\0', +'U','T','C','\0', +'C','E','%','s','T','\0', +'E','E','%','s','T','\0', '+','0','3','\0', 'G','M','T','/','B','S','T','\0', 'M','S','K','\0', -'N','Z','%','c','T','\0', +'N','Z','%','s','T','\0', 'C','h','S','T','\0', 'H','S','T','\0', 'S','S','T','\0', }; -const uzone_packed_t zone_defns[46] = { +const uzone_packed_t utz_zone_defns[47] = { // Africa/Cairo 2:00 Egypt EE%sT { 8, 0, 0, 0}, // Africa/Lagos 1:00 - WAT @@ -109,34 +99,34 @@ const uzone_packed_t zone_defns[46] = { // Africa/Nairobi 3:00 - EAT { 12, 0, 0, 13}, // America/Anchorage -9:00 US AK%sT -{-36, 46, 2, 17}, +{-36, 35, 2, 17}, // America/Argentina/Buenos_Aires -3:00 Arg AR%sT {-12, 0, 0, 23}, // America/Chicago -6:00 US C%sT -{-24, 46, 2, 28}, +{-24, 35, 2, 28}, // America/Denver -7:00 US M%sT -{-28, 46, 2, 33}, +{-28, 35, 2, 33}, // America/Halifax -4:00 Canada A%sT -{-16, 16, 2, 38}, +{-16, 13, 2, 38}, // America/Los_Angeles -8:00 US P%sT // America/Tijuana -8:00 US P%sT -{-32, 46, 2, 43}, +{-32, 35, 2, 43}, // America/Manaus -4:00 - AMT {-16, 0, 0, 48}, // America/Mexico_City -6:00 Mexico C%sT -{-24, 42, 2, 28}, +{-24, 31, 2, 28}, // America/New_York -5:00 US E%sT -{-20, 46, 2, 52}, +{-20, 35, 2, 52}, // America/Phoenix -7:00 - MST {-28, 0, 0, 57}, // America/Regina -6:00 - CST {-24, 0, 0, 61}, // America/Santiago -4:00 Chile CL%sT -{-16, 18, 2, 65}, +{-16, 15, 2, 65}, // America/Sao_Paulo -3:00 Brazil BR%sT -{-12, 6, 10, 71}, +{-12, 6, 7, 71}, // America/St_Johns -3:30 Canada N%sT -{-10, 16, 2, 77}, +{-14, 13, 2, 77}, // Asia/Bangkok 7:00 - ICT { 28, 0, 0, 82}, // Asia/Dubai 4:00 - GST @@ -144,7 +134,7 @@ const uzone_packed_t zone_defns[46] = { // Asia/Hong_Kong 8:00 HK HK%sT { 32, 0, 0, 90}, // Asia/Jerusalem 2:00 Zion I%sT -{ 8, 48, 2, 95}, +{ 8, 37, 2, 95}, // Asia/Karachi 5:00 Pakistan PK%sT { 20, 0, 0, 100}, // Asia/Kathmandu 5:45 - NPT @@ -159,7 +149,7 @@ const uzone_packed_t zone_defns[46] = { // Asia/Taipei 8:00 Taiwan C%sT { 32, 0, 0, 61}, // Asia/Tehran 3:30 Iran IR%sT -{ 14, 22, 20, 121}, +{ 14, 19, 12, 121}, // Asia/Tokyo 9:00 Japan J%sT { 36, 0, 0, 127}, // Asia/Yangon 6:30 - MMT @@ -176,43 +166,45 @@ const uzone_packed_t zone_defns[46] = { { 32, 0, 0, 157}, // Australia/Sydney 10:00 AN AE%sT { 40, 0, 2, 151}, +// Etc/UTC 0 - UTC +{ 0, 0, 0, 162}, // Europe/Belgrade 1:00 EU CE%sT // Europe/Berlin 1:00 EU CE%sT // Europe/Madrid 1:00 EU CE%sT // Europe/Rome 1:00 EU CE%sT -{ 4, 20, 2, 162}, +{ 4, 17, 2, 166}, // Europe/Helsinki 2:00 EU EE%sT -{ 8, 20, 2, 168}, +{ 8, 17, 2, 172}, // Europe/Istanbul 3:00 - +03 -{ 12, 0, 0, 174}, +{ 12, 0, 0, 178}, // Europe/London 0:00 EU GMT/BST -{ 0, 20, 2, 178}, +{ 0, 17, 2, 182}, // Europe/Moscow 3:00 - MSK -{ 12, 0, 0, 186}, +{ 12, 0, 0, 190}, // Pacific/Auckland 12:00 NZ NZ%sT -{ 48, 44, 2, 190}, +{ 48, 33, 2, 194}, // Pacific/Guam 10:00 - ChST -{ 40, 0, 0, 196}, +{ 40, 0, 0, 200}, // Pacific/Honolulu -10:00 - HST -{-40, 0, 0, 201}, +{-40, 0, 0, 205}, // Pacific/Pago_Pago -11:00 - SST -{-44, 0, 0, 205}, +{-44, 0, 0, 209}, }; -const unsigned char zone_names[961] = { +const char utz_zone_names[972] = { 'A','d','e','l','a','i','d','e','\0', 31, // Adelaide 'A','h','m','e','d','a','b','a','d','\0', 24, // Ahmedabad 'A','n','c','h','o','r','a','g','e','\0', 4, // Anchorage - 'A','n','k','a','r','a','\0', 39, // Ankara + 'A','n','k','a','r','a','\0', 40, // Ankara 'A','t','l','a','n','t','a','\0', 12, // Atlanta - 'A','u','c','k','l','a','n','d','\0', 42, // Auckland + 'A','u','c','k','l','a','n','d','\0', 43, // Auckland 'B','a','n','g','a','l','o','r','e','\0', 24, // Bangalore 'B','a','n','g','k','o','k','\0', 18, // Bangkok - 'B','a','r','c','e','l','o','n','a','\0', 37, // Barcelona + 'B','a','r','c','e','l','o','n','a','\0', 38, // Barcelona 'B','e','i','j','i','n','g','\0', 27, // Beijing - 'B','e','l','g','r','a','d','e','\0', 37, // Belgrade + 'B','e','l','g','r','a','d','e','\0', 38, // Belgrade 'B','e','l','o',' ','H','o','r','i','z','o','n','t','e','\0', 16, // Belo Horizonte - 'B','e','r','l','i','n','\0', 37, // Berlin + 'B','e','r','l','i','n','\0', 38, // Berlin 'B','o','s','t','o','n','\0', 12, // Boston 'B','r','a','z','z','a','v','i','l','l','e','\0', 1, // Brazzaville 'B','r','i','s','b','a','n','e','\0', 32, // Brisbane @@ -224,26 +216,26 @@ const unsigned char zone_names[961] = { 'C','h','e','n','n','a','i','\0', 24, // Chennai 'C','h','i','c','a','g','o','\0', 6, // Chicago 'C','h','o','n','g','q','i','n','g','\0', 27, // Chongqing - 'D','a','l','l','a','s','F','o','r','t',' ','W','o','r','t','h','\0', 6, // DallasFort Worth 'D','a','r','w','i','n','\0', 33, // Darwin 'D','e','l','h','i','\0', 24, // Delhi 'D','e','n','v','e','r','\0', 7, // Denver 'D','u','b','a','i','\0', 19, // Dubai 'G','u','a','d','a','l','a','j','a','r','a','\0', 11, // Guadalajara - 'G','u','a','m','\0', 43, // Guam + 'G','u','a','m','\0', 44, // Guam 'G','u','a','n','g','z','h','o','u','\0', 27, // Guangzhou 'H','a','l','i','f','a','x','\0', 8, // Halifax - 'H','a','m','b','u','r','g','\0', 37, // Hamburg + 'H','a','m','b','u','r','g','\0', 38, // Hamburg 'H','a','n','g','z','h','o','u','\0', 27, // Hangzhou 'H','a','r','a','r','e','\0', 2, // Harare - 'H','e','l','s','i','n','k','i','\0', 38, // Helsinki + 'H','a','r','b','i','n','\0', 27, // Harbin + 'H','e','l','s','i','n','k','i','\0', 39, // Helsinki 'H','o','b','a','r','t','\0', 34, // Hobart 'H','o','n','g',' ','K','o','n','g','\0', 20, // Hong Kong - 'H','o','n','o','l','u','l','u','\0', 44, // Honolulu + 'H','o','n','o','l','u','l','u','\0', 45, // Honolulu 'H','o','u','s','t','o','n','\0', 6, // Houston 'H','y','d','e','r','a','b','a','d','\0', 24, // Hyderabad 'I','n','l','a','n','d',' ','E','m','p','i','r','e','\0', 9, // Inland Empire - 'I','s','t','a','n','b','u','l','\0', 39, // Istanbul + 'I','s','t','a','n','b','u','l','\0', 40, // Istanbul 'J','e','r','u','s','a','l','e','m','\0', 21, // Jerusalem 'J','i','n','a','n','\0', 27, // Jinan 'K','a','r','a','c','h','i','\0', 22, // Karachi @@ -251,17 +243,17 @@ const unsigned char zone_names[961] = { 'K','o','l','k','a','t','a','\0', 24, // Kolkata 'K','u','w','a','i','t','\0', 25, // Kuwait 'L','a','h','o','r','e','\0', 22, // Lahore - 'L','o','n','d','o','n','\0', 40, // London + 'L','o','n','d','o','n','\0', 41, // London 'L','o','s',' ','A','n','g','e','l','e','s','\0', 9, // Los Angeles - 'M','a','d','r','i','d','\0', 37, // Madrid + 'M','a','d','r','i','d','\0', 38, // Madrid 'M','a','n','a','u','s','\0', 10, // Manaus 'M','e','x','i','c','o',' ','C','i','t','y','\0', 11, // Mexico City 'M','i','a','m','i','\0', 12, // Miami - 'M','i','d','w','a','y','\0', 45, // Midway - 'M','i','l','a','n','\0', 37, // Milan - 'M','o','s','c','o','w','\0', 41, // Moscow + 'M','i','d','w','a','y','\0', 46, // Midway + 'M','i','l','a','n','\0', 38, // Milan + 'M','o','s','c','o','w','\0', 42, // Moscow 'M','u','m','b','a','i','\0', 24, // Mumbai - 'M','u','n','i','c','h','\0', 37, // Munich + 'M','u','n','i','c','h','\0', 38, // Munich 'N','a','g','o','y','a','\0', 29, // Nagoya 'N','a','i','r','o','b','i','\0', 3, // Nairobi 'N','a','n','c','h','a','n','g','\0', 27, // Nanchang @@ -275,19 +267,20 @@ const unsigned char zone_names[961] = { 'Q','i','n','g','d','a','o','\0', 27, // Qingdao 'R','a','n','g','o','o','n','\0', 30, // Rangoon 'R','e','g','i','n','a','\0', 14, // Regina - 'R','h','i','n','e','-','R','u','h','r','\0', 37, // Rhine-Ruhr + 'R','h','i','n','e','-','R','u','h','r','\0', 38, // Rhine-Ruhr 'R','i','o',' ','d','e',' ','J','a','n','e','i','r','o','\0', 16, // Rio de Janeiro - 'R','o','m','e','\0', 37, // Rome + 'R','o','m','e','\0', 38, // Rome 'S','a','n',' ','F','r','a','n','c','i','s','c','o','\0', 9, // San Francisco 'S','a','n','t','i','a','g','o','\0', 15, // Santiago 'S','a','o',' ','P','a','u','l','o','\0', 16, // Sao Paulo - 'S','a','r','a','j','e','v','o','\0', 37, // Sarajevo + 'S','a','r','a','j','e','v','o','\0', 38, // Sarajevo 'S','e','o','u','l','\0', 26, // Seoul 'S','h','a','n','g','h','a','i','\0', 27, // Shanghai + 'S','h','a','n','t','o','u','\0', 27, // Shantou 'S','h','e','n','y','a','n','g','\0', 27, // Shenyang 'S','h','e','n','z','h','e','n','\0', 27, // Shenzhen 'S','t',' ','J','o','h','n','s','\0', 17, // St Johns - 'S','t','u','t','t','g','a','r','t','\0', 37, // Stuttgart + 'S','t','u','t','t','g','a','r','t','\0', 38, // Stuttgart 'S','u','r','a','t','\0', 24, // Surat 'S','y','d','n','e','y','\0', 36, // Sydney 'T','a','i','p','e','i','\0', 27, // Taipei @@ -295,9 +288,11 @@ const unsigned char zone_names[961] = { 'T','i','a','n','j','i','n','\0', 27, // Tianjin 'T','i','j','u','a','n','a','\0', 9, // Tijuana 'T','o','k','y','o','\0', 29, // Tokyo + 'U','n','i','v','e','r','s','a','l','\0', 37, // Universal 'W','a','s','h','i','n','g','t','o','n',',',' ','D','.','C','.','\0', 12, // Washington, D.C. 'W','e','n','z','h','o','u','\0', 27, // Wenzhou 'W','u','h','a','n','\0', 27, // Wuhan 'X','i','\'','a','n','\0', 27, // Xi'an 'Z','h','e','n','g','z','h','o','u','\0', 27, // Zhengzhou + 0 }; diff --git a/zones.h b/zones.h index e85ab97..9584607 100644 --- a/zones.h +++ b/zones.h @@ -1,115 +1,119 @@ #ifndef _ZONES_H #define _ZONES_H -const urule_packed_t zone_rules[50]; -const char zone_abrevs[209]; +#include "types.h" + +extern const utz_rule_packed_t utz_zone_rules[39]; +extern const char utz_zone_abrevs[213]; #define MAX_ABREV_FORMATTER_LEN 7 -const uzone_packed_t zone_defns[46]; -#define UTZ_ADELAIDE &zone_defns[ 31] -#define UTZ_AHMEDABAD &zone_defns[ 24] -#define UTZ_ANCHORAGE &zone_defns[ 4] -#define UTZ_ANKARA &zone_defns[ 39] -#define UTZ_ATLANTA &zone_defns[ 12] -#define UTZ_AUCKLAND &zone_defns[ 42] -#define UTZ_BANGALORE &zone_defns[ 24] -#define UTZ_BANGKOK &zone_defns[ 18] -#define UTZ_BARCELONA &zone_defns[ 37] -#define UTZ_BEIJING &zone_defns[ 27] -#define UTZ_BELGRADE &zone_defns[ 37] -#define UTZ_BELO_HORIZONTE &zone_defns[ 16] -#define UTZ_BERLIN &zone_defns[ 37] -#define UTZ_BOSTON &zone_defns[ 12] -#define UTZ_BRAZZAVILLE &zone_defns[ 1] -#define UTZ_BRISBANE &zone_defns[ 32] -#define UTZ_BUENOS_AIRES &zone_defns[ 5] -#define UTZ_CAIRO &zone_defns[ 0] -#define UTZ_CALCUTTA &zone_defns[ 24] -#define UTZ_CHANGZHOU &zone_defns[ 27] -#define UTZ_CHENGDU &zone_defns[ 27] -#define UTZ_CHENNAI &zone_defns[ 24] -#define UTZ_CHICAGO &zone_defns[ 6] -#define UTZ_CHONGQING &zone_defns[ 27] -#define UTZ_DALLASFORT_WORTH &zone_defns[ 6] -#define UTZ_DARWIN &zone_defns[ 33] -#define UTZ_DELHI &zone_defns[ 24] -#define UTZ_DENVER &zone_defns[ 7] -#define UTZ_DUBAI &zone_defns[ 19] -#define UTZ_GUADALAJARA &zone_defns[ 11] -#define UTZ_GUAM &zone_defns[ 43] -#define UTZ_GUANGZHOU &zone_defns[ 27] -#define UTZ_HALIFAX &zone_defns[ 8] -#define UTZ_HAMBURG &zone_defns[ 37] -#define UTZ_HANGZHOU &zone_defns[ 27] -#define UTZ_HARARE &zone_defns[ 2] -#define UTZ_HELSINKI &zone_defns[ 38] -#define UTZ_HOBART &zone_defns[ 34] -#define UTZ_HONG_KONG &zone_defns[ 20] -#define UTZ_HONOLULU &zone_defns[ 44] -#define UTZ_HOUSTON &zone_defns[ 6] -#define UTZ_HYDERABAD &zone_defns[ 24] -#define UTZ_INLAND_EMPIRE &zone_defns[ 9] -#define UTZ_ISTANBUL &zone_defns[ 39] -#define UTZ_JERUSALEM &zone_defns[ 21] -#define UTZ_JINAN &zone_defns[ 27] -#define UTZ_KARACHI &zone_defns[ 22] -#define UTZ_KATMANDU &zone_defns[ 23] -#define UTZ_KOLKATA &zone_defns[ 24] -#define UTZ_KUWAIT &zone_defns[ 25] -#define UTZ_LAHORE &zone_defns[ 22] -#define UTZ_LONDON &zone_defns[ 40] -#define UTZ_LOS_ANGELES &zone_defns[ 9] -#define UTZ_MADRID &zone_defns[ 37] -#define UTZ_MANAUS &zone_defns[ 10] -#define UTZ_MEXICO_CITY &zone_defns[ 11] -#define UTZ_MIAMI &zone_defns[ 12] -#define UTZ_MIDWAY &zone_defns[ 45] -#define UTZ_MILAN &zone_defns[ 37] -#define UTZ_MOSCOW &zone_defns[ 41] -#define UTZ_MUMBAI &zone_defns[ 24] -#define UTZ_MUNICH &zone_defns[ 37] -#define UTZ_NAGOYA &zone_defns[ 29] -#define UTZ_NAIROBI &zone_defns[ 3] -#define UTZ_NANCHANG &zone_defns[ 27] -#define UTZ_NANJING &zone_defns[ 27] -#define UTZ_NEW_YORK &zone_defns[ 12] -#define UTZ_OSAKA &zone_defns[ 29] -#define UTZ_PERTH &zone_defns[ 35] -#define UTZ_PHILADELPHIA &zone_defns[ 12] -#define UTZ_PHOENIX &zone_defns[ 13] -#define UTZ_PUNE &zone_defns[ 24] -#define UTZ_QINGDAO &zone_defns[ 27] -#define UTZ_RANGOON &zone_defns[ 30] -#define UTZ_REGINA &zone_defns[ 14] -#define UTZ_RHINERUHR &zone_defns[ 37] -#define UTZ_RIO_DE_JANEIRO &zone_defns[ 16] -#define UTZ_ROME &zone_defns[ 37] -#define UTZ_SAN_FRANCISCO &zone_defns[ 9] -#define UTZ_SANTIAGO &zone_defns[ 15] -#define UTZ_SAO_PAULO &zone_defns[ 16] -#define UTZ_SARAJEVO &zone_defns[ 37] -#define UTZ_SEOUL &zone_defns[ 26] -#define UTZ_SHANGHAI &zone_defns[ 27] -#define UTZ_SHENYANG &zone_defns[ 27] -#define UTZ_SHENZHEN &zone_defns[ 27] -#define UTZ_ST_JOHNS &zone_defns[ 17] -#define UTZ_STUTTGART &zone_defns[ 37] -#define UTZ_SURAT &zone_defns[ 24] -#define UTZ_SYDNEY &zone_defns[ 36] -#define UTZ_TAIPEI &zone_defns[ 27] -#define UTZ_TEHRAN &zone_defns[ 28] -#define UTZ_TIANJIN &zone_defns[ 27] -#define UTZ_TIJUANA &zone_defns[ 9] -#define UTZ_TOKYO &zone_defns[ 29] -#define UTZ_WASHINGTON_DC &zone_defns[ 12] -#define UTZ_WENZHOU &zone_defns[ 27] -#define UTZ_WUHAN &zone_defns[ 27] -#define UTZ_XIAN &zone_defns[ 27] -#define UTZ_ZHENGZHOU &zone_defns[ 27] +extern const uzone_packed_t utz_zone_defns[47]; +#define UTZ_ADELAIDE &utz_zone_defns[ 31] +#define UTZ_AHMEDABAD &utz_zone_defns[ 24] +#define UTZ_ANCHORAGE &utz_zone_defns[ 4] +#define UTZ_ANKARA &utz_zone_defns[ 40] +#define UTZ_ATLANTA &utz_zone_defns[ 12] +#define UTZ_AUCKLAND &utz_zone_defns[ 43] +#define UTZ_BANGALORE &utz_zone_defns[ 24] +#define UTZ_BANGKOK &utz_zone_defns[ 18] +#define UTZ_BARCELONA &utz_zone_defns[ 38] +#define UTZ_BEIJING &utz_zone_defns[ 27] +#define UTZ_BELGRADE &utz_zone_defns[ 38] +#define UTZ_BELO_HORIZONTE &utz_zone_defns[ 16] +#define UTZ_BERLIN &utz_zone_defns[ 38] +#define UTZ_BOSTON &utz_zone_defns[ 12] +#define UTZ_BRAZZAVILLE &utz_zone_defns[ 1] +#define UTZ_BRISBANE &utz_zone_defns[ 32] +#define UTZ_BUENOS_AIRES &utz_zone_defns[ 5] +#define UTZ_CAIRO &utz_zone_defns[ 0] +#define UTZ_CALCUTTA &utz_zone_defns[ 24] +#define UTZ_CHANGZHOU &utz_zone_defns[ 27] +#define UTZ_CHENGDU &utz_zone_defns[ 27] +#define UTZ_CHENNAI &utz_zone_defns[ 24] +#define UTZ_CHICAGO &utz_zone_defns[ 6] +#define UTZ_CHONGQING &utz_zone_defns[ 27] +#define UTZ_DARWIN &utz_zone_defns[ 33] +#define UTZ_DELHI &utz_zone_defns[ 24] +#define UTZ_DENVER &utz_zone_defns[ 7] +#define UTZ_DUBAI &utz_zone_defns[ 19] +#define UTZ_GUADALAJARA &utz_zone_defns[ 11] +#define UTZ_GUAM &utz_zone_defns[ 44] +#define UTZ_GUANGZHOU &utz_zone_defns[ 27] +#define UTZ_HALIFAX &utz_zone_defns[ 8] +#define UTZ_HAMBURG &utz_zone_defns[ 38] +#define UTZ_HANGZHOU &utz_zone_defns[ 27] +#define UTZ_HARARE &utz_zone_defns[ 2] +#define UTZ_HARBIN &utz_zone_defns[ 27] +#define UTZ_HELSINKI &utz_zone_defns[ 39] +#define UTZ_HOBART &utz_zone_defns[ 34] +#define UTZ_HONG_KONG &utz_zone_defns[ 20] +#define UTZ_HONOLULU &utz_zone_defns[ 45] +#define UTZ_HOUSTON &utz_zone_defns[ 6] +#define UTZ_HYDERABAD &utz_zone_defns[ 24] +#define UTZ_INLAND_EMPIRE &utz_zone_defns[ 9] +#define UTZ_ISTANBUL &utz_zone_defns[ 40] +#define UTZ_JERUSALEM &utz_zone_defns[ 21] +#define UTZ_JINAN &utz_zone_defns[ 27] +#define UTZ_KARACHI &utz_zone_defns[ 22] +#define UTZ_KATMANDU &utz_zone_defns[ 23] +#define UTZ_KOLKATA &utz_zone_defns[ 24] +#define UTZ_KUWAIT &utz_zone_defns[ 25] +#define UTZ_LAHORE &utz_zone_defns[ 22] +#define UTZ_LONDON &utz_zone_defns[ 41] +#define UTZ_LOS_ANGELES &utz_zone_defns[ 9] +#define UTZ_MADRID &utz_zone_defns[ 38] +#define UTZ_MANAUS &utz_zone_defns[ 10] +#define UTZ_MEXICO_CITY &utz_zone_defns[ 11] +#define UTZ_MIAMI &utz_zone_defns[ 12] +#define UTZ_MIDWAY &utz_zone_defns[ 46] +#define UTZ_MILAN &utz_zone_defns[ 38] +#define UTZ_MOSCOW &utz_zone_defns[ 42] +#define UTZ_MUMBAI &utz_zone_defns[ 24] +#define UTZ_MUNICH &utz_zone_defns[ 38] +#define UTZ_NAGOYA &utz_zone_defns[ 29] +#define UTZ_NAIROBI &utz_zone_defns[ 3] +#define UTZ_NANCHANG &utz_zone_defns[ 27] +#define UTZ_NANJING &utz_zone_defns[ 27] +#define UTZ_NEW_YORK &utz_zone_defns[ 12] +#define UTZ_OSAKA &utz_zone_defns[ 29] +#define UTZ_PERTH &utz_zone_defns[ 35] +#define UTZ_PHILADELPHIA &utz_zone_defns[ 12] +#define UTZ_PHOENIX &utz_zone_defns[ 13] +#define UTZ_PUNE &utz_zone_defns[ 24] +#define UTZ_QINGDAO &utz_zone_defns[ 27] +#define UTZ_RANGOON &utz_zone_defns[ 30] +#define UTZ_REGINA &utz_zone_defns[ 14] +#define UTZ_RHINERUHR &utz_zone_defns[ 38] +#define UTZ_RIO_DE_JANEIRO &utz_zone_defns[ 16] +#define UTZ_ROME &utz_zone_defns[ 38] +#define UTZ_SAN_FRANCISCO &utz_zone_defns[ 9] +#define UTZ_SANTIAGO &utz_zone_defns[ 15] +#define UTZ_SAO_PAULO &utz_zone_defns[ 16] +#define UTZ_SARAJEVO &utz_zone_defns[ 38] +#define UTZ_SEOUL &utz_zone_defns[ 26] +#define UTZ_SHANGHAI &utz_zone_defns[ 27] +#define UTZ_SHANTOU &utz_zone_defns[ 27] +#define UTZ_SHENYANG &utz_zone_defns[ 27] +#define UTZ_SHENZHEN &utz_zone_defns[ 27] +#define UTZ_ST_JOHNS &utz_zone_defns[ 17] +#define UTZ_STUTTGART &utz_zone_defns[ 38] +#define UTZ_SURAT &utz_zone_defns[ 24] +#define UTZ_SYDNEY &utz_zone_defns[ 36] +#define UTZ_TAIPEI &utz_zone_defns[ 27] +#define UTZ_TEHRAN &utz_zone_defns[ 28] +#define UTZ_TIANJIN &utz_zone_defns[ 27] +#define UTZ_TIJUANA &utz_zone_defns[ 9] +#define UTZ_TOKYO &utz_zone_defns[ 29] +#define UTZ_UNIVERSAL &utz_zone_defns[ 37] +#define UTZ_WASHINGTON_DC &utz_zone_defns[ 12] +#define UTZ_WENZHOU &utz_zone_defns[ 27] +#define UTZ_WUHAN &utz_zone_defns[ 27] +#define UTZ_XIAN &utz_zone_defns[ 27] +#define UTZ_ZHENGZHOU &utz_zone_defns[ 27] -#define NUM_ZONE_NAMES 100 -#define MAX_ZONE_NAME_LEN 17 +#define UTZ_NUM_ZONE_NAMES 102 +#define UTZ_MAX_ZONE_NAME_LEN 17 -const unsigned char zone_names[961]; +extern const char utz_zone_names[972]; #endif /* _ZONES_H */ \ No newline at end of file