1Code.require_file("../test_helper.exs", __DIR__)
2Code.require_file("holocene.exs", __DIR__)
3Code.require_file("fakes.exs", __DIR__)
4
5defmodule NaiveDateTimeTest do
6  use ExUnit.Case, async: true
7  doctest NaiveDateTime
8
9  test "sigil_N" do
10    assert ~N[2000-01-01T12:34:56] ==
11             %NaiveDateTime{
12               calendar: Calendar.ISO,
13               year: 2000,
14               month: 1,
15               day: 1,
16               hour: 12,
17               minute: 34,
18               second: 56
19             }
20
21    assert ~N[2000-01-01T12:34:56 Calendar.Holocene] ==
22             %NaiveDateTime{
23               calendar: Calendar.Holocene,
24               year: 2000,
25               month: 1,
26               day: 1,
27               hour: 12,
28               minute: 34,
29               second: 56
30             }
31
32    assert ~N[2000-01-01 12:34:56] ==
33             %NaiveDateTime{
34               calendar: Calendar.ISO,
35               year: 2000,
36               month: 1,
37               day: 1,
38               hour: 12,
39               minute: 34,
40               second: 56
41             }
42
43    assert ~N[2000-01-01 12:34:56 Calendar.Holocene] ==
44             %NaiveDateTime{
45               calendar: Calendar.Holocene,
46               year: 2000,
47               month: 1,
48               day: 1,
49               hour: 12,
50               minute: 34,
51               second: 56
52             }
53
54    assert_raise ArgumentError,
55                 ~s/cannot parse "2001-50-50T12:34:56" as NaiveDateTime for Calendar.ISO, reason: :invalid_date/,
56                 fn -> Code.eval_string("~N[2001-50-50T12:34:56]") end
57
58    assert_raise ArgumentError,
59                 ~s/cannot parse "2001-01-01T12:34:65" as NaiveDateTime for Calendar.ISO, reason: :invalid_time/,
60                 fn -> Code.eval_string("~N[2001-01-01T12:34:65]") end
61
62    assert_raise ArgumentError,
63                 ~s/cannot parse "20010101 123456" as NaiveDateTime for Calendar.ISO, reason: :invalid_format/,
64                 fn -> Code.eval_string(~s{~N[20010101 123456]}) end
65
66    assert_raise ArgumentError,
67                 ~s/cannot parse "2001-01-01 12:34:56 notalias" as NaiveDateTime for Calendar.ISO, reason: :invalid_format/,
68                 fn -> Code.eval_string("~N[2001-01-01 12:34:56 notalias]") end
69
70    assert_raise ArgumentError,
71                 ~s/cannot parse "2001-01-01T12:34:56 notalias" as NaiveDateTime for Calendar.ISO, reason: :invalid_format/,
72                 fn -> Code.eval_string("~N[2001-01-01T12:34:56 notalias]") end
73
74    assert_raise ArgumentError,
75                 ~s/cannot parse "2001-50-50T12:34:56" as NaiveDateTime for Calendar.Holocene, reason: :invalid_date/,
76                 fn -> Code.eval_string("~N[2001-50-50T12:34:56 Calendar.Holocene]") end
77
78    assert_raise ArgumentError,
79                 ~s/cannot parse "2001-01-01T12:34:65" as NaiveDateTime for Calendar.Holocene, reason: :invalid_time/,
80                 fn -> Code.eval_string("~N[2001-01-01T12:34:65 Calendar.Holocene]") end
81
82    assert_raise UndefinedFunctionError, fn ->
83      Code.eval_string("~N[2001-01-01 12:34:56 UnknownCalendar]")
84    end
85
86    assert_raise UndefinedFunctionError, fn ->
87      Code.eval_string("~N[2001-01-01T12:34:56 UnknownCalendar]")
88    end
89  end
90
91  test "to_string/1" do
92    assert to_string(~N[2000-01-01 23:00:07.005]) == "2000-01-01 23:00:07.005"
93    assert NaiveDateTime.to_string(~N[2000-01-01 23:00:07.005]) == "2000-01-01 23:00:07.005"
94
95    ndt = %{~N[2000-01-01 23:00:07.005] | calendar: FakeCalendar}
96    assert to_string(ndt) == "1/1/2000F23::0::7"
97  end
98
99  test "inspect/1" do
100    assert inspect(~N[2000-01-01 23:00:07.005]) == "~N[2000-01-01 23:00:07.005]"
101    assert inspect(~N[-0100-12-31 23:00:07.005]) == "~N[-0100-12-31 23:00:07.005]"
102
103    ndt = %{~N[2000-01-01 23:00:07.005] | calendar: FakeCalendar}
104    assert inspect(ndt) == "~N[1/1/2000F23::0::7 FakeCalendar]"
105  end
106
107  test "compare/2" do
108    ndt1 = ~N[2000-04-16 13:30:15.0049]
109    ndt2 = ~N[2000-04-16 13:30:15.0050]
110    ndt3 = ~N[2001-04-16 13:30:15.0050]
111    ndt4 = ~N[-0001-04-16 13:30:15.004]
112    assert NaiveDateTime.compare(ndt1, ndt1) == :eq
113    assert NaiveDateTime.compare(ndt1, ndt2) == :lt
114    assert NaiveDateTime.compare(ndt2, ndt1) == :gt
115    assert NaiveDateTime.compare(ndt3, ndt1) == :gt
116    assert NaiveDateTime.compare(ndt3, ndt2) == :gt
117    assert NaiveDateTime.compare(ndt4, ndt4) == :eq
118    assert NaiveDateTime.compare(ndt1, ndt4) == :gt
119    assert NaiveDateTime.compare(ndt4, ndt3) == :lt
120  end
121
122  test "to_iso8601/1" do
123    ndt = ~N[2000-04-16 12:34:15.1234]
124    ndt = put_in(ndt.calendar, FakeCalendar)
125
126    message =
127      "cannot convert #{inspect(ndt)} to target calendar Calendar.ISO, " <>
128        "reason: #{inspect(ndt.calendar)} and Calendar.ISO have different day rollover moments, " <>
129        "making this conversion ambiguous"
130
131    assert_raise ArgumentError, message, fn ->
132      NaiveDateTime.to_iso8601(ndt)
133    end
134  end
135
136  test "add/2 with other calendars" do
137    assert ~N[2000-01-01 12:34:15.123456]
138           |> NaiveDateTime.convert!(Calendar.Holocene)
139           |> NaiveDateTime.add(10, :second) ==
140             %NaiveDateTime{
141               calendar: Calendar.Holocene,
142               year: 12000,
143               month: 1,
144               day: 1,
145               hour: 12,
146               minute: 34,
147               second: 25,
148               microsecond: {123_456, 6}
149             }
150  end
151
152  test "add/2 with datetime" do
153    dt = %DateTime{
154      year: 2000,
155      month: 2,
156      day: 29,
157      zone_abbr: "CET",
158      hour: 23,
159      minute: 0,
160      second: 7,
161      microsecond: {0, 0},
162      utc_offset: 3600,
163      std_offset: 0,
164      time_zone: "Europe/Warsaw"
165    }
166
167    assert NaiveDateTime.add(dt, 21, :second) == ~N[2000-02-29 23:00:28]
168  end
169
170  test "diff/2 with other calendars" do
171    assert ~N[2000-01-01 12:34:15.123456]
172           |> NaiveDateTime.convert!(Calendar.Holocene)
173           |> NaiveDateTime.add(10, :second)
174           |> NaiveDateTime.diff(~N[2000-01-01 12:34:15.123456]) == 10
175  end
176
177  test "diff/2 with datetime" do
178    dt = %DateTime{
179      year: 2000,
180      month: 2,
181      day: 29,
182      zone_abbr: "CET",
183      hour: 23,
184      minute: 0,
185      second: 7,
186      microsecond: {0, 0},
187      utc_offset: 3600,
188      std_offset: 0,
189      time_zone: "Europe/Warsaw"
190    }
191
192    assert NaiveDateTime.diff(%{dt | second: 57}, dt, :second) == 50
193  end
194
195  test "convert/2" do
196    assert NaiveDateTime.convert(~N[2000-01-01 12:34:15.123400], Calendar.Holocene) ==
197             {:ok, Calendar.Holocene.naive_datetime(12000, 1, 1, 12, 34, 15, {123_400, 6})}
198
199    assert ~N[2000-01-01 12:34:15]
200           |> NaiveDateTime.convert!(Calendar.Holocene)
201           |> NaiveDateTime.convert!(Calendar.ISO) == ~N[2000-01-01 12:34:15]
202
203    assert ~N[2000-01-01 12:34:15.123456]
204           |> NaiveDateTime.convert!(Calendar.Holocene)
205           |> NaiveDateTime.convert!(Calendar.ISO) == ~N[2000-01-01 12:34:15.123456]
206
207    assert NaiveDateTime.convert(~N[2016-02-03 00:00:01], FakeCalendar) ==
208             {:error, :incompatible_calendars}
209
210    assert NaiveDateTime.convert(~N[1970-01-01 00:00:00], Calendar.Holocene) ==
211             {:ok, Calendar.Holocene.naive_datetime(11970, 1, 1, 0, 0, 0, {0, 0})}
212
213    assert NaiveDateTime.convert(DateTime.from_unix!(0, :second), Calendar.Holocene) ==
214             {:ok, Calendar.Holocene.naive_datetime(11970, 1, 1, 0, 0, 0, {0, 0})}
215  end
216
217  test "truncate/2" do
218    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :microsecond) ==
219             ~N[2017-11-06 00:23:51.123456]
220
221    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.0], :millisecond) ==
222             ~N[2017-11-06 00:23:51.0]
223
224    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.999], :millisecond) ==
225             ~N[2017-11-06 00:23:51.999]
226
227    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.1009], :millisecond) ==
228             ~N[2017-11-06 00:23:51.100]
229
230    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :millisecond) ==
231             ~N[2017-11-06 00:23:51.123]
232
233    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.000456], :millisecond) ==
234             ~N[2017-11-06 00:23:51.000]
235
236    assert NaiveDateTime.truncate(~N[2017-11-06 00:23:51.123456], :second) ==
237             ~N[2017-11-06 00:23:51]
238  end
239
240  test "truncate/2 with datetime" do
241    dt = %DateTime{
242      year: 2000,
243      month: 2,
244      day: 29,
245      zone_abbr: "CET",
246      hour: 23,
247      minute: 0,
248      second: 7,
249      microsecond: {3000, 6},
250      utc_offset: 3600,
251      std_offset: 0,
252      time_zone: "Europe/Warsaw"
253    }
254
255    assert NaiveDateTime.truncate(dt, :millisecond) == ~N[2000-02-29 23:00:07.003]
256    assert catch_error(NaiveDateTime.truncate(~T[00:00:00.000000], :millisecond))
257  end
258
259  describe "utc_now/1" do
260    test "utc_now/1 with default calendar (ISO)" do
261      naive_datetime = NaiveDateTime.utc_now()
262      assert naive_datetime.year >= 2019
263    end
264
265    test "utc_now/1 with alternative calendar" do
266      naive_datetime = NaiveDateTime.utc_now(Calendar.Holocene)
267      assert naive_datetime.calendar == Calendar.Holocene
268      assert naive_datetime.year >= 12019
269    end
270  end
271
272  describe "local_now/1" do
273    test "local_now/1 with default calendar (ISO)" do
274      naive_datetime = NaiveDateTime.local_now()
275      assert naive_datetime.year >= 2018
276    end
277
278    test "local_now/1 alternative calendar" do
279      naive_datetime = NaiveDateTime.local_now(Calendar.Holocene)
280      assert naive_datetime.calendar == Calendar.Holocene
281      assert naive_datetime.year >= 12018
282    end
283
284    test "local_now/1 incompatible calendar" do
285      assert_raise ArgumentError,
286                   ~s(cannot get "local now" in target calendar FakeCalendar, reason: cannot convert from Calendar.ISO to FakeCalendar.),
287                   fn ->
288                     NaiveDateTime.local_now(FakeCalendar)
289                   end
290    end
291  end
292
293  describe "to_date/2" do
294    test "downcasting" do
295      dt = %DateTime{
296        year: 2000,
297        month: 2,
298        day: 29,
299        zone_abbr: "CET",
300        hour: 23,
301        minute: 0,
302        second: 7,
303        microsecond: {3000, 6},
304        utc_offset: 3600,
305        std_offset: 0,
306        time_zone: "Europe/Warsaw"
307      }
308
309      assert NaiveDateTime.to_date(dt) == ~D[2000-02-29]
310    end
311
312    test "upcasting" do
313      assert catch_error(NaiveDateTime.to_date(~D[2000-02-29]))
314    end
315  end
316
317  describe "to_time/2" do
318    test "downcasting" do
319      dt = %DateTime{
320        year: 2000,
321        month: 2,
322        day: 29,
323        zone_abbr: "CET",
324        hour: 23,
325        minute: 0,
326        second: 7,
327        microsecond: {3000, 6},
328        utc_offset: 3600,
329        std_offset: 0,
330        time_zone: "Europe/Warsaw"
331      }
332
333      assert NaiveDateTime.to_time(dt) == ~T[23:00:07.003000]
334    end
335
336    test "upcasting" do
337      assert catch_error(NaiveDateTime.to_time(~T[00:00:00.000000]))
338    end
339  end
340end
341