From 4c00c0bb681619ff7953db3a73e88decd107de1b Mon Sep 17 00:00:00 2001 From: Theo Fiedler Date: Thu, 13 Apr 2023 11:50:43 +0200 Subject: [PATCH] Fix: preserve datetime precision after Timex.shift/2 Resolves https://github.com/bitwalker/timex/issues/731 Unfortunately this broke with https://github.com/elixir-lang/elixir/commit/5a583c753b96865a7cdec2fb4c1ab9c96b836d24 which was release with Elixir 1.14.3 Elixir 1.13.4: ``` iex(3)> DateTime.utc_now() |> IO.inspect() |> DateTime.truncate(:second) |> IO.inspect() |> Timex.shift(minutes: 1) |> IO.inspect() ~U[2023-04-13 09:56:10.136274Z] ~U[2023-04-13 09:56:10Z] ~U[2023-04-13 09:57:10Z] ``` Elixir 1.14.4: ``` iex(1)> DateTime.utc_now() |> IO.inspect() |> DateTime.truncate(:second) |> IO.inspect() |> Timex.shift(minutes: 1) |> IO.inspect() ~U[2023-04-13 09:55:16.405357Z] ~U[2023-04-13 09:55:16Z] ~U[2023-04-13 09:56:16.000000Z] ``` --- CHANGELOG.md | 1 + lib/datetime/datetime.ex | 17 ++++++++++++++--- test/shift_test.exs | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22d7c3fa..24c13ea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Updated `Timex.now/1` typespec to remove the `AmbiguousDateTime` - Corrected pluralization rules for bg/cs/he/id/ro/ru +- Fixed `Timex.shift/2` to preserve the precision of the provided datetime --- diff --git a/lib/datetime/datetime.ex b/lib/datetime/datetime.ex index 25cb8e9e..8bd79ff5 100644 --- a/lib/datetime/datetime.ex +++ b/lib/datetime/datetime.ex @@ -447,13 +447,17 @@ defimpl Timex.Protocol, for: DateTime do err %DateTime{} = datetime when shift != 0 -> - DateTime.add(datetime, shift, :microsecond, Timex.Timezone.Database) + datetime + |> DateTime.add(shift, :microsecond, Timex.Timezone.Database) + |> retain_precision(datetime) %DateTime{} = datetime -> datetime - {{ty, _, _}, %DateTime{} = orig} when ty in [:gap, :ambiguous] and shift != 0 -> - DateTime.add(orig, shift, :microsecond, Timex.Timezone.Database) + {{ty, _, _}, %DateTime{} = original} when ty in [:gap, :ambiguous] and shift != 0 -> + original + |> DateTime.add(shift, :microsecond, Timex.Timezone.Database) + |> retain_precision(datetime) {{ty, _a, _b} = amb, _} when ty in [:gap, :ambiguous] -> amb @@ -480,6 +484,13 @@ defimpl Timex.Protocol, for: DateTime do err end + defp retain_precision( + %DateTime{microsecond: {ms, _precision}} = new_datetime, + %DateTime{microsecond: {_ms, precision}} = _original_datetime + ) do + %{new_datetime | microsecond: {ms, precision}} + end + defp logical_shift(datetime, []), do: datetime defp logical_shift(datetime, shifts) do diff --git a/test/shift_test.exs b/test/shift_test.exs index f82b0148..f3f36438 100644 --- a/test/shift_test.exs +++ b/test/shift_test.exs @@ -229,4 +229,44 @@ defmodule ShiftTests do expected = ~D[2017-12-31] |> Timex.to_datetime() assert expected === date end + + describe "DateTime does not change precision" do + test "seconds" do + datetime = Timex.shift(~U[2023-04-13 08:00:00Z], minutes: 1) + expected = ~U[2023-04-13 08:01:00Z] + assert expected === datetime + end + + test "milliseconds" do + datetime = Timex.shift(~U[2023-04-13 08:00:00.000Z], minutes: 1) + expected = ~U[2023-04-13 08:01:00.000Z] + assert expected === datetime + end + + test "microseconds" do + datetime = Timex.shift(~U[2023-04-13 08:00:00.000000Z], minutes: 1) + expected = ~U[2023-04-13 08:01:00.000000Z] + assert expected === datetime + end + end + + describe "NaiveDateTime does not change precision" do + test "seconds" do + datetime = Timex.shift(~N[2023-04-13 08:00:00Z], minutes: 1) + expected = ~N[2023-04-13 08:01:00Z] + assert expected === datetime + end + + test "milliseconds" do + datetime = Timex.shift(~N[2023-04-13 08:00:00.000Z], minutes: 1) + expected = ~N[2023-04-13 08:01:00.000Z] + assert expected === datetime + end + + test "microseconds" do + datetime = Timex.shift(~N[2023-04-13 08:00:00.000000Z], minutes: 1) + expected = ~N[2023-04-13 08:01:00.000000Z] + assert expected === datetime + end + end end