2022-12-01 15:44:57 +03:00
|
|
|
package tzif
|
|
|
|
|
|
|
|
import (
|
2022-12-03 10:06:46 +03:00
|
|
|
"embed"
|
2022-12-01 15:44:57 +03:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/wader/fq/format"
|
|
|
|
"github.com/wader/fq/pkg/decode"
|
|
|
|
"github.com/wader/fq/pkg/interp"
|
|
|
|
"github.com/wader/fq/pkg/scalar"
|
|
|
|
)
|
|
|
|
|
2022-12-03 10:06:46 +03:00
|
|
|
//go:embed tzif.md
|
|
|
|
var tzifFS embed.FS
|
|
|
|
|
2022-12-01 15:44:57 +03:00
|
|
|
func init() {
|
|
|
|
interp.RegisterFormat(decode.Format{
|
|
|
|
Name: format.TZIF,
|
|
|
|
Description: "Time Zone Information Format",
|
|
|
|
DecodeFn: decodeTZIF,
|
|
|
|
Groups: []string{format.PROBE},
|
|
|
|
})
|
2022-12-03 10:06:46 +03:00
|
|
|
interp.RegisterFS(tzifFS)
|
2022-12-01 15:44:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func decodeTZIF(d *decode.D, _ any) any {
|
|
|
|
d.Endian = decode.BigEndian
|
|
|
|
|
|
|
|
v1h := decodeTZifHeader(d, "v1header")
|
|
|
|
decodeTZifDataBlock(d, v1h, 1, "v1datablock")
|
|
|
|
|
|
|
|
if v1h.ver >= 2 {
|
|
|
|
v2h := decodeTZifHeader(d, "v2plusheader")
|
|
|
|
decodeTZifDataBlock(d, v2h, 2, "v2plusdatablock")
|
|
|
|
decodeTZifFooter(d)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type tzifHeader struct {
|
|
|
|
magic uint32
|
|
|
|
ver uint8
|
|
|
|
isutcnt uint32
|
|
|
|
isstdcnt uint32
|
|
|
|
leapcnt uint32
|
|
|
|
timecnt uint32
|
|
|
|
typecnt uint32
|
|
|
|
charcnt uint32
|
|
|
|
}
|
|
|
|
|
2022-09-30 14:58:23 +03:00
|
|
|
var versionToSymMapper = scalar.UintMapSymStr{
|
2022-12-01 15:44:57 +03:00
|
|
|
0x00: "1",
|
|
|
|
0x32: "2",
|
|
|
|
0x33: "3",
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeTZifHeader(d *decode.D, name string) tzifHeader {
|
|
|
|
var h tzifHeader
|
|
|
|
|
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
2022-09-30 14:58:23 +03:00
|
|
|
h.magic = uint32(d.FieldU32("magic", scalar.UintHex, d.UintAssert(0x545a6966)))
|
|
|
|
h.ver = uint8(d.FieldU8("ver", d.UintAssert(0x00, 0x32, 0x33), scalar.UintHex, versionToSymMapper))
|
2022-12-01 15:44:57 +03:00
|
|
|
d.FieldRawLen("reserved", 15*8)
|
|
|
|
h.isutcnt = uint32(d.FieldU32("isutcnt"))
|
|
|
|
h.isstdcnt = uint32(d.FieldU32("isstdcnt"))
|
|
|
|
h.leapcnt = uint32(d.FieldU32("leapcnt"))
|
|
|
|
h.timecnt = uint32(d.FieldU32("timecnt"))
|
|
|
|
h.typecnt = uint32(d.FieldU32("typecnt"))
|
|
|
|
h.charcnt = uint32(d.FieldU32("charcnt"))
|
|
|
|
})
|
|
|
|
|
|
|
|
if h.isutcnt != 0 && h.isutcnt != h.typecnt {
|
|
|
|
d.Fatalf("invalid isutcnt")
|
|
|
|
}
|
|
|
|
if h.isstdcnt != 0 && h.isstdcnt != h.typecnt {
|
|
|
|
d.Fatalf("invalid isstdcnt")
|
|
|
|
}
|
|
|
|
if h.typecnt == 0 {
|
|
|
|
d.Fatalf("invalid typecnt")
|
|
|
|
}
|
|
|
|
if h.charcnt == 0 {
|
|
|
|
d.Fatalf("invalid charcnt")
|
|
|
|
}
|
|
|
|
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2022-09-30 14:58:23 +03:00
|
|
|
var unixTimeToStr = scalar.SintFn(func(s scalar.Sint) (scalar.Sint, error) {
|
|
|
|
s.Sym = time.Unix(s.Actual, 0).UTC().Format(time.RFC3339)
|
2022-12-01 15:44:57 +03:00
|
|
|
return s, nil
|
2022-12-03 09:45:11 +03:00
|
|
|
})
|
2022-12-01 15:44:57 +03:00
|
|
|
|
|
|
|
func decodeTZifDataBlock(d *decode.D, h tzifHeader, decodeAsVer int, name string) {
|
|
|
|
timeSize := 8 * 8
|
|
|
|
if decodeAsVer == 1 {
|
|
|
|
timeSize = 4 * 8
|
|
|
|
}
|
|
|
|
|
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
|
|
|
|
|
|
|
d.FieldArray("transition_times", func(d *decode.D) {
|
|
|
|
for i := uint32(0); i < h.timecnt; i++ {
|
|
|
|
t := d.FieldS("transition_time", timeSize, unixTimeToStr)
|
|
|
|
if t < -576460752303423488 {
|
|
|
|
d.Fatalf("transition time value should be at least -2^59 (-576460752303423488), but: %d (%0#16x)", t, t)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("transition_types", func(d *decode.D) {
|
|
|
|
for i := uint32(0); i < h.timecnt; i++ {
|
|
|
|
t := uint8(d.FieldU8("transition_type"))
|
|
|
|
if uint32(t) >= h.typecnt {
|
|
|
|
d.Fatalf("transition type must be in the range [0, %d]", h.typecnt-1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("local_time_type_records", func(d *decode.D) {
|
|
|
|
for i := uint32(0); i < h.typecnt; i++ {
|
|
|
|
d.FieldStruct("local_time_type", func(d *decode.D) {
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldS32("utoff", d.SintAssertRange(-89999, 93599))
|
|
|
|
d.FieldU8("dst", d.UintAssert(0, 1))
|
|
|
|
d.FieldU8("idx", d.UintAssertRange(0, uint64(h.charcnt)-1))
|
2022-12-01 15:44:57 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("time_zone_designations", func(d *decode.D) {
|
|
|
|
i := int(h.charcnt)
|
|
|
|
for {
|
|
|
|
s := d.FieldUTF8Null("time_zone_designation")
|
|
|
|
i -= len(s) + 1
|
|
|
|
if i <= 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("leap_second_records", func(d *decode.D) {
|
|
|
|
prevOccur := int64(0)
|
|
|
|
prevCorr := int64(0)
|
|
|
|
|
|
|
|
for i := uint32(0); i < h.leapcnt; i++ {
|
|
|
|
d.FieldStruct("leap_second_record", func(d *decode.D) {
|
|
|
|
occur := d.FieldS("occur", timeSize, unixTimeToStr)
|
|
|
|
corr := d.FieldS32("corr")
|
|
|
|
|
|
|
|
if i == 0 && occur < 0 {
|
|
|
|
d.Fatalf("the first value of occur must be nonnegative")
|
|
|
|
}
|
|
|
|
if i > 0 && occur-prevOccur < 2419199 {
|
|
|
|
d.Fatalf("occur must be at least 2419199 greater than the previous value")
|
|
|
|
}
|
|
|
|
if i == 0 && corr != 1 && corr != -1 {
|
|
|
|
d.Fatalf("the first value of corr must be either 1 or -1")
|
|
|
|
}
|
|
|
|
diff := corr - prevCorr
|
|
|
|
if i > 0 && diff != 1 && diff != -1 {
|
|
|
|
d.Fatalf("corr must differ by exactly 1 from the previous value: diff = %d, current corr = %d, previous corr = %d", diff, corr, prevCorr)
|
|
|
|
}
|
|
|
|
|
|
|
|
prevOccur = occur
|
|
|
|
prevCorr = corr
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("standard_wall_indicators", func(d *decode.D) {
|
|
|
|
for i := uint32(0); i < h.isstdcnt; i++ {
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldU8("standard_wall_indicator", d.UintAssert(0, 1))
|
2022-12-01 15:44:57 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
d.FieldArray("ut_local_indicators", func(d *decode.D) {
|
|
|
|
for i := uint32(0); i < h.isutcnt; i++ {
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldU8("ut_local_indicator", d.UintAssert(0, 1))
|
2022-12-01 15:44:57 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func decodeTZifFooter(d *decode.D) {
|
|
|
|
d.FieldStruct("footer", func(d *decode.D) {
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldU8("nl1", d.UintAssert(0x0a))
|
2022-12-03 09:31:07 +03:00
|
|
|
n := d.PeekFindByte(0x0a, d.BitsLeft()/8)
|
|
|
|
d.FieldScalarUTF8("tz_string", int(n))
|
2022-09-30 14:58:23 +03:00
|
|
|
d.FieldU8("nl2", d.UintAssert(0x0a))
|
2022-12-01 15:44:57 +03:00
|
|
|
})
|
|
|
|
}
|