-Pure Go TAI64N (http://cr.yp.to/libtai/tai64.html) implementation.
-cmd/tai64nlocal contains similar to DJB's one utility.
-Example TAI64N creation:
-
- import "go.cypherpunks.ru/tai64n"
-
- tai := new(tai64n.TAI64N)
- tai64n.FromTime(time.Now(), tai)
- println(tai.Encode()) // @400000005fd24ce33323c4d1
+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.
--- /dev/null
+/*
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+ "bufio"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "time"
+
+ "go.cypherpunks.ru/tai64n/v2"
+)
+
+func main() {
+ log.SetFlags(0)
+ flag.Usage = func() {
+ fmt.Fprintf(
+ flag.CommandLine.Output(),
+ `Convert YYYY-MM-DD dates to TAI64 timestamps.
+$ leapsecsdb > leapsecs.dat <<EOF
+1972-07-01
+1973-01-01
+EOF
+`,
+ )
+ flag.PrintDefaults()
+ }
+ flag.Parse()
+
+ scanner := bufio.NewScanner(os.Stdin)
+ var err error
+ var t time.Time
+ tai := new(tai64n.TAI64)
+ for {
+ if !scanner.Scan() {
+ if err = scanner.Err(); err != nil {
+ log.Fatalln(err)
+ }
+ break
+ }
+ t, err = time.Parse("2006-01-02", scanner.Text())
+ if err != nil {
+ log.Fatalln(err)
+ }
+ tai.FromTime(t)
+ if _, err = os.Stdout.Write(tai[:]); err != nil {
+ log.Fatalln(err)
+ }
+ }
+}
/*
-go.cypherpunks.ru/tai64n -- Pure Go TAI64N implementation
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
import (
"bufio"
+ "flag"
+ "fmt"
+ "io/ioutil"
"log"
"os"
"strings"
"time"
- "go.cypherpunks.ru/tai64n"
+ "go.cypherpunks.ru/tai64n/v2"
)
func main() {
log.SetFlags(0)
+ flag.Usage = func() {
+ fmt.Fprintf(
+ flag.CommandLine.Output(),
+ "Replace \"@HEX(TAI64)\"-prefixed line with human readable UTC.\n",
+ )
+ flag.PrintDefaults()
+ }
+ leapsecs := flag.Bool("leapsecs", false, "Take leap seconds into account: honest TAI->UTC")
+ db := flag.String("leapsecsdb", "", "Use that leapsecs.dat leap seconds database")
+ flag.Parse()
+
+ if *db != "" {
+ buf, err := ioutil.ReadFile(*db)
+ if err != nil {
+ log.Fatalln(err)
+ }
+ tai64n.LeapsecsDBLoad(buf)
+ }
+
scanner := bufio.NewScanner(os.Stdin)
var err error
var s string
if err != nil {
log.Fatalln(err)
}
+ if *leapsecs {
+ t = tai64n.LeapsecsSub(t)
+ }
os.Stdout.WriteString(t.Format(tai64n.LocalFmt) + s[sep:] + "\n")
}
}
--- /dev/null
+/*
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+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(),
+ }
+}
-module go.cypherpunks.ru/tai64n
+module go.cypherpunks.ru/tai64n/v2
go 1.12
--- /dev/null
+/*
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+package tai64n
+
+import (
+ "sort"
+ "time"
+)
+
+const Leapsecs1972 = 10
+
+// 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
+
+// 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
+ }
+ }
+ 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())))
+}
+
+// Opposite of LeapsecsAdd().
+func LeapsecsSub(t time.Time) time.Time {
+ return t.Add(-time.Second * time.Duration(LeapsecsDiff(t.Unix())))
+}
+
+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) {
+ db := make([]int64, 0, len(buf)/TAI64Size)
+ for i := 0; i < len(buf); i += TAI64Size {
+ db = append(db, (ToTime(buf[i:i+TAI64Size]).Unix()/86400)*86400)
+ }
+ sort.Sort(Int64s(db))
+ LeapsecsDB = db
+}
/*
-go.cypherpunks.ru/tai64n -- Pure Go TAI64N implementation
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
This program is free software: you can redistribute it and/or modify
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+// 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 (
)
const (
- Size = 12
- Base = 0x400000000000000a
- LocalFmt = "2006-01-02 15:04:05.000000000"
+ TAI64Size = 8
+ TAI64NSize = TAI64Size + 4
+ LocalFmt = "2006-01-02 15:04:05.000000000"
+ Base = 0x4000000000000000 + Leapsecs1972
)
-type TAI64N [Size]byte
+type TAI64 [TAI64Size]byte
+type TAI64N [TAI64NSize]byte
-func FromTime(src time.Time, dst *TAI64N) {
+func (dst *TAI64) FromTime(src time.Time) {
+ binary.BigEndian.PutUint64(dst[:], uint64(Base)+uint64(src.Unix()))
+}
+
+func (dst *TAI64N) FromTime(src time.Time) {
binary.BigEndian.PutUint64(dst[:], uint64(Base)+uint64(src.Unix()))
binary.BigEndian.PutUint32(dst[8:], uint32(src.Nanosecond()))
}
func ToTime(tai []byte) time.Time {
- if len(tai) != Size {
- panic("invalid size")
+ var secs, nano int64
+ switch len(tai) {
+ case TAI64NSize:
+ nano = int64(binary.BigEndian.Uint32(tai[8:]))
+ fallthrough
+ case TAI64Size:
+ secs = int64(binary.BigEndian.Uint64(tai[:8])) - Base
+ default:
+ panic("invalid tai size")
+ }
+ if secs < 0 {
+ panic("dates < 1970-01-01 are not supported")
}
- secs := int64(binary.BigEndian.Uint64(tai[:8]))
- nano := int64(binary.BigEndian.Uint32(tai[8:]))
- return time.Unix(secs-Base, nano)
+ return time.Unix(secs, nano)
}
-func (tai TAI64N) Encode() string {
- return "@" + hex.EncodeToString(tai[:])
+// Convert TAI64/TAI64N to "@HEX(TAI64)" format.
+func Encode(tai []byte) string {
+ raw := make([]byte, 1+hex.EncodedLen(len(tai)))
+ raw[0] = byte('@')
+ hex.Encode(raw[1:], tai)
+ return string(raw)
}
+// Convert TAI64/TAI64N "@HEX(TAI64)" format to Time.
func Decode(s string) (time.Time, error) {
- tai, err := hex.DecodeString(strings.TrimPrefix(s, "@"))
- if len(tai) != Size {
- return time.Time{}, errors.New("invalid ts length")
+ raw, err := hex.DecodeString(strings.TrimPrefix(s, "@"))
+ if !(len(raw) == TAI64Size || len(raw) == TAI64NSize) {
+ err = errors.New("invalid length")
}
- if err != nil {
- return time.Time{}, err
+ if err == nil {
+ return ToTime(raw), nil
}
- return ToTime(tai), nil
+ return time.Time{}, err
}
--- /dev/null
+/*
+go.cypherpunks.ru/tai64n -- Pure Go TAI64/TAI64N implementation
+Copyright (C) 2020-2021 Sergey Matveev <stargrave@stargrave.org>
+
+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 <http://www.gnu.org/licenses/>.
+*/
+
+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()
+ }
+ }
+}