From 98b5f0188b690103b529452f4684d28e8e23ad29 Mon Sep 17 00:00:00 2001 From: Oleg Jukovec Date: Thu, 28 Jul 2022 15:39:45 +0300 Subject: [PATCH] datetime: add TZ support The patch adds timezone support [1] for datetime. For the purpose of compatibility we got constants for timezones from Tarantool [2]. The patch adds a script to generate the data according to the instructions [3]. 1. https://github.com/tarantool/tarantool/wiki/Datetime-Internals#timezone-support 2. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/timezones.h 3. https://github.com/tarantool/tarantool/blob/9ee45289e01232b8df1413efea11db170ae3b3b4/src/lib/tzcode/gen-zone-abbrevs.pl#L35-L39 Closes #163 --- CHANGELOG.md | 1 + Makefile | 4 + datetime/datetime.go | 51 +- datetime/datetime_test.go | 233 ++++-- datetime/example_test.go | 21 + datetime/export_test.go | 5 + datetime/gen-timezones.sh | 54 ++ datetime/timezones.go | 1484 +++++++++++++++++++++++++++++++++++++ 8 files changed, 1793 insertions(+), 60 deletions(-) create mode 100644 datetime/export_test.go create mode 100755 datetime/gen-timezones.sh create mode 100644 datetime/timezones.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a840c13c..409951e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Support datetime type in msgpack (#118) - Prepared SQL statements (#117) - Context support for request objects (#48) +- TZ support for datetime (#163) ### Changed diff --git a/Makefile b/Makefile index 7bc6411e4..418b3c89f 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,10 @@ clean: deps: clean ( cd ./queue; tarantoolctl rocks install queue 1.1.0 ) +.PHONY: datetime-timezones +datetime-timezones: + (cd ./datetime; ./gen-timezones.sh) + .PHONY: format format: goimports -l -w . diff --git a/datetime/datetime.go b/datetime/datetime.go index e9664c7ae..579f48d23 100644 --- a/datetime/datetime.go +++ b/datetime/datetime.go @@ -12,6 +12,7 @@ package datetime import ( "encoding/binary" "fmt" + "math" "time" "gopkg.in/vmihailenco/msgpack.v2" @@ -81,6 +82,16 @@ type Datetime struct { time time.Time } +const ( + noTimezoneZone = "" + noTimezoneOffset = 0 +) + +// NoTimezone allows to create a datetime without UTC timezone for +// Tarantool. The problem is that Golang by default creates a time value with +// UTC timezone. So it is a way to create a datetime without timezone. +var NoTimezone = time.FixedZone(noTimezoneZone, noTimezoneOffset) + // NewDatetime returns a pointer to a new datetime.Datetime that contains a // specified time.Time. It may returns an error if the Time value is out of // supported range: [-5879610-06-22T00:00Z .. 5879611-07-11T00:00Z] @@ -91,6 +102,18 @@ func NewDatetime(t time.Time) (*Datetime, error) { return nil, fmt.Errorf("Time %s is out of supported range.", t) } + zone, offset := t.Zone() + if zone != noTimezoneZone { + if _, ok := timezoneToIndex[zone]; !ok { + return nil, fmt.Errorf("Unknown timezone %s with offset %d", + zone, offset) + } + } + offset /= 60 + if offset < math.MinInt16 || offset > math.MaxInt16 { + return nil, fmt.Errorf("Offset must be between %d and %d", math.MinInt16, math.MaxInt16) + } + dt := new(Datetime) dt.time = t return dt, nil @@ -110,8 +133,12 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) { var dt datetime dt.seconds = tm.Unix() dt.nsec = int32(tm.Nanosecond()) - dt.tzIndex = 0 // It is not implemented, see gh-163. - dt.tzOffset = 0 // It is not implemented, see gh-163. + + zone, offset := tm.Zone() + if zone != noTimezoneZone { + dt.tzIndex = int16(timezoneToIndex[zone]) + } + dt.tzOffset = int16(offset / 60) var bytesSize = secondsSize if dt.nsec != 0 || dt.tzOffset != 0 || dt.tzIndex != 0 { @@ -132,7 +159,7 @@ func (dtime *Datetime) MarshalMsgpack() ([]byte, error) { func (tm *Datetime) UnmarshalMsgpack(b []byte) error { l := len(b) if l != maxSize && l != secondsSize { - return fmt.Errorf("invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize) + return fmt.Errorf("Invalid data length: got %d, wanted %d or %d", len(b), secondsSize, maxSize) } var dt datetime @@ -145,7 +172,23 @@ func (tm *Datetime) UnmarshalMsgpack(b []byte) error { dt.tzIndex = int16(binary.LittleEndian.Uint16(b[secondsSize+nsecSize+tzOffsetSize:])) } - tt := time.Unix(dt.seconds, int64(dt.nsec)).UTC() + tt := time.Unix(dt.seconds, int64(dt.nsec)) + + loc := NoTimezone + if dt.tzIndex != 0 || dt.tzOffset != 0 { + zone := noTimezoneZone + offset := int(dt.tzOffset) * 60 + + if dt.tzIndex != 0 { + if _, ok := indexToTimezone[int(dt.tzIndex)]; !ok { + return fmt.Errorf("Unknown timezone index %d", dt.tzIndex) + } + zone = indexToTimezone[int(dt.tzIndex)] + } + loc = time.FixedZone(zone, offset) + } + tt = tt.In(loc) + dtp, err := NewDatetime(tt) if dtp != nil { *tm = *dtp diff --git a/datetime/datetime_test.go b/datetime/datetime_test.go index 93d292ecd..1cb3f7c59 100644 --- a/datetime/datetime_test.go +++ b/datetime/datetime_test.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "log" + "math" "os" "reflect" "testing" @@ -74,6 +75,115 @@ func assertDatetimeIsEqual(t *testing.T, tuples []interface{}, tm time.Time) { } } +func TestTimezonesIndexMapping(t *testing.T) { + for _, index := range TimezoneToIndex { + if _, ok := IndexToTimezone[index]; !ok { + t.Errorf("index %d not found", index) + } + } +} + +func TestTimezonesZonesMapping(t *testing.T) { + for _, zone := range IndexToTimezone { + if _, ok := TimezoneToIndex[zone]; !ok { + t.Errorf("zone %s not found", zone) + } + } +} + +func TestInvalidTimezone(t *testing.T) { + invalidLoc := time.FixedZone("AnyInvalid", 0) + tm, err := time.Parse(time.RFC3339, "2010-08-12T11:39:14Z") + if err != nil { + t.Fatalf("Time parse failed: %s", err) + } + tm = tm.In(invalidLoc) + dt, err := NewDatetime(tm) + if err == nil { + t.Fatalf("Unexpected success: %v", dt) + } +} + +func TestInvalidOffset(t *testing.T) { + tests := []struct { + ok bool + offset int + }{ + {ok: true, offset: math.MinInt16}, + {ok: true, offset: (math.MinInt16 + 1) * 60}, + {ok: true, offset: (math.MaxInt16 - 1) * 60}, + {ok: true, offset: math.MaxInt16}, + {ok: false, offset: (math.MinInt16 - 1) * 60}, + {ok: false, offset: (math.MaxInt16 + 1) * 60}, + } + + for _, testcase := range tests { + name := "" + if testcase.ok { + name = fmt.Sprintf("in_boundary_%d", testcase.offset) + } else { + name = fmt.Sprintf("out_of_boundary_%d", testcase.offset) + } + t.Run(name, func(t *testing.T) { + loc := time.FixedZone("MSK", testcase.offset) + tm, err := time.Parse(time.RFC3339, "2010-08-12T11:39:14Z") + if err != nil { + t.Fatalf("Time parse failed: %s", err) + } + tm = tm.In(loc) + dt, err := NewDatetime(tm) + if testcase.ok && err != nil { + t.Fatalf("Unexpected error: %s", err.Error()) + } + if !testcase.ok && err == nil { + t.Fatalf("Unexpected success: %v", dt) + } + }) + } +} + +func TestCustomTimezone(t *testing.T) { + skipIfDatetimeUnsupported(t) + + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + customZone := "Europe/Moscow" + customOffset := 180 * 60 + + customLoc := time.FixedZone(customZone, customOffset) + tm, err := time.Parse(time.RFC3339, "2010-08-12T11:44:14Z") + if err != nil { + t.Fatalf("Time parse failed: %s", err) + } + tm = tm.In(customLoc) + dt, err := NewDatetime(tm) + if err != nil { + t.Fatalf("Unable to create datetime: %s", err.Error()) + } + + resp, err := conn.Replace(spaceTuple1, []interface{}{dt, "payload"}) + if err != nil { + t.Fatalf("Datetime replace failed %s", err.Error()) + } + assertDatetimeIsEqual(t, resp.Data, tm) + + tpl := resp.Data[0].([]interface{}) + respDt := tpl[0].(Datetime) + zone, offset := respDt.ToTime().Zone() + if zone != customZone { + t.Fatalf("Expected zone %s instead of %s", customZone, zone) + } + if offset != customOffset { + t.Fatalf("Expected offset %d instead of %d", customOffset, offset) + } + + _, err = conn.Delete(spaceTuple1, 0, []interface{}{dt}) + if err != nil { + t.Fatalf("Datetime delete failed: %s", err.Error()) + } +} + func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) { t.Helper() @@ -112,61 +222,63 @@ func tupleInsertSelectDelete(t *testing.T, conn *Connection, tm time.Time) { } var datetimeSample = []struct { + fmt string dt string mpBuf string // MessagePack buffer. }{ - {"2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"}, - {"1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"}, - {"2010-08-12T11:39:14Z", "d70462dd634c00000000"}, - {"1984-03-24T18:04:05Z", "d7041530c31a00000000"}, - {"2010-01-12T00:00:00Z", "d70480bb4b4b00000000"}, - {"1970-01-01T00:00:00Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"}, - {"1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"}, - {"1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"}, - {"1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"}, - {"1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"}, - {"1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"}, - {"1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"}, - {"1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"}, - {"1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"}, - {"1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"}, - {"1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"}, - {"1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"}, - {"1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"}, - {"1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"}, - {"1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"}, - {"1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"}, - {"1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"}, - {"1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"}, - {"1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"}, - {"1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"}, - {"1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"}, - {"1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"}, - {"1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"}, - {"1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"}, - {"1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"}, - {"1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"}, - {"1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"}, - {"1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"}, - {"1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"}, - {"1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"}, - {"1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"}, - {"1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"}, - {"1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"}, - {"1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"}, - {"1970-01-01T00:00:00.0Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.00Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.0000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.00000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.000000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.0000000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.00000000Z", "d7040000000000000000"}, - {"1970-01-01T00:00:00.000000000Z", "d7040000000000000000"}, - {"1973-11-29T21:33:09Z", "d70415cd5b0700000000"}, - {"2013-10-28T17:51:56Z", "d7043ca46e5200000000"}, - {"9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"}, + {time.RFC3339, "2012-01-31T23:59:59.000000010Z", "d8047f80284f000000000a00000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000000010Z", "d80400000000000000000a00000000000000"}, + {time.RFC3339, "2010-08-12T11:39:14Z", "d70462dd634c00000000"}, + {time.RFC3339, "1984-03-24T18:04:05Z", "d7041530c31a00000000"}, + {time.RFC3339, "2010-01-12T00:00:00Z", "d70480bb4b4b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.123456789Z", "d804000000000000000015cd5b0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.12345678Z", "d80400000000000000000ccd5b0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.1234567Z", "d8040000000000000000bccc5b0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.123456Z", "d804000000000000000000ca5b0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.12345Z", "d804000000000000000090b25b0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.1234Z", "d804000000000000000040ef5a0700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.123Z", "d8040000000000000000c0d4540700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.12Z", "d8040000000000000000000e270700000000"}, + {time.RFC3339, "1970-01-01T00:00:00.1Z", "d804000000000000000000e1f50500000000"}, + {time.RFC3339, "1970-01-01T00:00:00.01Z", "d80400000000000000008096980000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.001Z", "d804000000000000000040420f0000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0001Z", "d8040000000000000000a086010000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00001Z", "d80400000000000000001027000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000001Z", "d8040000000000000000e803000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0000001Z", "d80400000000000000006400000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00000001Z", "d80400000000000000000a00000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000000001Z", "d80400000000000000000100000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000000009Z", "d80400000000000000000900000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00000009Z", "d80400000000000000005a00000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0000009Z", "d80400000000000000008403000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000009Z", "d80400000000000000002823000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00009Z", "d8040000000000000000905f010000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0009Z", "d8040000000000000000a0bb0d0000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.009Z", "d80400000000000000004054890000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.09Z", "d8040000000000000000804a5d0500000000"}, + {time.RFC3339, "1970-01-01T00:00:00.9Z", "d804000000000000000000e9a43500000000"}, + {time.RFC3339, "1970-01-01T00:00:00.99Z", "d80400000000000000008033023b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.999Z", "d8040000000000000000c0878b3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.9999Z", "d80400000000000000006043993b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.99999Z", "d8040000000000000000f0a29a3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.999999Z", "d804000000000000000018c69a3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.9999999Z", "d80400000000000000009cc99a3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.99999999Z", "d8040000000000000000f6c99a3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.999999999Z", "d8040000000000000000ffc99a3b00000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.0000000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.00000000Z", "d7040000000000000000"}, + {time.RFC3339, "1970-01-01T00:00:00.000000000Z", "d7040000000000000000"}, + {time.RFC3339, "1973-11-29T21:33:09Z", "d70415cd5b0700000000"}, + {time.RFC3339, "2013-10-28T17:51:56Z", "d7043ca46e5200000000"}, + {time.RFC3339, "9999-12-31T23:59:59Z", "d7047f41f4ff3a000000"}, + {time.RFC822, "02 Jan 06 15:04 MSK", "d804b016b9430000000000000000b400ee00"}, } func TestDatetimeInsertSelectDelete(t *testing.T) { @@ -177,7 +289,10 @@ func TestDatetimeInsertSelectDelete(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { - tm, err := time.Parse(time.RFC3339, testcase.dt) + tm, err := time.Parse(testcase.fmt, testcase.dt) + if testcase.fmt == time.RFC3339 { + tm = tm.In(NoTimezone) + } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) } @@ -517,7 +632,10 @@ func TestCustomEncodeDecodeTuple5(t *testing.T) { func TestMPEncode(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { - tm, err := time.Parse(time.RFC3339, testcase.dt) + tm, err := time.Parse(testcase.fmt, testcase.dt) + if testcase.fmt == time.RFC3339 { + tm = tm.In(NoTimezone) + } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) } @@ -531,7 +649,7 @@ func TestMPEncode(t *testing.T) { } refBuf, _ := hex.DecodeString(testcase.mpBuf) if reflect.DeepEqual(buf, refBuf) != true { - t.Fatalf("Failed to encode datetime '%s', actual %v, expected %v", + t.Fatalf("Failed to encode datetime '%s', actual %x, expected %x", testcase.dt, buf, refBuf) @@ -543,7 +661,10 @@ func TestMPEncode(t *testing.T) { func TestMPDecode(t *testing.T) { for _, testcase := range datetimeSample { t.Run(testcase.dt, func(t *testing.T) { - tm, err := time.Parse(time.RFC3339, testcase.dt) + tm, err := time.Parse(testcase.fmt, testcase.dt) + if testcase.fmt == time.RFC3339 { + tm = tm.In(NoTimezone) + } if err != nil { t.Fatalf("Time (%s) parse failed: %s", testcase.dt, err) } diff --git a/datetime/example_test.go b/datetime/example_test.go index 97359f43c..7969c6a96 100644 --- a/datetime/example_test.go +++ b/datetime/example_test.go @@ -79,3 +79,24 @@ func Example() { fmt.Printf("Code: %d\n", resp.Code) fmt.Printf("Data: %v\n", respDt.ToTime()) } + +// Example demonstrates how to create a datetime for Tarantool without UTC +// timezone in datetime. +func ExampleNewDatetime_noTimezone() { + var datetime = "2013-10-28T17:51:56.000000009Z" + tm, err := time.Parse(time.RFC3339, datetime) + if err != nil { + fmt.Printf("error in time.Parse() is %v", err) + return + } + + tm = tm.In(NoTimezone) + + dt, err := NewDatetime(tm) + if err != nil { + fmt.Printf("Unable to create Datetime from %s: %s", tm, err) + return + } + + fmt.Printf("Time value: %v\n", dt.ToTime()) +} diff --git a/datetime/export_test.go b/datetime/export_test.go new file mode 100644 index 000000000..f138b7a7a --- /dev/null +++ b/datetime/export_test.go @@ -0,0 +1,5 @@ +package datetime + +/* It's kind of an integration test data from an external data source. */ +var IndexToTimezone = indexToTimezone +var TimezoneToIndex = timezoneToIndex diff --git a/datetime/gen-timezones.sh b/datetime/gen-timezones.sh new file mode 100755 index 000000000..e251d7db6 --- /dev/null +++ b/datetime/gen-timezones.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +set -xeuo pipefail + +SRC_COMMIT="9ee45289e01232b8df1413efea11db170ae3b3b4" +SRC_FILE=timezones.h +DST_FILE=timezones.go + +[ -e ${SRC_FILE} ] && rm ${SRC_FILE} +wget -O ${SRC_FILE} \ + https://raw.githubusercontent.com/tarantool/tarantool/${SRC_COMMIT}/src/lib/tzcode/timezones.h + +# We don't need aliases in indexToTimezone because Tarantool always replace it: +# +# tarantool> T = date.parse '2022-01-01T00:00 Pacific/Enderbury' +# --- +# ... +# tarantool> T +# --- +# - 2022-01-01T00:00:00 Pacific/Kanton +# ... +# +# So we can do the same and don't worry, be happy. + +cat < ${DST_FILE} +package datetime + +/* Automatically generated by gen-timezones.sh */ + +var indexToTimezone = map[int]string{ +EOF + +grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \ + | awk '{printf("\t%s : %s,\n", $1, $3)}' >> ${DST_FILE} +grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \ + | awk '{printf("\t%s : %s,\n", $1, $2)}' >> ${DST_FILE} + +cat <> ${DST_FILE} +} + +var timezoneToIndex = map[string]int{ +EOF + +grep ZONE_ABBREV ${SRC_FILE} | sed "s/ZONE_ABBREV( *//g" | sed "s/[),]//g" \ + | awk '{printf("\t%s : %s,\n", $3, $1)}' >> ${DST_FILE} +grep ZONE_UNIQUE ${SRC_FILE} | sed "s/ZONE_UNIQUE( *//g" | sed "s/[),]//g" \ + | awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE} +grep ZONE_ALIAS ${SRC_FILE} | sed "s/ZONE_ALIAS( *//g" | sed "s/[),]//g" \ + | awk '{printf("\t%s : %s,\n", $2, $1)}' >> ${DST_FILE} + +echo "}" >> ${DST_FILE} + +rm timezones.h + +gofmt -s -w ${DST_FILE} diff --git a/datetime/timezones.go b/datetime/timezones.go new file mode 100644 index 000000000..ca4234fd1 --- /dev/null +++ b/datetime/timezones.go @@ -0,0 +1,1484 @@ +package datetime + +/* Automatically generated by gen-timezones.sh */ + +var indexToTimezone = map[int]string{ + 1: "A", + 2: "B", + 3: "C", + 4: "D", + 5: "E", + 6: "F", + 7: "G", + 8: "H", + 9: "I", + 10: "K", + 11: "L", + 12: "M", + 13: "N", + 14: "O", + 15: "P", + 16: "Q", + 17: "R", + 18: "S", + 19: "T", + 20: "U", + 21: "V", + 22: "W", + 23: "X", + 24: "Y", + 25: "Z", + 32: "AT", + 40: "BT", + 48: "CT", + 56: "ET", + 64: "GT", + 72: "IT", + 80: "KT", + 88: "MT", + 96: "PT", + 104: "ST", + 112: "UT", + 120: "WT", + 128: "ACT", + 129: "ADT", + 130: "AET", + 131: "AFT", + 132: "AMT", + 133: "AoE", + 134: "ART", + 135: "AST", + 136: "AZT", + 144: "BDT", + 145: "BNT", + 146: "BOT", + 147: "BRT", + 148: "BST", + 149: "BTT", + 152: "CAT", + 153: "CCT", + 154: "CDT", + 155: "CET", + 156: "CIT", + 157: "CKT", + 158: "CLT", + 159: "COT", + 160: "CST", + 161: "CVT", + 162: "CXT", + 168: "EAT", + 169: "ECT", + 170: "EDT", + 171: "EET", + 172: "EGT", + 173: "EST", + 176: "FET", + 177: "FJT", + 178: "FKT", + 179: "FNT", + 184: "GET", + 185: "GFT", + 186: "GMT", + 187: "GST", + 188: "GYT", + 192: "HAA", + 193: "HAC", + 194: "HAE", + 195: "HAP", + 196: "HAR", + 197: "HAT", + 198: "HDT", + 199: "HKT", + 200: "HLV", + 201: "HNA", + 202: "HNC", + 203: "HNE", + 204: "HNP", + 205: "HNR", + 206: "HNT", + 207: "HST", + 208: "ICT", + 209: "IDT", + 210: "IOT", + 211: "IST", + 216: "JST", + 224: "KGT", + 225: "KIT", + 226: "KST", + 232: "MCK", + 233: "MDT", + 234: "MEZ", + 235: "MHT", + 236: "MMT", + 237: "MSD", + 238: "MSK", + 239: "MST", + 240: "MUT", + 241: "MVT", + 242: "MYT", + 248: "NCT", + 249: "NDT", + 250: "NFT", + 251: "NPT", + 252: "NRT", + 253: "NST", + 254: "NUT", + 256: "OEZ", + 264: "PDT", + 265: "PET", + 266: "PGT", + 267: "PHT", + 268: "PKT", + 269: "PST", + 270: "PWT", + 271: "PYT", + 272: "RET", + 280: "SBT", + 281: "SCT", + 282: "SGT", + 283: "SRT", + 284: "SST", + 288: "TFT", + 289: "TJT", + 290: "TKT", + 291: "TLT", + 292: "TMT", + 293: "TOT", + 294: "TRT", + 295: "TVT", + 296: "UTC", + 297: "UYT", + 298: "UZT", + 304: "VET", + 305: "VUT", + 312: "WAT", + 313: "WDT", + 314: "WET", + 315: "WEZ", + 316: "WFT", + 317: "WGT", + 318: "WIB", + 319: "WIT", + 320: "WST", + 328: "ACDT", + 329: "ACST", + 330: "ADST", + 331: "AEDT", + 332: "AEST", + 333: "AKDT", + 334: "AKST", + 335: "ALMT", + 336: "AMDT", + 337: "AMST", + 338: "ANAT", + 339: "AQTT", + 340: "AWDT", + 341: "AWST", + 342: "AZOT", + 343: "AZST", + 344: "BDST", + 345: "BRST", + 352: "CAST", + 353: "CDST", + 354: "CEDT", + 355: "CEST", + 356: "CHOT", + 357: "ChST", + 358: "CHUT", + 359: "CIST", + 360: "CLDT", + 361: "CLST", + 368: "DAVT", + 369: "DDUT", + 376: "EADT", + 377: "EAST", + 378: "ECST", + 379: "EDST", + 380: "EEDT", + 381: "EEST", + 382: "EGST", + 384: "FJDT", + 385: "FJST", + 386: "FKDT", + 387: "FKST", + 392: "GALT", + 393: "GAMT", + 394: "GILT", + 400: "HADT", + 401: "HAST", + 402: "HOVT", + 408: "IRDT", + 409: "IRKT", + 410: "IRST", + 416: "KOST", + 417: "KRAT", + 418: "KUYT", + 424: "LHDT", + 425: "LHST", + 426: "LINT", + 432: "MAGT", + 433: "MART", + 434: "MAWT", + 435: "MDST", + 436: "MESZ", + 440: "NFDT", + 441: "NOVT", + 442: "NZDT", + 443: "NZST", + 448: "OESZ", + 449: "OMST", + 450: "ORAT", + 456: "PDST", + 457: "PETT", + 458: "PHOT", + 459: "PMDT", + 460: "PMST", + 461: "PONT", + 462: "PYST", + 464: "QYZT", + 472: "ROTT", + 480: "SAKT", + 481: "SAMT", + 482: "SAST", + 483: "SRET", + 484: "SYOT", + 488: "TAHT", + 489: "TOST", + 496: "ULAT", + 497: "UYST", + 504: "VLAT", + 505: "VOST", + 512: "WAKT", + 513: "WAST", + 514: "WEDT", + 515: "WEST", + 516: "WESZ", + 517: "WGST", + 518: "WITA", + 520: "YAKT", + 521: "YAPT", + 522: "YEKT", + 528: "ACWST", + 529: "ANAST", + 530: "AZODT", + 531: "AZOST", + 536: "CHADT", + 537: "CHAST", + 538: "CHODT", + 539: "CHOST", + 540: "CIDST", + 544: "EASST", + 545: "EFATE", + 552: "HOVDT", + 553: "HOVST", + 560: "IRKST", + 568: "KRAST", + 576: "MAGST", + 584: "NACDT", + 585: "NACST", + 586: "NAEDT", + 587: "NAEST", + 588: "NAMDT", + 589: "NAMST", + 590: "NAPDT", + 591: "NAPST", + 592: "NOVST", + 600: "OMSST", + 608: "PETST", + 616: "SAMST", + 624: "ULAST", + 632: "VLAST", + 640: "WARST", + 648: "YAKST", + 649: "YEKST", + 656: "CHODST", + 664: "HOVDST", + 672: "Africa/Abidjan", + 673: "Africa/Algiers", + 674: "Africa/Bissau", + 675: "Africa/Cairo", + 676: "Africa/Casablanca", + 677: "Africa/Ceuta", + 678: "Africa/El_Aaiun", + 679: "Africa/Johannesburg", + 680: "Africa/Juba", + 681: "Africa/Khartoum", + 682: "Africa/Lagos", + 683: "Africa/Maputo", + 684: "Africa/Monrovia", + 685: "Africa/Nairobi", + 686: "Africa/Ndjamena", + 687: "Africa/Sao_Tome", + 688: "Africa/Tripoli", + 689: "Africa/Tunis", + 690: "Africa/Windhoek", + 691: "America/Adak", + 692: "America/Anchorage", + 693: "America/Araguaina", + 694: "America/Argentina/Buenos_Aires", + 695: "America/Argentina/Catamarca", + 696: "America/Argentina/Cordoba", + 697: "America/Argentina/Jujuy", + 698: "America/Argentina/La_Rioja", + 699: "America/Argentina/Mendoza", + 700: "America/Argentina/Rio_Gallegos", + 701: "America/Argentina/Salta", + 702: "America/Argentina/San_Juan", + 703: "America/Argentina/San_Luis", + 704: "America/Argentina/Tucuman", + 705: "America/Argentina/Ushuaia", + 706: "America/Asuncion", + 707: "America/Bahia", + 708: "America/Bahia_Banderas", + 709: "America/Barbados", + 710: "America/Belem", + 711: "America/Belize", + 712: "America/Boa_Vista", + 713: "America/Bogota", + 714: "America/Boise", + 715: "America/Cambridge_Bay", + 716: "America/Campo_Grande", + 717: "America/Cancun", + 718: "America/Caracas", + 719: "America/Cayenne", + 720: "America/Chicago", + 721: "America/Chihuahua", + 722: "America/Costa_Rica", + 723: "America/Cuiaba", + 724: "America/Danmarkshavn", + 725: "America/Dawson", + 726: "America/Dawson_Creek", + 727: "America/Denver", + 728: "America/Detroit", + 729: "America/Edmonton", + 730: "America/Eirunepe", + 731: "America/El_Salvador", + 732: "America/Fort_Nelson", + 733: "America/Fortaleza", + 734: "America/Glace_Bay", + 735: "America/Goose_Bay", + 736: "America/Grand_Turk", + 737: "America/Guatemala", + 738: "America/Guayaquil", + 739: "America/Guyana", + 740: "America/Halifax", + 741: "America/Havana", + 742: "America/Hermosillo", + 743: "America/Indiana/Indianapolis", + 744: "America/Indiana/Knox", + 745: "America/Indiana/Marengo", + 746: "America/Indiana/Petersburg", + 747: "America/Indiana/Tell_City", + 748: "America/Indiana/Vevay", + 749: "America/Indiana/Vincennes", + 750: "America/Indiana/Winamac", + 751: "America/Inuvik", + 752: "America/Iqaluit", + 753: "America/Jamaica", + 754: "America/Juneau", + 755: "America/Kentucky/Louisville", + 756: "America/Kentucky/Monticello", + 757: "America/La_Paz", + 758: "America/Lima", + 759: "America/Los_Angeles", + 760: "America/Maceio", + 761: "America/Managua", + 762: "America/Manaus", + 763: "America/Martinique", + 764: "America/Matamoros", + 765: "America/Mazatlan", + 766: "America/Menominee", + 767: "America/Merida", + 768: "America/Metlakatla", + 769: "America/Mexico_City", + 770: "America/Miquelon", + 771: "America/Moncton", + 772: "America/Monterrey", + 773: "America/Montevideo", + 774: "America/New_York", + 775: "America/Nipigon", + 776: "America/Nome", + 777: "America/Noronha", + 778: "America/North_Dakota/Beulah", + 779: "America/North_Dakota/Center", + 780: "America/North_Dakota/New_Salem", + 781: "America/Nuuk", + 782: "America/Ojinaga", + 783: "America/Panama", + 784: "America/Pangnirtung", + 785: "America/Paramaribo", + 786: "America/Phoenix", + 787: "America/Port-au-Prince", + 788: "America/Porto_Velho", + 789: "America/Puerto_Rico", + 790: "America/Punta_Arenas", + 791: "America/Rainy_River", + 792: "America/Rankin_Inlet", + 793: "America/Recife", + 794: "America/Regina", + 795: "America/Resolute", + 796: "America/Rio_Branco", + 797: "America/Santarem", + 798: "America/Santiago", + 799: "America/Santo_Domingo", + 800: "America/Sao_Paulo", + 801: "America/Scoresbysund", + 802: "America/Sitka", + 803: "America/St_Johns", + 804: "America/Swift_Current", + 805: "America/Tegucigalpa", + 806: "America/Thule", + 807: "America/Thunder_Bay", + 808: "America/Tijuana", + 809: "America/Toronto", + 810: "America/Vancouver", + 811: "America/Whitehorse", + 812: "America/Winnipeg", + 813: "America/Yakutat", + 814: "America/Yellowknife", + 815: "Antarctica/Casey", + 816: "Antarctica/Davis", + 817: "Antarctica/Macquarie", + 818: "Antarctica/Mawson", + 819: "Antarctica/Palmer", + 820: "Antarctica/Rothera", + 821: "Antarctica/Troll", + 822: "Antarctica/Vostok", + 823: "Asia/Almaty", + 824: "Asia/Amman", + 825: "Asia/Anadyr", + 826: "Asia/Aqtau", + 827: "Asia/Aqtobe", + 828: "Asia/Ashgabat", + 829: "Asia/Atyrau", + 830: "Asia/Baghdad", + 831: "Asia/Baku", + 832: "Asia/Bangkok", + 833: "Asia/Barnaul", + 834: "Asia/Beirut", + 835: "Asia/Bishkek", + 836: "Asia/Brunei", + 837: "Asia/Chita", + 838: "Asia/Choibalsan", + 839: "Asia/Colombo", + 840: "Asia/Damascus", + 841: "Asia/Dhaka", + 842: "Asia/Dili", + 843: "Asia/Dubai", + 844: "Asia/Dushanbe", + 845: "Asia/Famagusta", + 846: "Asia/Gaza", + 847: "Asia/Hebron", + 848: "Asia/Ho_Chi_Minh", + 849: "Asia/Hong_Kong", + 850: "Asia/Hovd", + 851: "Asia/Irkutsk", + 852: "Asia/Jakarta", + 853: "Asia/Jayapura", + 854: "Asia/Jerusalem", + 855: "Asia/Kabul", + 856: "Asia/Kamchatka", + 857: "Asia/Karachi", + 858: "Asia/Kathmandu", + 859: "Asia/Khandyga", + 860: "Asia/Kolkata", + 861: "Asia/Krasnoyarsk", + 862: "Asia/Kuala_Lumpur", + 863: "Asia/Kuching", + 864: "Asia/Macau", + 865: "Asia/Magadan", + 866: "Asia/Makassar", + 867: "Asia/Manila", + 868: "Asia/Nicosia", + 869: "Asia/Novokuznetsk", + 870: "Asia/Novosibirsk", + 871: "Asia/Omsk", + 872: "Asia/Oral", + 873: "Asia/Pontianak", + 874: "Asia/Pyongyang", + 875: "Asia/Qatar", + 876: "Asia/Qostanay", + 877: "Asia/Qyzylorda", + 878: "Asia/Riyadh", + 879: "Asia/Sakhalin", + 880: "Asia/Samarkand", + 881: "Asia/Seoul", + 882: "Asia/Shanghai", + 883: "Asia/Singapore", + 884: "Asia/Srednekolymsk", + 885: "Asia/Taipei", + 886: "Asia/Tashkent", + 887: "Asia/Tbilisi", + 888: "Asia/Tehran", + 889: "Asia/Thimphu", + 890: "Asia/Tokyo", + 891: "Asia/Tomsk", + 892: "Asia/Ulaanbaatar", + 893: "Asia/Urumqi", + 894: "Asia/Ust-Nera", + 895: "Asia/Vladivostok", + 896: "Asia/Yakutsk", + 897: "Asia/Yangon", + 898: "Asia/Yekaterinburg", + 899: "Asia/Yerevan", + 900: "Atlantic/Azores", + 901: "Atlantic/Bermuda", + 902: "Atlantic/Canary", + 903: "Atlantic/Cape_Verde", + 904: "Atlantic/Faroe", + 905: "Atlantic/Madeira", + 906: "Atlantic/Reykjavik", + 907: "Atlantic/South_Georgia", + 908: "Atlantic/Stanley", + 909: "Australia/Adelaide", + 910: "Australia/Brisbane", + 911: "Australia/Broken_Hill", + 912: "Australia/Darwin", + 913: "Australia/Eucla", + 914: "Australia/Hobart", + 915: "Australia/Lindeman", + 916: "Australia/Lord_Howe", + 917: "Australia/Melbourne", + 918: "Australia/Perth", + 919: "Australia/Sydney", + 920: "Etc/GMT", + 921: "Etc/UTC", + 922: "Europe/Amsterdam", + 923: "Europe/Andorra", + 924: "Europe/Astrakhan", + 925: "Europe/Athens", + 926: "Europe/Belgrade", + 927: "Europe/Berlin", + 928: "Europe/Brussels", + 929: "Europe/Bucharest", + 930: "Europe/Budapest", + 931: "Europe/Chisinau", + 932: "Europe/Copenhagen", + 933: "Europe/Dublin", + 934: "Europe/Gibraltar", + 935: "Europe/Helsinki", + 936: "Europe/Istanbul", + 937: "Europe/Kaliningrad", + 938: "Europe/Kiev", + 939: "Europe/Kirov", + 940: "Europe/Lisbon", + 941: "Europe/London", + 942: "Europe/Luxembourg", + 943: "Europe/Madrid", + 944: "Europe/Malta", + 945: "Europe/Minsk", + 946: "Europe/Monaco", + 947: "Europe/Moscow", + 948: "Europe/Oslo", + 949: "Europe/Paris", + 950: "Europe/Prague", + 951: "Europe/Riga", + 952: "Europe/Rome", + 953: "Europe/Samara", + 954: "Europe/Saratov", + 955: "Europe/Simferopol", + 956: "Europe/Sofia", + 957: "Europe/Stockholm", + 958: "Europe/Tallinn", + 959: "Europe/Tirane", + 960: "Europe/Ulyanovsk", + 961: "Europe/Uzhgorod", + 962: "Europe/Vienna", + 963: "Europe/Vilnius", + 964: "Europe/Volgograd", + 965: "Europe/Warsaw", + 966: "Europe/Zaporozhye", + 967: "Europe/Zurich", + 968: "Indian/Chagos", + 969: "Indian/Christmas", + 970: "Indian/Cocos", + 971: "Indian/Kerguelen", + 972: "Indian/Mahe", + 973: "Indian/Maldives", + 974: "Indian/Mauritius", + 975: "Indian/Reunion", + 976: "Pacific/Apia", + 977: "Pacific/Auckland", + 978: "Pacific/Bougainville", + 979: "Pacific/Chatham", + 980: "Pacific/Chuuk", + 981: "Pacific/Easter", + 982: "Pacific/Efate", + 983: "Pacific/Fakaofo", + 984: "Pacific/Fiji", + 985: "Pacific/Funafuti", + 986: "Pacific/Galapagos", + 987: "Pacific/Gambier", + 988: "Pacific/Guadalcanal", + 989: "Pacific/Guam", + 990: "Pacific/Honolulu", + 991: "Pacific/Kanton", + 992: "Pacific/Kiritimati", + 993: "Pacific/Kosrae", + 994: "Pacific/Kwajalein", + 995: "Pacific/Majuro", + 996: "Pacific/Marquesas", + 997: "Pacific/Nauru", + 998: "Pacific/Niue", + 999: "Pacific/Norfolk", + 1000: "Pacific/Noumea", + 1001: "Pacific/Pago_Pago", + 1002: "Pacific/Palau", + 1003: "Pacific/Pitcairn", + 1004: "Pacific/Pohnpei", + 1005: "Pacific/Port_Moresby", + 1006: "Pacific/Rarotonga", + 1007: "Pacific/Tahiti", + 1008: "Pacific/Tarawa", + 1009: "Pacific/Tongatapu", + 1010: "Pacific/Wake", + 1011: "Pacific/Wallis", +} + +var timezoneToIndex = map[string]int{ + "A": 1, + "B": 2, + "C": 3, + "D": 4, + "E": 5, + "F": 6, + "G": 7, + "H": 8, + "I": 9, + "K": 10, + "L": 11, + "M": 12, + "N": 13, + "O": 14, + "P": 15, + "Q": 16, + "R": 17, + "S": 18, + "T": 19, + "U": 20, + "V": 21, + "W": 22, + "X": 23, + "Y": 24, + "Z": 25, + "AT": 32, + "BT": 40, + "CT": 48, + "ET": 56, + "GT": 64, + "IT": 72, + "KT": 80, + "MT": 88, + "PT": 96, + "ST": 104, + "UT": 112, + "WT": 120, + "ACT": 128, + "ADT": 129, + "AET": 130, + "AFT": 131, + "AMT": 132, + "AoE": 133, + "ART": 134, + "AST": 135, + "AZT": 136, + "BDT": 144, + "BNT": 145, + "BOT": 146, + "BRT": 147, + "BST": 148, + "BTT": 149, + "CAT": 152, + "CCT": 153, + "CDT": 154, + "CET": 155, + "CIT": 156, + "CKT": 157, + "CLT": 158, + "COT": 159, + "CST": 160, + "CVT": 161, + "CXT": 162, + "EAT": 168, + "ECT": 169, + "EDT": 170, + "EET": 171, + "EGT": 172, + "EST": 173, + "FET": 176, + "FJT": 177, + "FKT": 178, + "FNT": 179, + "GET": 184, + "GFT": 185, + "GMT": 186, + "GST": 187, + "GYT": 188, + "HAA": 192, + "HAC": 193, + "HAE": 194, + "HAP": 195, + "HAR": 196, + "HAT": 197, + "HDT": 198, + "HKT": 199, + "HLV": 200, + "HNA": 201, + "HNC": 202, + "HNE": 203, + "HNP": 204, + "HNR": 205, + "HNT": 206, + "HST": 207, + "ICT": 208, + "IDT": 209, + "IOT": 210, + "IST": 211, + "JST": 216, + "KGT": 224, + "KIT": 225, + "KST": 226, + "MCK": 232, + "MDT": 233, + "MEZ": 234, + "MHT": 235, + "MMT": 236, + "MSD": 237, + "MSK": 238, + "MST": 239, + "MUT": 240, + "MVT": 241, + "MYT": 242, + "NCT": 248, + "NDT": 249, + "NFT": 250, + "NPT": 251, + "NRT": 252, + "NST": 253, + "NUT": 254, + "OEZ": 256, + "PDT": 264, + "PET": 265, + "PGT": 266, + "PHT": 267, + "PKT": 268, + "PST": 269, + "PWT": 270, + "PYT": 271, + "RET": 272, + "SBT": 280, + "SCT": 281, + "SGT": 282, + "SRT": 283, + "SST": 284, + "TFT": 288, + "TJT": 289, + "TKT": 290, + "TLT": 291, + "TMT": 292, + "TOT": 293, + "TRT": 294, + "TVT": 295, + "UTC": 296, + "UYT": 297, + "UZT": 298, + "VET": 304, + "VUT": 305, + "WAT": 312, + "WDT": 313, + "WET": 314, + "WEZ": 315, + "WFT": 316, + "WGT": 317, + "WIB": 318, + "WIT": 319, + "WST": 320, + "ACDT": 328, + "ACST": 329, + "ADST": 330, + "AEDT": 331, + "AEST": 332, + "AKDT": 333, + "AKST": 334, + "ALMT": 335, + "AMDT": 336, + "AMST": 337, + "ANAT": 338, + "AQTT": 339, + "AWDT": 340, + "AWST": 341, + "AZOT": 342, + "AZST": 343, + "BDST": 344, + "BRST": 345, + "CAST": 352, + "CDST": 353, + "CEDT": 354, + "CEST": 355, + "CHOT": 356, + "ChST": 357, + "CHUT": 358, + "CIST": 359, + "CLDT": 360, + "CLST": 361, + "DAVT": 368, + "DDUT": 369, + "EADT": 376, + "EAST": 377, + "ECST": 378, + "EDST": 379, + "EEDT": 380, + "EEST": 381, + "EGST": 382, + "FJDT": 384, + "FJST": 385, + "FKDT": 386, + "FKST": 387, + "GALT": 392, + "GAMT": 393, + "GILT": 394, + "HADT": 400, + "HAST": 401, + "HOVT": 402, + "IRDT": 408, + "IRKT": 409, + "IRST": 410, + "KOST": 416, + "KRAT": 417, + "KUYT": 418, + "LHDT": 424, + "LHST": 425, + "LINT": 426, + "MAGT": 432, + "MART": 433, + "MAWT": 434, + "MDST": 435, + "MESZ": 436, + "NFDT": 440, + "NOVT": 441, + "NZDT": 442, + "NZST": 443, + "OESZ": 448, + "OMST": 449, + "ORAT": 450, + "PDST": 456, + "PETT": 457, + "PHOT": 458, + "PMDT": 459, + "PMST": 460, + "PONT": 461, + "PYST": 462, + "QYZT": 464, + "ROTT": 472, + "SAKT": 480, + "SAMT": 481, + "SAST": 482, + "SRET": 483, + "SYOT": 484, + "TAHT": 488, + "TOST": 489, + "ULAT": 496, + "UYST": 497, + "VLAT": 504, + "VOST": 505, + "WAKT": 512, + "WAST": 513, + "WEDT": 514, + "WEST": 515, + "WESZ": 516, + "WGST": 517, + "WITA": 518, + "YAKT": 520, + "YAPT": 521, + "YEKT": 522, + "ACWST": 528, + "ANAST": 529, + "AZODT": 530, + "AZOST": 531, + "CHADT": 536, + "CHAST": 537, + "CHODT": 538, + "CHOST": 539, + "CIDST": 540, + "EASST": 544, + "EFATE": 545, + "HOVDT": 552, + "HOVST": 553, + "IRKST": 560, + "KRAST": 568, + "MAGST": 576, + "NACDT": 584, + "NACST": 585, + "NAEDT": 586, + "NAEST": 587, + "NAMDT": 588, + "NAMST": 589, + "NAPDT": 590, + "NAPST": 591, + "NOVST": 592, + "OMSST": 600, + "PETST": 608, + "SAMST": 616, + "ULAST": 624, + "VLAST": 632, + "WARST": 640, + "YAKST": 648, + "YEKST": 649, + "CHODST": 656, + "HOVDST": 664, + "Africa/Abidjan": 672, + "Africa/Algiers": 673, + "Africa/Bissau": 674, + "Africa/Cairo": 675, + "Africa/Casablanca": 676, + "Africa/Ceuta": 677, + "Africa/El_Aaiun": 678, + "Africa/Johannesburg": 679, + "Africa/Juba": 680, + "Africa/Khartoum": 681, + "Africa/Lagos": 682, + "Africa/Maputo": 683, + "Africa/Monrovia": 684, + "Africa/Nairobi": 685, + "Africa/Ndjamena": 686, + "Africa/Sao_Tome": 687, + "Africa/Tripoli": 688, + "Africa/Tunis": 689, + "Africa/Windhoek": 690, + "America/Adak": 691, + "America/Anchorage": 692, + "America/Araguaina": 693, + "America/Argentina/Buenos_Aires": 694, + "America/Argentina/Catamarca": 695, + "America/Argentina/Cordoba": 696, + "America/Argentina/Jujuy": 697, + "America/Argentina/La_Rioja": 698, + "America/Argentina/Mendoza": 699, + "America/Argentina/Rio_Gallegos": 700, + "America/Argentina/Salta": 701, + "America/Argentina/San_Juan": 702, + "America/Argentina/San_Luis": 703, + "America/Argentina/Tucuman": 704, + "America/Argentina/Ushuaia": 705, + "America/Asuncion": 706, + "America/Bahia": 707, + "America/Bahia_Banderas": 708, + "America/Barbados": 709, + "America/Belem": 710, + "America/Belize": 711, + "America/Boa_Vista": 712, + "America/Bogota": 713, + "America/Boise": 714, + "America/Cambridge_Bay": 715, + "America/Campo_Grande": 716, + "America/Cancun": 717, + "America/Caracas": 718, + "America/Cayenne": 719, + "America/Chicago": 720, + "America/Chihuahua": 721, + "America/Costa_Rica": 722, + "America/Cuiaba": 723, + "America/Danmarkshavn": 724, + "America/Dawson": 725, + "America/Dawson_Creek": 726, + "America/Denver": 727, + "America/Detroit": 728, + "America/Edmonton": 729, + "America/Eirunepe": 730, + "America/El_Salvador": 731, + "America/Fort_Nelson": 732, + "America/Fortaleza": 733, + "America/Glace_Bay": 734, + "America/Goose_Bay": 735, + "America/Grand_Turk": 736, + "America/Guatemala": 737, + "America/Guayaquil": 738, + "America/Guyana": 739, + "America/Halifax": 740, + "America/Havana": 741, + "America/Hermosillo": 742, + "America/Indiana/Indianapolis": 743, + "America/Indiana/Knox": 744, + "America/Indiana/Marengo": 745, + "America/Indiana/Petersburg": 746, + "America/Indiana/Tell_City": 747, + "America/Indiana/Vevay": 748, + "America/Indiana/Vincennes": 749, + "America/Indiana/Winamac": 750, + "America/Inuvik": 751, + "America/Iqaluit": 752, + "America/Jamaica": 753, + "America/Juneau": 754, + "America/Kentucky/Louisville": 755, + "America/Kentucky/Monticello": 756, + "America/La_Paz": 757, + "America/Lima": 758, + "America/Los_Angeles": 759, + "America/Maceio": 760, + "America/Managua": 761, + "America/Manaus": 762, + "America/Martinique": 763, + "America/Matamoros": 764, + "America/Mazatlan": 765, + "America/Menominee": 766, + "America/Merida": 767, + "America/Metlakatla": 768, + "America/Mexico_City": 769, + "America/Miquelon": 770, + "America/Moncton": 771, + "America/Monterrey": 772, + "America/Montevideo": 773, + "America/New_York": 774, + "America/Nipigon": 775, + "America/Nome": 776, + "America/Noronha": 777, + "America/North_Dakota/Beulah": 778, + "America/North_Dakota/Center": 779, + "America/North_Dakota/New_Salem": 780, + "America/Nuuk": 781, + "America/Ojinaga": 782, + "America/Panama": 783, + "America/Pangnirtung": 784, + "America/Paramaribo": 785, + "America/Phoenix": 786, + "America/Port-au-Prince": 787, + "America/Porto_Velho": 788, + "America/Puerto_Rico": 789, + "America/Punta_Arenas": 790, + "America/Rainy_River": 791, + "America/Rankin_Inlet": 792, + "America/Recife": 793, + "America/Regina": 794, + "America/Resolute": 795, + "America/Rio_Branco": 796, + "America/Santarem": 797, + "America/Santiago": 798, + "America/Santo_Domingo": 799, + "America/Sao_Paulo": 800, + "America/Scoresbysund": 801, + "America/Sitka": 802, + "America/St_Johns": 803, + "America/Swift_Current": 804, + "America/Tegucigalpa": 805, + "America/Thule": 806, + "America/Thunder_Bay": 807, + "America/Tijuana": 808, + "America/Toronto": 809, + "America/Vancouver": 810, + "America/Whitehorse": 811, + "America/Winnipeg": 812, + "America/Yakutat": 813, + "America/Yellowknife": 814, + "Antarctica/Casey": 815, + "Antarctica/Davis": 816, + "Antarctica/Macquarie": 817, + "Antarctica/Mawson": 818, + "Antarctica/Palmer": 819, + "Antarctica/Rothera": 820, + "Antarctica/Troll": 821, + "Antarctica/Vostok": 822, + "Asia/Almaty": 823, + "Asia/Amman": 824, + "Asia/Anadyr": 825, + "Asia/Aqtau": 826, + "Asia/Aqtobe": 827, + "Asia/Ashgabat": 828, + "Asia/Atyrau": 829, + "Asia/Baghdad": 830, + "Asia/Baku": 831, + "Asia/Bangkok": 832, + "Asia/Barnaul": 833, + "Asia/Beirut": 834, + "Asia/Bishkek": 835, + "Asia/Brunei": 836, + "Asia/Chita": 837, + "Asia/Choibalsan": 838, + "Asia/Colombo": 839, + "Asia/Damascus": 840, + "Asia/Dhaka": 841, + "Asia/Dili": 842, + "Asia/Dubai": 843, + "Asia/Dushanbe": 844, + "Asia/Famagusta": 845, + "Asia/Gaza": 846, + "Asia/Hebron": 847, + "Asia/Ho_Chi_Minh": 848, + "Asia/Hong_Kong": 849, + "Asia/Hovd": 850, + "Asia/Irkutsk": 851, + "Asia/Jakarta": 852, + "Asia/Jayapura": 853, + "Asia/Jerusalem": 854, + "Asia/Kabul": 855, + "Asia/Kamchatka": 856, + "Asia/Karachi": 857, + "Asia/Kathmandu": 858, + "Asia/Khandyga": 859, + "Asia/Kolkata": 860, + "Asia/Krasnoyarsk": 861, + "Asia/Kuala_Lumpur": 862, + "Asia/Kuching": 863, + "Asia/Macau": 864, + "Asia/Magadan": 865, + "Asia/Makassar": 866, + "Asia/Manila": 867, + "Asia/Nicosia": 868, + "Asia/Novokuznetsk": 869, + "Asia/Novosibirsk": 870, + "Asia/Omsk": 871, + "Asia/Oral": 872, + "Asia/Pontianak": 873, + "Asia/Pyongyang": 874, + "Asia/Qatar": 875, + "Asia/Qostanay": 876, + "Asia/Qyzylorda": 877, + "Asia/Riyadh": 878, + "Asia/Sakhalin": 879, + "Asia/Samarkand": 880, + "Asia/Seoul": 881, + "Asia/Shanghai": 882, + "Asia/Singapore": 883, + "Asia/Srednekolymsk": 884, + "Asia/Taipei": 885, + "Asia/Tashkent": 886, + "Asia/Tbilisi": 887, + "Asia/Tehran": 888, + "Asia/Thimphu": 889, + "Asia/Tokyo": 890, + "Asia/Tomsk": 891, + "Asia/Ulaanbaatar": 892, + "Asia/Urumqi": 893, + "Asia/Ust-Nera": 894, + "Asia/Vladivostok": 895, + "Asia/Yakutsk": 896, + "Asia/Yangon": 897, + "Asia/Yekaterinburg": 898, + "Asia/Yerevan": 899, + "Atlantic/Azores": 900, + "Atlantic/Bermuda": 901, + "Atlantic/Canary": 902, + "Atlantic/Cape_Verde": 903, + "Atlantic/Faroe": 904, + "Atlantic/Madeira": 905, + "Atlantic/Reykjavik": 906, + "Atlantic/South_Georgia": 907, + "Atlantic/Stanley": 908, + "Australia/Adelaide": 909, + "Australia/Brisbane": 910, + "Australia/Broken_Hill": 911, + "Australia/Darwin": 912, + "Australia/Eucla": 913, + "Australia/Hobart": 914, + "Australia/Lindeman": 915, + "Australia/Lord_Howe": 916, + "Australia/Melbourne": 917, + "Australia/Perth": 918, + "Australia/Sydney": 919, + "Etc/GMT": 920, + "Etc/UTC": 921, + "Europe/Amsterdam": 922, + "Europe/Andorra": 923, + "Europe/Astrakhan": 924, + "Europe/Athens": 925, + "Europe/Belgrade": 926, + "Europe/Berlin": 927, + "Europe/Brussels": 928, + "Europe/Bucharest": 929, + "Europe/Budapest": 930, + "Europe/Chisinau": 931, + "Europe/Copenhagen": 932, + "Europe/Dublin": 933, + "Europe/Gibraltar": 934, + "Europe/Helsinki": 935, + "Europe/Istanbul": 936, + "Europe/Kaliningrad": 937, + "Europe/Kiev": 938, + "Europe/Kirov": 939, + "Europe/Lisbon": 940, + "Europe/London": 941, + "Europe/Luxembourg": 942, + "Europe/Madrid": 943, + "Europe/Malta": 944, + "Europe/Minsk": 945, + "Europe/Monaco": 946, + "Europe/Moscow": 947, + "Europe/Oslo": 948, + "Europe/Paris": 949, + "Europe/Prague": 950, + "Europe/Riga": 951, + "Europe/Rome": 952, + "Europe/Samara": 953, + "Europe/Saratov": 954, + "Europe/Simferopol": 955, + "Europe/Sofia": 956, + "Europe/Stockholm": 957, + "Europe/Tallinn": 958, + "Europe/Tirane": 959, + "Europe/Ulyanovsk": 960, + "Europe/Uzhgorod": 961, + "Europe/Vienna": 962, + "Europe/Vilnius": 963, + "Europe/Volgograd": 964, + "Europe/Warsaw": 965, + "Europe/Zaporozhye": 966, + "Europe/Zurich": 967, + "Indian/Chagos": 968, + "Indian/Christmas": 969, + "Indian/Cocos": 970, + "Indian/Kerguelen": 971, + "Indian/Mahe": 972, + "Indian/Maldives": 973, + "Indian/Mauritius": 974, + "Indian/Reunion": 975, + "Pacific/Apia": 976, + "Pacific/Auckland": 977, + "Pacific/Bougainville": 978, + "Pacific/Chatham": 979, + "Pacific/Chuuk": 980, + "Pacific/Easter": 981, + "Pacific/Efate": 982, + "Pacific/Fakaofo": 983, + "Pacific/Fiji": 984, + "Pacific/Funafuti": 985, + "Pacific/Galapagos": 986, + "Pacific/Gambier": 987, + "Pacific/Guadalcanal": 988, + "Pacific/Guam": 989, + "Pacific/Honolulu": 990, + "Pacific/Kanton": 991, + "Pacific/Kiritimati": 992, + "Pacific/Kosrae": 993, + "Pacific/Kwajalein": 994, + "Pacific/Majuro": 995, + "Pacific/Marquesas": 996, + "Pacific/Nauru": 997, + "Pacific/Niue": 998, + "Pacific/Norfolk": 999, + "Pacific/Noumea": 1000, + "Pacific/Pago_Pago": 1001, + "Pacific/Palau": 1002, + "Pacific/Pitcairn": 1003, + "Pacific/Pohnpei": 1004, + "Pacific/Port_Moresby": 1005, + "Pacific/Rarotonga": 1006, + "Pacific/Tahiti": 1007, + "Pacific/Tarawa": 1008, + "Pacific/Tongatapu": 1009, + "Pacific/Wake": 1010, + "Pacific/Wallis": 1011, + "Africa/Accra": 672, + "Africa/Addis_Ababa": 685, + "Africa/Asmara": 685, + "Africa/Asmera": 685, + "Africa/Bamako": 672, + "Africa/Bangui": 682, + "Africa/Banjul": 672, + "Africa/Blantyre": 683, + "Africa/Brazzaville": 682, + "Africa/Bujumbura": 683, + "Africa/Conakry": 672, + "Africa/Dakar": 672, + "Africa/Dar_es_Salaam": 685, + "Africa/Djibouti": 685, + "Africa/Douala": 682, + "Africa/Freetown": 672, + "Africa/Gaborone": 683, + "Africa/Harare": 683, + "Africa/Kampala": 685, + "Africa/Kigali": 683, + "Africa/Kinshasa": 682, + "Africa/Libreville": 682, + "Africa/Lome": 672, + "Africa/Luanda": 682, + "Africa/Lubumbashi": 683, + "Africa/Lusaka": 683, + "Africa/Malabo": 682, + "Africa/Maseru": 679, + "Africa/Mbabane": 679, + "Africa/Mogadishu": 685, + "Africa/Niamey": 682, + "Africa/Nouakchott": 672, + "Africa/Ouagadougou": 672, + "Africa/Porto-Novo": 682, + "Africa/Timbuktu": 672, + "America/Anguilla": 789, + "America/Antigua": 789, + "America/Argentina/ComodRivadavia": 695, + "America/Aruba": 789, + "America/Atikokan": 783, + "America/Atka": 691, + "America/Blanc-Sablon": 789, + "America/Buenos_Aires": 694, + "America/Catamarca": 695, + "America/Cayman": 783, + "America/Coral_Harbour": 783, + "America/Cordoba": 696, + "America/Creston": 786, + "America/Curacao": 789, + "America/Dominica": 789, + "America/Ensenada": 808, + "America/Fort_Wayne": 743, + "America/Godthab": 781, + "America/Grenada": 789, + "America/Guadeloupe": 789, + "America/Indianapolis": 743, + "America/Jujuy": 697, + "America/Knox_IN": 744, + "America/Kralendijk": 789, + "America/Louisville": 755, + "America/Lower_Princes": 789, + "America/Marigot": 789, + "America/Mendoza": 699, + "America/Montreal": 809, + "America/Montserrat": 789, + "America/Nassau": 809, + "America/Port_of_Spain": 789, + "America/Porto_Acre": 796, + "America/Rosario": 696, + "America/Santa_Isabel": 808, + "America/Shiprock": 727, + "America/St_Barthelemy": 789, + "America/St_Kitts": 789, + "America/St_Lucia": 789, + "America/St_Thomas": 789, + "America/St_Vincent": 789, + "America/Tortola": 789, + "America/Virgin": 789, + "Antarctica/DumontDUrville": 1005, + "Antarctica/McMurdo": 977, + "Antarctica/South_Pole": 977, + "Antarctica/Syowa": 878, + "Arctic/Longyearbyen": 948, + "Asia/Aden": 878, + "Asia/Ashkhabad": 828, + "Asia/Bahrain": 875, + "Asia/Calcutta": 860, + "Asia/Chongqing": 882, + "Asia/Chungking": 882, + "Asia/Dacca": 841, + "Asia/Harbin": 882, + "Asia/Istanbul": 936, + "Asia/Kashgar": 893, + "Asia/Katmandu": 858, + "Asia/Kuwait": 878, + "Asia/Macao": 864, + "Asia/Muscat": 843, + "Asia/Phnom_Penh": 832, + "Asia/Rangoon": 897, + "Asia/Saigon": 848, + "Asia/Tel_Aviv": 854, + "Asia/Thimbu": 889, + "Asia/Ujung_Pandang": 866, + "Asia/Ulan_Bator": 892, + "Asia/Vientiane": 832, + "Atlantic/Faeroe": 904, + "Atlantic/Jan_Mayen": 948, + "Atlantic/St_Helena": 672, + "Australia/ACT": 919, + "Australia/Canberra": 919, + "Australia/Currie": 914, + "Australia/LHI": 916, + "Australia/NSW": 919, + "Australia/North": 912, + "Australia/Queensland": 910, + "Australia/South": 909, + "Australia/Tasmania": 914, + "Australia/Victoria": 917, + "Australia/West": 918, + "Australia/Yancowinna": 911, + "Brazil/Acre": 796, + "Brazil/DeNoronha": 777, + "Brazil/East": 800, + "Brazil/West": 762, + "Canada/Atlantic": 740, + "Canada/Central": 812, + "Canada/Eastern": 809, + "Canada/Mountain": 729, + "Canada/Newfoundland": 803, + "Canada/Pacific": 810, + "Canada/Saskatchewan": 794, + "Canada/Yukon": 811, + "Chile/Continental": 798, + "Chile/EasterIsland": 981, + "Cuba": 741, + "Egypt": 675, + "Eire": 933, + "Etc/GMT+0": 920, + "Etc/GMT-0": 920, + "Etc/GMT0": 920, + "Etc/Greenwich": 920, + "Etc/UCT": 921, + "Etc/Universal": 921, + "Etc/Zulu": 921, + "Europe/Belfast": 941, + "Europe/Bratislava": 950, + "Europe/Busingen": 967, + "Europe/Guernsey": 941, + "Europe/Isle_of_Man": 941, + "Europe/Jersey": 941, + "Europe/Ljubljana": 926, + "Europe/Mariehamn": 935, + "Europe/Nicosia": 868, + "Europe/Podgorica": 926, + "Europe/San_Marino": 952, + "Europe/Sarajevo": 926, + "Europe/Skopje": 926, + "Europe/Tiraspol": 931, + "Europe/Vaduz": 967, + "Europe/Vatican": 952, + "Europe/Zagreb": 926, + "GB": 941, + "GB-Eire": 941, + "GMT+0": 920, + "GMT-0": 920, + "GMT0": 920, + "Greenwich": 920, + "Hongkong": 849, + "Iceland": 906, + "Indian/Antananarivo": 685, + "Indian/Comoro": 685, + "Indian/Mayotte": 685, + "Iran": 888, + "Israel": 854, + "Jamaica": 753, + "Japan": 890, + "Kwajalein": 994, + "Libya": 688, + "Mexico/BajaNorte": 808, + "Mexico/BajaSur": 765, + "Mexico/General": 769, + "NZ": 977, + "NZ-CHAT": 979, + "Navajo": 727, + "PRC": 882, + "Pacific/Enderbury": 991, + "Pacific/Johnston": 990, + "Pacific/Midway": 1001, + "Pacific/Ponape": 1004, + "Pacific/Saipan": 989, + "Pacific/Samoa": 1001, + "Pacific/Truk": 980, + "Pacific/Yap": 980, + "Poland": 965, + "Portugal": 940, + "ROC": 885, + "ROK": 881, + "Singapore": 883, + "Turkey": 936, + "UCT": 921, + "US/Alaska": 692, + "US/Aleutian": 691, + "US/Arizona": 786, + "US/Central": 720, + "US/East-Indiana": 743, + "US/Eastern": 774, + "US/Hawaii": 990, + "US/Indiana-Starke": 744, + "US/Michigan": 728, + "US/Mountain": 727, + "US/Pacific": 759, + "US/Samoa": 1001, + "Universal": 921, + "W-SU": 947, + "Zulu": 921, +}