1
1
mirror of https://github.com/wader/fq.git synced 2024-12-28 08:02:28 +03:00
fq/format/tzif/tzif.go

198 lines
4.9 KiB
Go
Raw Normal View History

package tzif
import (
2022-12-03 10:06:46 +03:00
"embed"
"fmt"
"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
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)
}
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.UToSymStr{
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.ActualHex, d.AssertU(0x545a6966)))
h.ver = uint8(d.FieldU8("ver", d.AssertU(0x00, 0x32, 0x33), scalar.ActualHex, 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.Fn(func(s scalar.S) (scalar.S, error) {
i, ok := s.Value().(int64)
if !ok {
return s, fmt.Errorf("expected int64 but got %T", s.Value())
}
s.Sym = time.Unix(i, 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.AssertSRange(-89999, 93599))
d.FieldU8("dst", d.AssertU(0, 1))
d.FieldU8("idx", d.AssertURange(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.AssertU(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.AssertU(0, 1))
}
})
})
}
func decodeTZifFooter(d *decode.D) {
d.FieldStruct("footer", func(d *decode.D) {
d.FieldU8("nl1", d.AssertU(0x0a))
n := d.PeekFindByte(0x0a, d.BitsLeft()/8)
d.FieldScalarUTF8("tz_string", int(n))
d.FieldU8("nl2", d.AssertU(0x0a))
})
}