1// +build go1.7
2
3package date
4
5// Copyright 2017 Microsoft Corporation
6//
7//  Licensed under the Apache License, Version 2.0 (the "License");
8//  you may not use this file except in compliance with the License.
9//  You may obtain a copy of the License at
10//
11//      http://www.apache.org/licenses/LICENSE-2.0
12//
13//  Unless required by applicable law or agreed to in writing, software
14//  distributed under the License is distributed on an "AS IS" BASIS,
15//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16//  See the License for the specific language governing permissions and
17//  limitations under the License.
18
19import (
20	"bytes"
21	"encoding/binary"
22	"encoding/json"
23	"fmt"
24	"math"
25	"testing"
26	"time"
27)
28
29func ExampleUnixTime_MarshalJSON() {
30	epoch := UnixTime(UnixEpoch())
31	text, _ := json.Marshal(epoch)
32	fmt.Print(string(text))
33	// Output: 0
34}
35
36func ExampleUnixTime_UnmarshalJSON() {
37	var myTime UnixTime
38	json.Unmarshal([]byte("1.3e2"), &myTime)
39	fmt.Printf("%v", time.Time(myTime))
40	// Output: 1970-01-01 00:02:10 +0000 UTC
41}
42
43func TestUnixTime_MarshalJSON(t *testing.T) {
44	testCases := []time.Time{
45		UnixEpoch().Add(-1 * time.Second),                        // One second befote the Unix Epoch
46		time.Date(2017, time.April, 14, 20, 27, 47, 0, time.UTC), // The time this test was written
47		UnixEpoch(),
48		time.Date(1800, 01, 01, 0, 0, 0, 0, time.UTC),
49		time.Date(2200, 12, 29, 00, 01, 37, 82, time.UTC),
50	}
51
52	for _, tc := range testCases {
53		t.Run(tc.String(), func(subT *testing.T) {
54			var actual, expected float64
55			var marshaled []byte
56
57			target := UnixTime(tc)
58			expected = float64(target.Duration().Nanoseconds()) / 1e9
59
60			if temp, err := json.Marshal(target); err == nil {
61				marshaled = temp
62			} else {
63				subT.Error(err)
64				return
65			}
66
67			dec := json.NewDecoder(bytes.NewReader(marshaled))
68			if err := dec.Decode(&actual); err != nil {
69				subT.Error(err)
70				return
71			}
72
73			diff := math.Abs(actual - expected)
74			subT.Logf("\ngot :\t%g\nwant:\t%g\ndiff:\t%g", actual, expected, diff)
75			if diff > 1e-9 { //Must be within 1 nanosecond of one another
76				subT.Fail()
77			}
78		})
79	}
80}
81
82func TestUnixTime_UnmarshalJSON(t *testing.T) {
83	testCases := []struct {
84		text     string
85		expected time.Time
86	}{
87		{"1", UnixEpoch().Add(time.Second)},
88		{"0", UnixEpoch()},
89		{"1492203742", time.Date(2017, time.April, 14, 21, 02, 22, 0, time.UTC)}, // The time this test was written
90		{"-1", time.Date(1969, time.December, 31, 23, 59, 59, 0, time.UTC)},
91		{"1.5", UnixEpoch().Add(1500 * time.Millisecond)},
92		{"0e1", UnixEpoch()}, // See http://json.org for 'number' format definition.
93		{"1.3e+2", UnixEpoch().Add(130 * time.Second)},
94		{"1.6E-10", UnixEpoch()}, // This is so small, it should get truncated into the UnixEpoch
95		{"2E-6", UnixEpoch().Add(2 * time.Microsecond)},
96		{"1.289345e9", UnixEpoch().Add(1289345000 * time.Second)},
97		{"1e-9", UnixEpoch().Add(time.Nanosecond)},
98	}
99
100	for _, tc := range testCases {
101		t.Run(tc.text, func(subT *testing.T) {
102			var rehydrated UnixTime
103			if err := json.Unmarshal([]byte(tc.text), &rehydrated); err != nil {
104				subT.Error(err)
105				return
106			}
107
108			if time.Time(rehydrated) != tc.expected {
109				subT.Logf("\ngot: \t%v\nwant:\t%v\ndiff:\t%v", time.Time(rehydrated), tc.expected, time.Time(rehydrated).Sub(tc.expected))
110				subT.Fail()
111			}
112		})
113	}
114}
115
116func TestUnixTime_JSONRoundTrip(t *testing.T) {
117	testCases := []time.Time{
118		UnixEpoch(),
119		time.Date(2005, time.November, 5, 0, 0, 0, 0, time.UTC), // The day V for Vendetta (film) was released.
120		UnixEpoch().Add(-6 * time.Second),
121		UnixEpoch().Add(800 * time.Hour),
122		UnixEpoch().Add(time.Nanosecond),
123		time.Date(2015, time.September, 05, 4, 30, 12, 9992, time.UTC),
124	}
125
126	for _, tc := range testCases {
127		t.Run(tc.String(), func(subT *testing.T) {
128			subject := UnixTime(tc)
129			var marshaled []byte
130			if temp, err := json.Marshal(subject); err == nil {
131				marshaled = temp
132			} else {
133				subT.Error(err)
134				return
135			}
136
137			var unmarshaled UnixTime
138			if err := json.Unmarshal(marshaled, &unmarshaled); err != nil {
139				subT.Error(err)
140			}
141
142			actual := time.Time(unmarshaled)
143			diff := actual.Sub(tc)
144			subT.Logf("\ngot :\t%s\nwant:\t%s\ndiff:\t%s", actual.String(), tc.String(), diff.String())
145
146			if diff > time.Duration(100) { // We lose some precision be working in floats. We shouldn't lose more than 100 nanoseconds.
147				subT.Fail()
148			}
149		})
150	}
151}
152
153func TestUnixTime_MarshalBinary(t *testing.T) {
154	testCases := []struct {
155		expected int64
156		subject  time.Time
157	}{
158		{0, UnixEpoch()},
159		{-15 * int64(time.Second), UnixEpoch().Add(-15 * time.Second)},
160		{54, UnixEpoch().Add(54 * time.Nanosecond)},
161	}
162
163	for _, tc := range testCases {
164		t.Run("", func(subT *testing.T) {
165			var marshaled []byte
166
167			if temp, err := UnixTime(tc.subject).MarshalBinary(); err == nil {
168				marshaled = temp
169			} else {
170				subT.Error(err)
171				return
172			}
173
174			var unmarshaled int64
175			if err := binary.Read(bytes.NewReader(marshaled), binary.LittleEndian, &unmarshaled); err != nil {
176				subT.Error(err)
177				return
178			}
179
180			if unmarshaled != tc.expected {
181				subT.Logf("\ngot: \t%d\nwant:\t%d", unmarshaled, tc.expected)
182				subT.Fail()
183			}
184		})
185	}
186}
187
188func TestUnixTime_BinaryRoundTrip(t *testing.T) {
189	testCases := []time.Time{
190		UnixEpoch(),
191		UnixEpoch().Add(800 * time.Minute),
192		UnixEpoch().Add(7 * time.Hour),
193		UnixEpoch().Add(-1 * time.Nanosecond),
194	}
195
196	for _, tc := range testCases {
197		t.Run(tc.String(), func(subT *testing.T) {
198			original := UnixTime(tc)
199			var marshaled []byte
200
201			if temp, err := original.MarshalBinary(); err == nil {
202				marshaled = temp
203			} else {
204				subT.Error(err)
205				return
206			}
207
208			var traveled UnixTime
209			if err := traveled.UnmarshalBinary(marshaled); err != nil {
210				subT.Error(err)
211				return
212			}
213
214			if traveled != original {
215				subT.Logf("\ngot: \t%s\nwant:\t%s", time.Time(original).String(), time.Time(traveled).String())
216				subT.Fail()
217			}
218		})
219	}
220}
221
222func TestUnixTime_MarshalText(t *testing.T) {
223	testCases := []time.Time{
224		UnixEpoch(),
225		UnixEpoch().Add(45 * time.Second),
226		UnixEpoch().Add(time.Nanosecond),
227		UnixEpoch().Add(-100000 * time.Second),
228	}
229
230	for _, tc := range testCases {
231		expected, _ := tc.MarshalText()
232		t.Run("", func(subT *testing.T) {
233			var marshaled []byte
234
235			if temp, err := UnixTime(tc).MarshalText(); err == nil {
236				marshaled = temp
237			} else {
238				subT.Error(err)
239				return
240			}
241
242			if string(marshaled) != string(expected) {
243				subT.Logf("\ngot: \t%s\nwant:\t%s", string(marshaled), string(expected))
244				subT.Fail()
245			}
246		})
247	}
248}
249
250func TestUnixTime_TextRoundTrip(t *testing.T) {
251	testCases := []time.Time{
252		UnixEpoch(),
253		UnixEpoch().Add(-1 * time.Nanosecond),
254		UnixEpoch().Add(1 * time.Nanosecond),
255		time.Date(2017, time.April, 17, 21, 00, 00, 00, time.UTC),
256	}
257
258	for _, tc := range testCases {
259		t.Run(tc.String(), func(subT *testing.T) {
260			unixTC := UnixTime(tc)
261
262			var marshaled []byte
263
264			if temp, err := unixTC.MarshalText(); err == nil {
265				marshaled = temp
266			} else {
267				subT.Error(err)
268				return
269			}
270
271			var unmarshaled UnixTime
272			if err := unmarshaled.UnmarshalText(marshaled); err != nil {
273				subT.Error(err)
274				return
275			}
276
277			if unmarshaled != unixTC {
278				t.Logf("\ngot: \t%s\nwant:\t%s", time.Time(unmarshaled).String(), tc.String())
279				t.Fail()
280			}
281		})
282	}
283}
284