pax_global_header00006660000000000000000000000064150347537610014525gustar00rootroot0000000000000052 comment=77537c86cd6f2f3dc5310a848fe3ddaece380020 golang-github-cactus-tai64-1.0.3/000077500000000000000000000000001503475376100164625ustar00rootroot00000000000000golang-github-cactus-tai64-1.0.3/.github/000077500000000000000000000000001503475376100200225ustar00rootroot00000000000000golang-github-cactus-tai64-1.0.3/.github/dependabot.yml000066400000000000000000000010551503475376100226530ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "gomod" # See documentation for possible values directory: "/" # Location of package manifests labels: - dependencies schedule: interval: "weekly" day: "monday" golang-github-cactus-tai64-1.0.3/.github/workflows/000077500000000000000000000000001503475376100220575ustar00rootroot00000000000000golang-github-cactus-tai64-1.0.3/.github/workflows/unit-tests.yml000066400000000000000000000011511503475376100247170ustar00rootroot00000000000000name: unit-tests on: push: pull_request: branches: - master jobs: build: name: Build strategy: matrix: goVer: ["1.16.x", "1.22.x"] platform: [ubuntu-latest] runs-on: ${{ matrix.platform }} steps: - name: Setup Go ${{ matrix.goVer }} uses: actions/setup-go@v1 with: go-version: ${{ matrix.goVer }} id: go - name: Src Checkout uses: actions/checkout@v1 with: fetch-depth: 1 - name: Tests env: GO111MODULE: "on" GOPROXY: "https://proxy.golang.org" run: go test -v -cpu=1,2 ./... golang-github-cactus-tai64-1.0.3/.gitignore000066400000000000000000000000131503475376100204440ustar00rootroot00000000000000/leaps.dat golang-github-cactus-tai64-1.0.3/LICENSE.md000066400000000000000000000013361503475376100200710ustar00rootroot00000000000000Copyright (c) 2012-2019 Eli Janssen Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. golang-github-cactus-tai64-1.0.3/README.md000066400000000000000000000025141503475376100177430ustar00rootroot00000000000000tai64 ===== [![Build Status](https://github.com/cactus/tai64/workflows/unit-tests/badge.svg)](https://github.com/cactus/tai64/actions) [![GoDoc](https://godoc.org/github.com/cactus/tai64?status.png)](https://godoc.org/github.com/cactus/tai64) [![Go Report Card](https://goreportcard.com/badge/github.com/cactus/tai64)](https://goreportcard.com/report/github.com/cactus/tai64) [![License](https://img.shields.io/github/license/cactus/tai64.svg)](https://github.com/cactus/tai64/blob/master/LICENSE.md) ## About Formats and parses [TAI64 and TAI64N][1] timestamps. ## Usage ``` go package main import ( "fmt" "os" "time" "github.com/cactus/tai64" ) func main() { t := time.Now() fmt.Println(t) s := tai64.FormatNano(t) fmt.Println(s) p, err := tai64.Parse(s) if err != nil { fmt.Println("Failed to decode time") os.Exit(1) } // tai64 times are in UTC fmt.Println(p) // time.Equal properly compares times with different locations. if t.Equal(p) { fmt.Println("equal") } else { fmt.Println("not equal") } } ``` Output: ``` 2016-05-25 13:44:01.281160355 -0700 PDT @4000000057460eb510c22aa3 2016-05-25 20:44:01.281160355 +0000 UTC equal ``` ## License Released under the [ISC license][2]. See `LICENSE.md` file for details. [1]: https://cr.yp.to/libtai/tai64.html [2]: https://choosealicense.com/licenses/isc/ golang-github-cactus-tai64-1.0.3/go.mod000066400000000000000000000000501503475376100175630ustar00rootroot00000000000000module github.com/cactus/tai64 go 1.16 golang-github-cactus-tai64-1.0.3/offsets.go000066400000000000000000000043021503475376100204610ustar00rootroot00000000000000// Copyright (c) 2012-2016 Eli Janssen // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. // THIS FILE IS AUTOGENERATED. DO NOT EDIT! package tai64 // https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat // http://www.stjarnhimlen.se/comp/time.html var tia64nDifferences = []struct { // TAI time ttime int64 // TAI-UTC offset offset int64 // unix UTC time utime int64 }{ {0x4000000003c2670a, 10, 63072000}, // 1972-01-01T00:00:00Z {0x4000000004b2580b, 11, 78796800}, // 1972-07-01T00:00:00Z {0x4000000005a4ec0c, 12, 94694400}, // 1973-01-01T00:00:00Z {0x4000000007861f8d, 13, 126230400}, // 1974-01-01T00:00:00Z {0x400000000967530e, 14, 157766400}, // 1975-01-01T00:00:00Z {0x400000000b48868f, 15, 189302400}, // 1976-01-01T00:00:00Z {0x400000000d2b0b90, 16, 220924800}, // 1977-01-01T00:00:00Z {0x400000000f0c3f11, 17, 252460800}, // 1978-01-01T00:00:00Z {0x4000000010ed7292, 18, 283996800}, // 1979-01-01T00:00:00Z {0x4000000012cea613, 19, 315532800}, // 1980-01-01T00:00:00Z {0x40000000159fca94, 20, 362793600}, // 1981-07-01T00:00:00Z {0x400000001780fe15, 21, 394329600}, // 1982-07-01T00:00:00Z {0x4000000019623196, 22, 425865600}, // 1983-07-01T00:00:00Z {0x400000001d25ea17, 23, 489024000}, // 1985-07-01T00:00:00Z {0x4000000021dae518, 24, 567993600}, // 1988-01-01T00:00:00Z {0x40000000259e9d99, 25, 631152000}, // 1990-01-01T00:00:00Z {0x40000000277fd11a, 26, 662688000}, // 1991-01-01T00:00:00Z {0x400000002a50f59b, 27, 709948800}, // 1992-07-01T00:00:00Z {0x400000002c32291c, 28, 741484800}, // 1993-07-01T00:00:00Z {0x400000002e135c9d, 29, 773020800}, // 1994-07-01T00:00:00Z {0x4000000030e7241e, 30, 820454400}, // 1996-01-01T00:00:00Z {0x4000000033b8489f, 31, 867715200}, // 1997-07-01T00:00:00Z {0x40000000368c1020, 32, 915148800}, // 1999-01-01T00:00:00Z {0x4000000043b71ba1, 33, 1136073600}, // 2006-01-01T00:00:00Z {0x40000000495c07a2, 34, 1230768000}, // 2009-01-01T00:00:00Z {0x400000004fef9323, 35, 1341100800}, // 2012-07-01T00:00:00Z {0x4000000055932da4, 36, 1435708800}, // 2015-07-01T00:00:00Z {0x40000000586846a5, 37, 1483228800}, // 2017-01-01T00:00:00Z } var tia64nSize = len(tia64nDifferences) golang-github-cactus-tai64-1.0.3/time.go000066400000000000000000000063031503475376100177510ustar00rootroot00000000000000// Copyright (c) 2012-2016 Eli Janssen // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tai64 //go:generate go run ./tools/generate.go -pkg $GOPACKAGE -output offsets.go import ( "fmt" "strconv" "time" ) // dt at Unix epoch: 1970-01-01 00:00:00 // Corresponding TAI: 1970-01-01 00:00:10 // TAI at Unix Epoch const epochTai64 = (2 << 61) + 10 // GetOffsetUnix returns the TAI64 offset for a UTC unix timestamp // returns int64 offset func GetOffsetUnix(utime int64) int64 { // default offset is 10 offset := int64(10) for i := tia64nSize - 1; i >= 0; i-- { if utime < tia64nDifferences[i].utime { continue } else { offset = tia64nDifferences[i].offset break } } return offset } // GetOffsetTime returns the TAI64 offset for a time.Time in UTC // returns int64 offset func GetOffsetTime(t time.Time) int64 { return GetOffsetUnix(t.UTC().Unix()) } func getInvOffsetTai64(ttime int64) int64 { // default offset is 10 offset := int64(10) // walk backwards because we are looking for // the "bucket" where we fit for i := tia64nSize - 1; i >= 0; i-- { t := tia64nDifferences[i] if ttime < (t.ttime) { continue } else { offset = t.offset break } } return offset } // FormatNano formats a time.Time as a TAI64N timestamp // returns a string TAI64N timestamps func FormatNano(t time.Time) string { t = t.UTC() u := t.Unix() if u < 0 { return fmt.Sprintf("@%016x%08x", epochTai64+u, t.Nanosecond()) } return fmt.Sprintf("@4%015x%08x", u+GetOffsetUnix(u), t.Nanosecond()) } // Format formats a time.Time as a TAI64 timestamp // returns a string TAI64 timestamps func Format(t time.Time) string { u := t.UTC().Unix() if u < 0 { return fmt.Sprintf("@%016x", epochTai64+u) } return fmt.Sprintf("@4%015x", u+GetOffsetUnix(u)) } // Parse parses a TAI64 or TAI64N timestamp // returns a time.Time and an error. func Parse(s string) (time.Time, error) { var tseconds, nanoseconds int64 if s[0] == '@' { s = s[1:] } if len(s) < 16 { return time.Time{}, fmt.Errorf("invalid tai64 time string") } i, err := strconv.ParseInt(s[0:16], 16, 64) if err != nil { return time.Time{}, err } tseconds = i s = s[16:] // Check for TAI64N or TAI64NA format slen := len(s) if slen == 8 || slen == 16 { // time.Time is not attoseconds granular, so // just pull off the TAI64N section. i, err := strconv.ParseInt(s[:8], 16, 64) if err != nil { return time.Time{}, err } nanoseconds = i } // if tia seconds are at least TAI epoch, we are in positive unix seconds if tseconds >= epochTai64 { // To get back to unix seconds, we need to... // 1. Figure out the TAI-UTC offset // 2. Subtract TAI epoch plus any additional offset // get inverse offset by Tai lookup offset := getInvOffsetTai64(tseconds) // epochTai64 already has the +10 default offset included, so adjust // for that when subtracting offset. unix := tseconds - epochTai64 - (offset - 10) t := time.Unix(unix, nanoseconds).UTC() return t, nil } // for values below TAI epoch, we can just subtract seconds from TIA epoch, // then change sign. unix := -(epochTai64 - tseconds) t := time.Unix(unix, nanoseconds).UTC() return t, nil } golang-github-cactus-tai64-1.0.3/time_test.go000066400000000000000000000072211503475376100210100ustar00rootroot00000000000000// Copyright (c) 2012-2016 Eli Janssen // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package tai64 import ( "sort" "testing" "time" ) var tests = map[string]struct { t string o string }{ "a while ago": {"1920-01-01T00:00:00Z", "@3fffffffa1f2cd8a00000000"}, "before tai swap": {"1969-12-31T23:59:49Z", "@3fffffffffffffff00000000"}, "at tai swap": {"1969-12-31T23:59:50Z", "@400000000000000000000000"}, "after tai swap": {"1969-12-31T23:59:51Z", "@400000000000000100000000"}, "before unix epoch": {"1969-12-31T23:59:59Z", "@400000000000000900000000"}, "at unix epoch": {"1970-01-01T00:00:00Z", "@400000000000000a00000000"}, "after unix epoch": {"1970-01-01T00:00:01Z", "@400000000000000b00000000"}, "before tai-utc epoch": {"1970-01-01T00:00:09Z", "@400000000000001300000000"}, "at tai-utc epoch": {"1970-01-01T00:00:10Z", "@400000000000001400000000"}, "after tai-utc epoch": {"1970-01-01T00:00:11Z", "@400000000000001500000000"}, "right before adjust 1": {"1972-06-30T23:59:59Z", "@4000000004b2580900000000"}, "right at adjust 1": {"1972-07-01T00:00:00Z", "@4000000004b2580b00000000"}, "right after adjust 1": {"1972-07-01T00:00:01Z", "@4000000004b2580c00000000"}, "right before adjust 2": {"2016-12-31T23:59:59Z", "@40000000586846a300000000"}, "right at adjust 2": {"2017-01-01T00:00:00Z", "@40000000586846a500000000"}, "right after adjust 2": {"2017-01-01T00:00:01Z", "@40000000586846a600000000"}, "nanoseconds": {"2015-06-30T23:59:59.908626131Z", "@4000000055932da2362888d3"}, } func getOrderedTestNames() []string { var keys []string for k := range tests { keys = append(keys, k) } sort.Strings(keys) return keys } func TestRoundTripNano(t *testing.T) { for _, name := range getOrderedTestNames() { tt := tests[name] tm, err := time.Parse(time.RFC3339Nano, tt.t) if err != nil { t.Fatalf("%s: test failed parsing time.Time", name) } o := FormatNano(tm) p, err := Parse(o) if err != nil { t.Fatalf("%s: test failed parsing", name) } if tm != p { t.Fatalf("%s: test failed date compare:\n %s != %s", name, tm, p) } } } func TestRoundTrip(t *testing.T) { for _, name := range getOrderedTestNames() { tt := tests[name] tm, err := time.Parse(time.RFC3339Nano, tt.t) if err != nil { t.Fatalf("%s: test failed parsing time.Time", name) } o := Format(tm) p, err := Parse(o[:17]) if err != nil { t.Fatalf("%s: test failed parsing", name) } ts := tm.Truncate(time.Second) if ts != p { t.Fatalf("%s: test failed date compare:\n %s != %s", name, ts, p) } } } func TestFormat(t *testing.T) { for _, name := range getOrderedTestNames() { tt := tests[name] tm, err := time.Parse(time.RFC3339Nano, tt.t) if err != nil { t.Fatalf("%s: test failed parsing time.Time", name) } o := FormatNano(tm) if tt.o != o { t.Fatalf("%s: test failed date compare:\n %s != %s", name, tt.o, o) } } } func TestParse(t *testing.T) { for _, name := range getOrderedTestNames() { tt := tests[name] tm, err := time.Parse(time.RFC3339Nano, tt.t) if err != nil { t.Fatalf("%s: test failed parsing time.Time", name) } p, err := Parse(tt.o) if err != nil { t.Fatalf("%s: test failed parsing", name) } if tm != p { t.Fatalf("%s: test failed date compare:\n %s != %s", name, tm, p) } } } func BenchmarkFormat(b *testing.B) { t := time.Date(2016, 12, 31, 23, 59, 59, 00, time.UTC) b.ResetTimer() for i := 0; i < b.N; i++ { Format(t) } } func BenchmarkParse(b *testing.B) { s := "@40000000586846a300000000" b.ResetTimer() for i := 0; i < b.N; i++ { Parse(s) } } golang-github-cactus-tai64-1.0.3/tools/000077500000000000000000000000001503475376100176225ustar00rootroot00000000000000golang-github-cactus-tai64-1.0.3/tools/generate.go000066400000000000000000000055101503475376100217440ustar00rootroot00000000000000// Copyright (c) 2012-2016 Eli Janssen // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. package main import ( "bufio" "bytes" "flag" "fmt" "go/format" "html/template" "log" "net/http" "os" "path" "strconv" "strings" "time" ) const tplText = ` // Copyright (c) 2012-2016 Eli Janssen // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. // THIS FILE IS AUTOGENERATED. DO NOT EDIT! package {{.Pkg}} // https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat // http://www.stjarnhimlen.se/comp/time.html var tia64nDifferences = []struct { // TAI time ttime int64 // TAI-UTC offset offset int64 // unix UTC time utime int64 }{ {{- range .Entries}} {{"{"}}0x{{.Tts|printf "%x"}}, {{.Drift}}, {{.Ts}}{{"}"}}, // {{.TsD}} {{- end}} } var tia64nSize = len(tia64nDifferences) ` type srcFile struct { Pkg string Entries []entry } type entry struct { Ts int64 TsD string Drift int64 Tts int64 } const datURL = "https://hpiers.obspm.fr/iers/bul/bulc/Leap_Second.dat" func main() { var output, pkg string flag.StringVar(&output, "output", "", "output file") flag.StringVar(&pkg, "pkg", "", "package name") flag.Parse() if output == "" { log.Fatal("Output option is required") } if pkg == "" { log.Fatal("Package option is required") } var br *bufio.Reader resp, err := http.Get(datURL) if err != nil { log.Fatalf("Error fetching '%s': %s", datURL, err) } defer resp.Body.Close() br = bufio.NewReader(resp.Body) fmt.Printf("Generating '%s' based on '%s'\n", path.Base(output), datURL) t, err := template.New("fileTemplate").Parse(strings.TrimLeft(tplText, "\n")) if err != nil { log.Fatal(err) } entries := make([]entry, 0) for { line, err := br.ReadString('\n') if err != nil { break } line = strings.TrimSpace(line) if line == "" || strings.HasPrefix(line, ";") || strings.HasPrefix(line, "#") { continue } parts := strings.Fields(line) // parse date of leap second t, err := time.Parse("2-1-2006", strings.Join(parts[1:4], "-")) if err != nil { fmt.Println(err) continue } if t.Before(time.Date(1972, 1, 1, 0, 0, 0, 0, time.UTC)) { continue } // parse TAI-UTC(s) s, err := strconv.ParseFloat(parts[4], 32) if err != nil { fmt.Println(err) continue } // just truncate the float to int unixT := t.Unix() taiT := (2 << 61) + unixT + int64(s) e := entry{unixT, t.Format(time.RFC3339), int64(s), taiT} entries = append(entries, e) } bufr := &bytes.Buffer{} srcfile := &srcFile{pkg, entries} err = t.Execute(bufr, srcfile) if err != nil { log.Fatal(err) } outBytes, err := format.Source(bufr.Bytes()) if err != nil { log.Fatal(err) } err = os.WriteFile(output, outBytes, 0644) if err != nil { log.Fatal(err) } }