Skip to content

Commit 9ee78cf

Browse files
briansorahantleb
andauthored
Timetag fix test (#22)
* Add Go modules to allow compiling * Fix compilation issues * Fix the timetag type The lower 32-bits are fractions of a second. That is, each increment increases the time by 1/(2^32) seconds, or 1e9/(2^32) ns ~ 0.233ns. Previously, each increment was counted as a full nanosecond. That could lead to errors of up to (2^32-1) ns - (2^32-1)*1e9/(2^32) ns ~= 3.3s! Also, if only the first bit is set, the value should be interpreted in a particular way. The choice made is to turn it into the zero time: time.Time{}. It makes it easy to identify using time.Time.IsZero(). * add a TimeTag test --------- Co-authored-by: tleb <[email protected]>
1 parent 051701f commit 9ee78cf

File tree

2 files changed

+44
-43
lines changed

2 files changed

+44
-43
lines changed

timetag.go

+26-17
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ import (
1010

1111
const (
1212
// SecondsFrom1900To1970 is exactly what it sounds like.
13-
SecondsFrom1900To1970 = 2208988800
14-
// SecondsFrom1900To1970 = 2207520000
13+
SecondsFrom1900To1970 = 2208988800 // Source: RFC 868
14+
15+
nanosecondsPerFraction = float64(0.23283064365386962891) // 1e9/(2^32)
1516

1617
// TimetagSize is the number of 8-bit bytes in an OSC timetag.
1718
TimetagSize = 8
@@ -43,30 +44,38 @@ func (tt Timetag) String() string {
4344

4445
// Time converts an OSC timetag to a time.Time.
4546
func (tt Timetag) Time() time.Time {
46-
secs := (uint64(tt) >> 32) - SecondsFrom1900To1970
47-
return time.Unix(int64(secs), int64(tt)&0xFFFFFFFF).UTC()
47+
t := uint64(tt)
48+
49+
if t == 1 {
50+
// Means "immediately". It cannot occur otherwise as timetag == 0 gets
51+
// converted to January 1, 1900 while time.Time{} means year 1 in Go.
52+
// Use the time.Time.IsZero() method to detect it.
53+
return time.Time{}
54+
}
55+
56+
return time.Unix(
57+
int64(t>>32)-SecondsFrom1900To1970,
58+
int64(nanosecondsPerFraction*float64(t&(1<<32-1))),
59+
).UTC()
4860
}
4961

5062
// FromTime converts the given time to an OSC timetag.
5163
func FromTime(t time.Time) Timetag {
52-
t = t.UTC()
53-
secs := uint64((SecondsFrom1900To1970 + t.Unix()) << 32)
54-
return Timetag(secs + uint64(uint32(t.Nanosecond())))
64+
if t.IsZero() {
65+
return 1
66+
}
67+
68+
seconds := uint64(t.Unix() + SecondsFrom1900To1970)
69+
secondFraction := float64(t.UTC().Nanosecond()) / nanosecondsPerFraction
70+
return Timetag((seconds << 32) + uint64(uint32(secondFraction)))
5571
}
5672

5773
// ReadTimetag parses a timetag from a byte slice.
5874
func ReadTimetag(data []byte) (Timetag, error) {
5975
if len(data) < TimetagSize {
6076
return Timetag(0), errors.New("timetags must be 64-bit")
6177
}
62-
zero := []byte{0, 0, 0, 0}
63-
var (
64-
L = append(zero, data[:TimetagSize/2]...)
65-
R = append(zero, data[TimetagSize/2:]...)
66-
secs uint64
67-
nsecs uint64
68-
)
69-
_ = binary.Read(bytes.NewReader(L), byteOrder, &secs) // Never fails
70-
_ = binary.Read(bytes.NewReader(R), byteOrder, &nsecs) // Never fails
71-
return Timetag((secs << 32) + nsecs), nil
78+
var tt uint64
79+
_ = binary.Read(bytes.NewReader(data), binary.BigEndian, &tt)
80+
return Timetag(tt), nil
7281
}

timetag_test.go

+18-26
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,48 @@
11
package osc
22

33
import (
4-
"bytes"
54
"testing"
65
"time"
76

87
"github.com/pkg/errors"
98
)
109

10+
func TestImmediately(t *testing.T) {
11+
if !Immediately.Time().IsZero() {
12+
t.Fatalf("expected Immediately to convert to the zero time")
13+
}
14+
}
15+
1116
func TestFromTime(t *testing.T) {
1217
// Test converting to/from time.Time
1318
for _, testcase := range []struct {
1419
Input Timetag
1520
Expected time.Time
1621
}{
17-
{
18-
Input: FromTime(time.Unix(0, 0)),
19-
Expected: time.Unix(0, 0),
20-
},
22+
{Input: FromTime(time.Unix(0, 0)), Expected: time.Unix(0, 0)},
23+
{Input: FromTime(time.Time{}), Expected: time.Time{}},
2124
} {
2225
if expected, got := testcase.Expected, testcase.Input.Time(); !expected.Equal(got) {
2326
t.Fatalf("expected %s, got %s", expected, got)
2427
}
2528
}
2629
}
2730

28-
func TestTimetagBytes(t *testing.T) {
29-
for _, testcase := range []struct {
30-
Input Timetag
31-
Expected []byte
32-
}{
33-
{
34-
Input: Timetag(0),
35-
Expected: []byte{0, 0, 0, 0, 0, 0, 0, 0},
36-
},
37-
{
38-
Input: Timetag(10),
39-
Expected: []byte{0, 0, 0, 0, 0, 0, 0, 0x0A},
40-
},
41-
} {
42-
if expected, got := testcase.Expected, testcase.Input.Bytes(); !bytes.Equal(expected, got) {
43-
t.Fatalf("expected, %q, got %q", expected, got)
44-
}
45-
}
46-
}
47-
4831
func TestTimetagString(t *testing.T) {
4932
for _, testcase := range []struct {
5033
Input Timetag
5134
Expected string
5235
}{
53-
{Input: Timetag(10), Expected: "1900-01-01T00:00:00Z"},
36+
// 0s + 0 * 0.233ns
37+
{Input: Timetag(0), Expected: "1900-01-01T00:00:00Z"},
38+
// "immediately" special value
39+
{Input: Timetag(1), Expected: "0001-01-01T00:00:00Z"},
40+
// 0s + 2 * 0.233ns
41+
{Input: Timetag(2), Expected: "1900-01-01T00:00:00Z"},
42+
// 0s + (2^32-1)/(2^32) seconds
43+
{Input: Timetag(0xFFFFFFFF), Expected: "1900-01-01T00:00:00Z"},
44+
// 1s + 0 * 0.233ns
45+
{Input: Timetag(0x100000000), Expected: "1900-01-01T00:00:01Z"},
5446
} {
5547
if expected, got := testcase.Expected, testcase.Input.String(); expected != got {
5648
t.Fatalf("expected, %s, got %s", expected, got)

0 commit comments

Comments
 (0)