From 0dd1ac5907c25fdd4987e9c25c62929d93fa6025 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 21 Nov 2024 11:26:49 +0300 Subject: [PATCH] =?utf8?q?Deal=20with=20leap=20seconds=20TAI=E2=86=94UTC?= =?utf8?q?=20conversion?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- README | 3 +- cmd/leapsecsdb/main.go | 65 --------------------- cmd/tai64nlocal/main.go | 8 +-- db.go | 50 ----------------- go.mod | 2 +- leapsecs.go | 106 +++++++++++++++++++++++++---------- printable.go | 56 +++++++++++++++++++ tai.go | 63 +++++++++++++++++++++ tai64n.go | 98 -------------------------------- tai64n_test.go | 65 --------------------- tai_test.go | 121 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 322 insertions(+), 315 deletions(-) delete mode 100644 cmd/leapsecsdb/main.go delete mode 100644 db.go create mode 100644 printable.go create mode 100644 tai.go delete mode 100644 tai64n.go delete mode 100644 tai64n_test.go create mode 100644 tai_test.go diff --git a/README b/README index 60e3fd9..f5e1b6d 100644 --- a/README +++ b/README @@ -1,3 +1,2 @@ Pure Go TAI64/TAI64N (http://cr.yp.to/libtai/tai64.html) implementation. -cmd/tai64nlocal and cmd/leapsecsdb are similar to DJB's ones. -Look at cmd/* and docstrings for example usage. +cmd/tai64nlocal is similar to DJB's one. diff --git a/cmd/leapsecsdb/main.go b/cmd/leapsecsdb/main.go deleted file mode 100644 index 0c94011..0000000 --- a/cmd/leapsecsdb/main.go +++ /dev/null @@ -1,65 +0,0 @@ -// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation -// Copyright (C) 2020-2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package main - -import ( - "bufio" - "flag" - "fmt" - "log" - "os" - "time" - - "go.cypherpunks.su/tai64n/v3" -) - -func main() { - log.SetFlags(0) - flag.Usage = func() { - fmt.Fprintf( - flag.CommandLine.Output(), - `Convert YYYY-MM-DD dates to TAI64 timestamps. -$ leapsecsdb > leapsecs.dat < +// Copyright (C) 2020-2025 Sergey Matveev // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ import ( "strings" "time" - "go.cypherpunks.su/tai64n/v3" + "go.cypherpunks.su/tai64n/v4" ) func main() { @@ -45,7 +45,7 @@ func main() { if err != nil { log.Fatalln(err) } - tai64n.LeapsecsDBLoad(buf) + tai64n.Leapsecs.Load(buf) } scanner := bufio.NewScanner(os.Stdin) @@ -75,7 +75,7 @@ func main() { log.Fatalln(err) } if *leapsecs { - t = tai64n.LeapsecsSub(t) + t, _ = tai64n.Leapsecs.Sub(t) } os.Stdout.WriteString(t.Format(tai64n.LocalFmt) + s[sep:] + "\n") } diff --git a/db.go b/db.go deleted file mode 100644 index f604532..0000000 --- a/db.go +++ /dev/null @@ -1,50 +0,0 @@ -// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation -// Copyright (C) 2020-2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package tai64n - -import "time" - -func init() { - LeapsecsDB = []int64{ - time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(2015, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(2012, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(2009, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(2006, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1999, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1997, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1996, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1994, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1993, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1992, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1991, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1988, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1985, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1983, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1982, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1981, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1980, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1979, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1978, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1977, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1976, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1975, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1974, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1973, 1, 1, 0, 0, 0, 0, time.UTC).Unix(), - time.Date(1972, 7, 1, 0, 0, 0, 0, time.UTC).Unix(), - } -} diff --git a/go.mod b/go.mod index 954cb2e..a816341 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module go.cypherpunks.su/tai64n/v3 +module go.cypherpunks.su/tai64n/v4 go 1.20 diff --git a/leapsecs.go b/leapsecs.go index f07ba10..4bad6a2 100644 --- a/leapsecs.go +++ b/leapsecs.go @@ -1,5 +1,5 @@ // go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation -// Copyright (C) 2020-2024 Sergey Matveev +// Copyright (C) 2020-2025 Sergey Matveev // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -16,50 +16,96 @@ package tai64n import ( - "sort" "time" ) const Leapsecs1972 = 10 +type LeapsecsList []int64 + // Database of Unix timestamps of the time when leap second occurred. // Library contains and initializes it with leap seconds up to 2016-12-31. -var LeapsecsDB []int64 +var Leapsecs LeapsecsList -// TAI<->UTC difference for the given Unix timestamp. -func LeapsecsDiff(t int64) int { - for i, leap := range LeapsecsDB { - if t > leap { - return len(LeapsecsDB) - i +// Add leap seconds to the time (convert TAI to UTC). +func (leapsecs LeapsecsList) Add(tai time.Time) (utc time.Time) { + var i int + v := tai.Unix() + for ; i < len(leapsecs); i++ { + if v < leapsecs[i] { + break } } - return 0 -} - -// Add currently known (LeapsecsDB) leap seconds, not including initial -// 1972-01-01 10-seconds offset. -func LeapsecsAdd(t time.Time) time.Time { - return t.Add(time.Second * time.Duration(LeapsecsDiff(t.Unix()))) + diff := Leapsecs1972 + int64(i) + if v+diff == leapsecs[i] { + diff++ + } + utc = tai.Add(time.Second * time.Duration(diff)) + return } -// Opposite of LeapsecsAdd(). -func LeapsecsSub(t time.Time) time.Time { - return t.Add(-time.Second * time.Duration(LeapsecsDiff(t.Unix()))) +// Subtract leap seconds from the time (convert UTC to TAI). +func (leapsecs LeapsecsList) Sub(utc time.Time) (tai time.Time, isLeap bool) { + diff := int64(Leapsecs1972) + v := utc.Unix() + for _, leapsec := range leapsecs { + if v < leapsec { + break + } + diff++ + if v == leapsec { + isLeap = true + break + } + } + tai = utc.Add(-time.Second * time.Duration(diff)) + return } -type Int64s []int64 - -func (a Int64s) Len() int { return len(a) } -func (a Int64s) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a Int64s) Less(i, j int) bool { return a[i] > a[j] } - -// Load "leapsecs.dat"-like database: concatenated TAI64 leap seconds. -// Function panics if encoding is invalid. -func LeapsecsDBLoad(buf []byte) { +// Load "leapsecs.dat"-like database (concatenated TAI64 leap seconds). +func (leapsecs *LeapsecsList) Load(buf []byte) { db := make([]int64, 0, len(buf)/TAI64Size) + var tai TAI64 for i := 0; i < len(buf); i += TAI64Size { - db = append(db, (ToTime(buf[i:i+TAI64Size]).Unix()/86400)*86400) + tai = [TAI64Size]byte(buf[i : i+TAI64Size]) + db = append(db, tai.Time().Unix()) + } + *leapsecs = db +} + +func init() { + def := []time.Time{ + time.Date(1972, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1972, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1973, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1974, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1975, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1976, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1977, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1978, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1979, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1981, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1982, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1983, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1985, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1987, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1989, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1990, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1992, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1993, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1994, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1995, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(1997, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(1998, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(2005, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(2008, 12, 31, 0, 0, 0, 0, time.UTC), + time.Date(2012, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(2015, 6, 30, 0, 0, 0, 0, time.UTC), + time.Date(2016, 12, 31, 0, 0, 0, 0, time.UTC), + } + db := make([]int64, len(def)) + for i, t := range def { + db[i] = t.Add(time.Second*86400).Unix() + Leapsecs1972 + int64(i) } - sort.Sort(Int64s(db)) - LeapsecsDB = db + Leapsecs = LeapsecsList(db) } diff --git a/printable.go b/printable.go new file mode 100644 index 0000000..eaad01d --- /dev/null +++ b/printable.go @@ -0,0 +1,56 @@ +// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation +// Copyright (C) 2020-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package tai64n + +import ( + "encoding/hex" + "errors" + "strings" + "time" +) + +func (tai *TAI64) Encode() string { + raw := make([]byte, 1+TAI64Size*2) + raw[0] = '@' + hex.Encode(raw[1:], tai[:]) + return string(raw) +} + +func (tai *TAI64N) Encode() string { + raw := make([]byte, 1+TAI64NSize*2) + raw[0] = '@' + hex.Encode(raw[1:], tai[:]) + return string(raw) +} + +// Convert "@HEX(TAI64(N?))" format to Time. +func Decode(s string) (t time.Time, err error) { + var raw []byte + raw, err = hex.DecodeString(strings.TrimPrefix(s, "@")) + if err != nil { + return + } + switch len(raw) { + case TAI64Size: + tai := TAI64(raw) + return tai.Time(), nil + case TAI64NSize: + tai := TAI64N(raw) + return tai.Time(), nil + } + err = errors.New("unsupported length") + return +} diff --git a/tai.go b/tai.go new file mode 100644 index 0000000..54142a7 --- /dev/null +++ b/tai.go @@ -0,0 +1,63 @@ +// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation +// Copyright (C) 2020-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// TAI64/TAI64N (http://cr.yp.to/libtai/tai64.html) dealing library. +// You can convert time to TAI64/TAI64N and vice versa with it. +// +// var tai tai64n.TAI64N +// tai.FromTime(time.Now()) +// printable := tai.Encode() +// decoded, err := tai64n.Decode(printable) +// tai.Time() == decoded +package tai64n + +import ( + "encoding/binary" + "time" +) + +const ( + TAI64Size = 8 + TAI64NSize = TAI64Size + 4 + TAI64NASize = TAI64NSize + 4 + LocalFmt = "2006-01-02 15:04:05.000000000" + Base = 0x4000000000000000 +) + +type ( + TAI64 [TAI64Size]byte + TAI64N [TAI64NSize]byte + TAI64NA [TAI64NASize]byte +) + +func (tai *TAI64) FromTime(src time.Time) { + binary.BigEndian.PutUint64(tai[:], uint64(Base)+uint64(src.Unix())) +} + +func (tai *TAI64N) FromTime(src time.Time) { + binary.BigEndian.PutUint64(tai[:], uint64(Base)+uint64(src.Unix())) + binary.BigEndian.PutUint32(tai[TAI64Size:], uint32(src.Nanosecond())) +} + +func (tai *TAI64) Time() time.Time { + return time.Unix(int64(binary.BigEndian.Uint64(tai[:TAI64Size]))-Base, 0) +} + +func (tai *TAI64N) Time() time.Time { + return time.Unix( + int64(binary.BigEndian.Uint64(tai[:TAI64Size]))-Base, + int64(binary.BigEndian.Uint32(tai[TAI64Size:])), + ) +} diff --git a/tai64n.go b/tai64n.go deleted file mode 100644 index 4f9c3e8..0000000 --- a/tai64n.go +++ /dev/null @@ -1,98 +0,0 @@ -// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation -// Copyright (C) 2020-2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -// TAI64/TAI64N (http://cr.yp.to/libtai/tai64.html) dealing library. -// You can convert time to TAI64/TAI64N and vice versa with it. -// -// tai := new(tai64n.TAI64N) -// tai.FromTime(time.Now()) -// printable := tai64n.Encode(tai[:]) -// decoded, err := tai64n.Decode(printable) -// tai64n.ToTime(tai[:]) == decoded -// -// By default TAI64 timestamps contain initial 1972-01-01 10-seconds -// TAI<->UTC difference. If you need honest TAI representation, then you -// should also use Leapsecs* functions. -package tai64n - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "strings" - "time" -) - -const ( - TAI64Size = 8 - TAI64NSize = TAI64Size + 4 - TAI64NASize = TAI64Size + 4 + 4 - LocalFmt = "2006-01-02 15:04:05.000000000" - Base = 0x4000000000000000 + Leapsecs1972 -) - -type ( - TAI64 [TAI64Size]byte - TAI64N [TAI64NSize]byte - TAI64NA [TAI64NASize]byte -) - -func (tai *TAI64) FromTime(src time.Time) { - binary.BigEndian.PutUint64(tai[:], uint64(Base)+uint64(src.Unix())) -} - -func (tai *TAI64N) FromTime(src time.Time) { - binary.BigEndian.PutUint64(tai[:], uint64(Base)+uint64(src.Unix())) - binary.BigEndian.PutUint32(tai[8:], uint32(src.Nanosecond())) -} - -func ToTime(tai []byte) time.Time { - var secs, nano int64 - switch len(tai) { - case TAI64NASize: - panic("TAI64NA can not be converted to time.Time") - case TAI64NSize: - nano = int64(binary.BigEndian.Uint32(tai[8:])) - fallthrough - case TAI64Size: - secs = int64(binary.BigEndian.Uint64(tai[:8])) - Base - default: - panic("invalid TAI64* size") - } - return time.Unix(secs, nano) -} - -// Convert TAI64* to "@HEX(TAI64)" format. -func Encode(tai []byte) string { - raw := make([]byte, 1+hex.EncodedLen(len(tai))) - raw[0] = '@' - hex.Encode(raw[1:], tai) - return string(raw) -} - -// Convert TAI64* "@HEX(TAI64)" format to Time. -func Decode(s string) (t time.Time, err error) { - raw, err := hex.DecodeString(strings.TrimPrefix(s, "@")) - switch len(raw) { - case TAI64NASize: - case TAI64NSize: - case TAI64Size: - default: - err = errors.New("invalid length") - return - } - t = ToTime(raw) - return -} diff --git a/tai64n_test.go b/tai64n_test.go deleted file mode 100644 index 8a9d8c9..0000000 --- a/tai64n_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation -// Copyright (C) 2020-2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package tai64n - -import ( - "testing" - "time" -) - -func TestVector(t *testing.T) { - tm, err := Decode("400000002a2b2c2d") - if err != nil { - t.Fatal(err) - } - - ref := time.Date(1992, 6, 2, 8, 7, 9, 0, time.UTC).Add(-Leapsecs1972 * time.Second) - if !tm.Equal(ref) { - t.Fatal("TAI64 != reference") - } - - tm = LeapsecsSub(tm) - ref = time.Date(1992, 6, 2, 8, 6, 43, 0, time.UTC) - if !tm.Equal(ref) { - t.Fatal("UTC != reference") - } -} - -func BenchmarkTAI64(b *testing.B) { - now := time.Now() - now = time.Unix(now.Unix(), 0) - tai := new(TAI64) - b.ResetTimer() - for i := 0; i < b.N; i++ { - tai.FromTime(now) - if !ToTime(tai[:]).Equal(now) { - b.FailNow() - } - } -} - -func BenchmarkTAI64N(b *testing.B) { - now := time.Now() - now = time.Unix(now.Unix(), now.UnixNano()) - tai := new(TAI64N) - b.ResetTimer() - for i := 0; i < b.N; i++ { - tai.FromTime(now) - if !ToTime(tai[:]).Equal(now) { - b.FailNow() - } - } -} diff --git a/tai_test.go b/tai_test.go new file mode 100644 index 0000000..bce160a --- /dev/null +++ b/tai_test.go @@ -0,0 +1,121 @@ +// go.cypherpunks.su/tai64n -- Pure Go TAI64/TAI64N implementation +// Copyright (C) 2020-2025 Sergey Matveev +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package tai64n + +import ( + "testing" + "time" +) + +func TestVectors(t *testing.T) { + tm, err := Decode("400000002a2b2c2d") + if err != nil { + t.Fatal(err) + } + ref := time.Date(1992, 6, 2, 8, 7, 9, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("TAI64 != reference") + } + tm, isLeap := Leapsecs.Sub(tm) + ref = time.Date(1992, 6, 2, 8, 6, 43, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("UTC != reference") + } + if isLeap { + t.Fatal("unexpectedly leap") + } + + tm, err = Decode("4000000034353637") + if err != nil { + t.Fatal(err) + } + ref = time.Date(1997, 10, 3, 18, 15, 19, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("TAI64 != reference") + } + tm, isLeap = Leapsecs.Sub(tm) + ref = time.Date(1997, 10, 3, 18, 14, 48, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("UTC != reference") + } + if isLeap { + t.Fatal("unexpectedly leap") + } + + tm, err = Decode("40000000586846a3") + if err != nil { + t.Fatal(err) + } + tm, isLeap = Leapsecs.Sub(tm) + ref = time.Date(2016, 12, 31, 23, 59, 59, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("UTC != reference") + } + if isLeap { + t.Fatal("unexpectedly leap") + } + + tm, err = Decode("40000000586846a4") + if err != nil { + t.Fatal(err) + } + tm, isLeap = Leapsecs.Sub(tm) + ref = time.Date(2016, 12, 31, 23, 59, 59, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("UTC != reference") + } + if !isLeap { + t.Fatal("unexpectedly non-leap") + } + + tm, err = Decode("40000000586846a5") + if err != nil { + t.Fatal(err) + } + tm, isLeap = Leapsecs.Sub(tm) + ref = time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC) + if !tm.Equal(ref) { + t.Fatal("UTC != reference") + } + if isLeap { + t.Fatal("unexpectedly leap") + } +} + +func BenchmarkTAI64(b *testing.B) { + now := time.Now() + now = now.Truncate(time.Second) + var tai TAI64 + b.ResetTimer() + for i := 0; i < b.N; i++ { + tai.FromTime(now) + if !tai.Time().Equal(now) { + b.FailNow() + } + } +} + +func BenchmarkTAI64N(b *testing.B) { + now := time.Now() + var tai TAI64N + b.ResetTimer() + for i := 0; i < b.N; i++ { + tai.FromTime(now) + if !tai.Time().Equal(now) { + b.FailNow() + } + } +} -- 2.48.1