1
1
mirror of https://github.com/wader/fq.git synced 2024-12-25 14:23:18 +03:00
fq/format/tzif/tzif.go
Mattias Wadman 9b81d4d3ab decode: More type safe API and split scalar into multiple types
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.
2022-12-14 16:23:58 +01:00

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))
})
}