diff --git a/lib/datetime/datetime.ex b/lib/datetime/datetime.ex index c4e65498..4a6e918f 100644 --- a/lib/datetime/datetime.ex +++ b/lib/datetime/datetime.ex @@ -245,14 +245,22 @@ defimpl Timex.Protocol, for: DateTime do shifted_us = us + shift shifted_secs = div(shifted_us, 1_000*1_000) + (applied_offset_ms * -1) rem_us = rem(shifted_us, 1_000*1_000) + new_precision = + case Timex.DateTime.Helpers.precision(rem_us) do + np when np < precision -> + precision + + np -> + np + end # Convert back to original timezone - case raw_convert(shifted_secs, {rem_us, precision}, tz, :wall) do + case raw_convert(shifted_secs, {rem_us, new_precision}, tz, :wall) do {:error, {:could_not_resolve_timezone, _, _, _}} -> # This occurs when the shifted date/time doesn't exist because of a leap forward # This doesn't mean the shift is invalid, simply that we need to ask for the right wall time # Which in these cases means asking for the time + 1h - raw_convert(shifted_secs + 3600, {rem_us, precision}, tz, :wall) + raw_convert(shifted_secs + 3600, {rem_us, new_precision}, tz, :wall) result -> result end diff --git a/lib/datetime/helpers.ex b/lib/datetime/helpers.ex index 42961e61..2d86ce2f 100644 --- a/lib/datetime/helpers.ex +++ b/lib/datetime/helpers.ex @@ -92,8 +92,8 @@ defmodule Timex.DateTime.Helpers do end end - defp precision(0), do: 0 - defp precision(n) when is_integer(n) do + def precision(0), do: 0 + def precision(n) when is_integer(n) do ns = Integer.to_string(n) n_width = byte_size(ns) trimmed = byte_size(String.trim_trailing(ns, "0")) diff --git a/test/helpers/property_helpers.ex b/test/helpers/property_helpers.ex index 375cf988..7a3abbeb 100644 --- a/test/helpers/property_helpers.ex +++ b/test/helpers/property_helpers.ex @@ -4,11 +4,18 @@ defmodule PropertyHelpers do """ def date_time_generator(:struct) do - date_time_generator(:tupple) - |> StreamData.map(&Timex.to_datetime/1) + [ + date_time_generator(:tuple), + microsecond_generator() + ] + |> StreamData.fixed_list() + |> StreamData.map(fn [{{y, m, d}, {h, mm, s}}, {us, p}] -> + {:ok, nd} = NaiveDateTime.new(y, m, d, h, mm, s, {us, p}) + DateTime.from_naive!(nd, "Etc/UTC") + end) end - def date_time_generator(:tupple) do + def date_time_generator(:tuple) do [ StreamData.integer(2000..2030), StreamData.integer(1..12), @@ -18,6 +25,9 @@ defmodule PropertyHelpers do StreamData.integer(0..59) ] |> StreamData.fixed_list() + |> StreamData.filter(fn [year, month, day, _hour, _minute, _second] -> + :calendar.valid_date(year, month, day) + end) |> StreamData.map(fn [year, month, day, hour, minute, second] -> {{year, month, day}, {hour, minute, second}} end) @@ -27,4 +37,15 @@ defmodule PropertyHelpers do Timex.timezones() |> StreamData.member_of() end -end \ No newline at end of file + + def microsecond_generator() do + [ + StreamData.integer(0..999_999), + StreamData.integer(1..6) + ] + |> StreamData.fixed_list() + |> StreamData.map(fn [us, precision] -> + {us, precision} + end) + end +end diff --git a/test/set_test.exs b/test/set_test.exs index e6f64cf4..a6f087b4 100644 --- a/test/set_test.exs +++ b/test/set_test.exs @@ -5,16 +5,16 @@ defmodule SetTests do property "setting all the properties from the target date should become a target date for a DateTime" do check all input_date <- PropertyHelpers.date_time_generator(:struct), - {{year, month, day}, {hour, minute, second}} = target_date <- PropertyHelpers.date_time_generator(:tupple) do + {{year, month, day}, {hour, minute, second}} = target_date <- PropertyHelpers.date_time_generator(:tuple) do date = Timex.set(input_date, [year: year, month: month, day: day, hour: hour, minute: minute, second: second]) assert Timex.to_erl(date) == target_date end end - property "setting all the properties from the target date should become a target date for a tupple" do - check all input_date <- PropertyHelpers.date_time_generator(:tupple), - {{year, month, day}, {hour, minute, second}} = target_date <- PropertyHelpers.date_time_generator(:tupple) do + property "setting all the properties from the target date should become a target date for a tuple" do + check all input_date <- PropertyHelpers.date_time_generator(:tuple), + {{year, month, day}, {hour, minute, second}} = target_date <- PropertyHelpers.date_time_generator(:tuple) do date = Timex.set(input_date, [year: year, month: month, day: day, hour: hour, minute: minute, second: second]) assert Timex.to_erl(date) == target_date diff --git a/test/timex_test.exs b/test/timex_test.exs index 39683d55..262e60d0 100644 --- a/test/timex_test.exs +++ b/test/timex_test.exs @@ -34,9 +34,9 @@ defmodule TimexTests do test "subtract milliseconds" do time = Timex.to_datetime({{2015, 6, 24}, {14, 27, 52}}) - time = %{time | microsecond: {910_000, 6}} + time = %{time | microsecond: {910_000, 2}} subtracted = Timex.subtract(time, Duration.from_milliseconds(10)) - assert subtracted.microsecond === {900_000, 6} + assert subtracted.microsecond === {900_000, 2} end test "weekday" do diff --git a/test/timezone_test.exs b/test/timezone_test.exs index 162f5986..ef3c7eee 100644 --- a/test/timezone_test.exs +++ b/test/timezone_test.exs @@ -69,7 +69,7 @@ defmodule TimezoneTests do end property "convert always returns DateTime or AmbiguousDateTime" do - check all input_date <- PropertyHelpers.date_time_generator(:tupple), + check all input_date <- PropertyHelpers.date_time_generator(:tuple), timezone <- PropertyHelpers.timezone_generator() do result = Timezone.convert(input_date, timezone) assert match?(%DateTime{}, result) || match?(%Timex.AmbiguousDateTime{}, result)