mirror of
https://github.com/wader/fq.git
synced 2024-12-23 13:22:58 +03:00
9b81d4d3ab
Preparation to make decoder use less memory and API more type safe. Now each scalar type has it's own struct type so it can store different things and enables to have a scalar interface. Also own types will enable experimenting with decode DLS designs like using chained methods that are type aware.
192 lines
4.8 KiB
Go
192 lines
4.8 KiB
Go
package tzif
|
|
|
|
import (
|
|
"embed"
|
|
"time"
|
|
|
|
"github.com/wader/fq/format"
|
|
"github.com/wader/fq/pkg/decode"
|
|
"github.com/wader/fq/pkg/interp"
|
|
"github.com/wader/fq/pkg/scalar"
|
|
)
|
|
|
|
//go:embed tzif.md
|
|
var tzifFS embed.FS
|
|
|
|
func init() {
|
|
interp.RegisterFormat(decode.Format{
|
|
Name: format.TZIF,
|
|
Description: "Time Zone Information Format",
|
|
DecodeFn: decodeTZIF,
|
|
Groups: []string{format.PROBE},
|
|
})
|
|
interp.RegisterFS(tzifFS)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
var versionToSymMapper = scalar.UintMapSymStr{
|
|
0x00: "1",
|
|
0x32: "2",
|
|
0x33: "3",
|
|
}
|
|
|
|
func decodeTZifHeader(d *decode.D, name string) tzifHeader {
|
|
var h tzifHeader
|
|
|
|
d.FieldStruct(name, func(d *decode.D) {
|
|
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))
|
|
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
|
|
}
|
|
|
|
var unixTimeToStr = scalar.SintFn(func(s scalar.Sint) (scalar.Sint, error) {
|
|
s.Sym = time.Unix(s.Actual, 0).UTC().Format(time.RFC3339)
|
|
return s, nil
|
|
})
|
|
|
|
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) {
|
|
d.FieldS32("utoff", d.SintAssertRange(-89999, 93599))
|
|
d.FieldU8("dst", d.UintAssert(0, 1))
|
|
d.FieldU8("idx", d.UintAssertRange(0, uint64(h.charcnt)-1))
|
|
})
|
|
}
|
|
})
|
|
|
|
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++ {
|
|
d.FieldU8("standard_wall_indicator", d.UintAssert(0, 1))
|
|
}
|
|
})
|
|
|
|
d.FieldArray("ut_local_indicators", func(d *decode.D) {
|
|
for i := uint32(0); i < h.isutcnt; i++ {
|
|
d.FieldU8("ut_local_indicator", d.UintAssert(0, 1))
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
func decodeTZifFooter(d *decode.D) {
|
|
d.FieldStruct("footer", func(d *decode.D) {
|
|
d.FieldU8("nl1", d.UintAssert(0x0a))
|
|
n := d.PeekFindByte(0x0a, d.BitsLeft()/8)
|
|
d.FieldScalarUTF8("tz_string", int(n))
|
|
d.FieldU8("nl2", d.UintAssert(0x0a))
|
|
})
|
|
}
|