From 9a830a5e90b3ebb0f0d3934a34ce97b87137f2d5 Mon Sep 17 00:00:00 2001 From: Oliver Kriska Date: Wed, 2 Jun 2021 21:58:15 +0200 Subject: [PATCH 1/3] Set correct day when year or month is changed too - when you call `Timex.set/2` function from date which has less days than new month it should apply month or year changes before day changes so validation will be correct. So options have to be in exact order: year, month, day - added alias for Helpers module --- lib/datetime/datetime.ex | 27 +++++++++++++++------------ lib/datetime/erlang.ex | 9 ++++++--- lib/datetime/helpers.ex | 24 ++++++++++++++++++++++++ lib/datetime/naivedatetime.ex | 13 ++++++++----- test/set_test.exs | 17 +++++++++++++++++ 5 files changed, 70 insertions(+), 20 deletions(-) diff --git a/lib/datetime/datetime.ex b/lib/datetime/datetime.ex index a4201efc..50ba3167 100644 --- a/lib/datetime/datetime.ex +++ b/lib/datetime/datetime.ex @@ -13,6 +13,7 @@ defimpl Timex.Protocol, for: DateTime do alias Timex.{Duration, AmbiguousDateTime} alias Timex.{Timezone, TimezoneInfo} + alias Timex.DateTime.Helpers def to_julian(%DateTime{:year => y, :month => m, :day => d}) do Timex.Calendar.Julian.julian_date(y, m, d) @@ -41,7 +42,7 @@ defimpl Timex.Protocol, for: DateTime do end def to_naive_datetime(%DateTime{} = d) do - # NOTE: For legacy reasons we shift DateTimes to UTC when making them naive, + # NOTE: For legacy reasons we shift DateTimes to UTC when making them naive, # but the standard library just drops the timezone info d |> Timex.DateTime.shift_zone!("Etc/UTC", Timex.Timezone.Database) @@ -57,7 +58,7 @@ defimpl Timex.Protocol, for: DateTime do def is_leap?(%DateTime{year: year}), do: :calendar.is_leap_year(year) def beginning_of_day(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -80,7 +81,7 @@ defimpl Timex.Protocol, for: DateTime do end def end_of_day(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- @@ -106,7 +107,7 @@ defimpl Timex.Protocol, for: DateTime do %DateTime{time_zone: time_zone, microsecond: {_, precision}} = date, weekstart ) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with weekstart when is_atom(weekstart) <- Timex.standardize_week_start(weekstart), @@ -129,7 +130,7 @@ defimpl Timex.Protocol, for: DateTime do def end_of_week(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = date, weekstart) do with weekstart when is_atom(weekstart) <- Timex.standardize_week_start(weekstart), date = Timex.Date.end_of_week(DateTime.to_date(date), weekstart), - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision), + us = Helpers.construct_microseconds(999_999, precision), time = Timex.Time.new!(23, 59, 59, us), {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do datetime @@ -147,7 +148,7 @@ defimpl Timex.Protocol, for: DateTime do end def beginning_of_year(%DateTime{year: year, time_zone: time_zone, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -169,7 +170,7 @@ defimpl Timex.Protocol, for: DateTime do end def end_of_year(%DateTime{year: year, time_zone: time_zone, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- @@ -197,7 +198,7 @@ defimpl Timex.Protocol, for: DateTime do microsecond: {_, precision} }) do month = 1 + 3 * (Timex.quarter(month) - 1) - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -226,7 +227,7 @@ defimpl Timex.Protocol, for: DateTime do }) do month = 3 * Timex.quarter(month) date = Timex.Date.end_of_month(Timex.Date.new!(year, month, 1)) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do @@ -247,7 +248,7 @@ defimpl Timex.Protocol, for: DateTime do time_zone: time_zone, microsecond: {_, precision} }) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -275,7 +276,7 @@ defimpl Timex.Protocol, for: DateTime do microsecond: {_, precision} }) do date = Timex.Date.end_of_month(Timex.Date.new!(year, month, 1)) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do @@ -325,7 +326,9 @@ defimpl Timex.Protocol, for: DateTime do def set(%DateTime{} = date, options) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/lib/datetime/erlang.ex b/lib/datetime/erlang.ex index cf915270..41741538 100644 --- a/lib/datetime/erlang.ex +++ b/lib/datetime/erlang.ex @@ -1,5 +1,6 @@ defimpl Timex.Protocol, for: Tuple do alias Timex.AmbiguousDateTime + alias Timex.DateTime.Helpers import Timex.Macros @epoch :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) @@ -40,7 +41,7 @@ defimpl Timex.Protocol, for: Tuple do end def to_datetime({{y, m, d}, {h, mm, s, us}}, timezone) when is_datetime(y, m, d, h, mm, s) do - us = Timex.DateTime.Helpers.construct_microseconds(us) + us = Helpers.construct_microseconds(us) dt = Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us) with %DateTime{} = datetime <- Timex.Timezone.convert(dt, timezone) do @@ -57,7 +58,7 @@ defimpl Timex.Protocol, for: Tuple do def to_datetime(_, _), do: {:error, :invalid_date} def to_naive_datetime({{y, m, d}, {h, mm, s, us}}) when is_datetime(y, m, d, h, mm, s) do - us = Timex.DateTime.Helpers.construct_microseconds(us) + us = Helpers.construct_microseconds(us) Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us) end @@ -285,7 +286,9 @@ defimpl Timex.Protocol, for: Tuple do defp do_set(date, options, datetime_type) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/lib/datetime/helpers.ex b/lib/datetime/helpers.ex index b85c804d..2cbdb5e1 100644 --- a/lib/datetime/helpers.ex +++ b/lib/datetime/helpers.ex @@ -145,4 +145,28 @@ defmodule Timex.DateTime.Helpers do new_p end end + + def sort_options(options) when is_list(options) do + options = + case Keyword.pop(options, :day) do + {nil, options} -> options + {day, opts} -> Keyword.put(opts, :day, day) + end + + options = + case Keyword.pop(options, :month) do + {nil, options} -> options + {month, opts} -> Keyword.put(opts, :month, month) + end + + options = + case Keyword.pop(options, :year) do + {nil, options} -> options + {year, opts} -> Keyword.put(opts, :year, year) + end + + options + end + + def sort_options(options), do: options end diff --git a/lib/datetime/naivedatetime.ex b/lib/datetime/naivedatetime.ex index 00735afc..1ba5e0b2 100644 --- a/lib/datetime/naivedatetime.ex +++ b/lib/datetime/naivedatetime.ex @@ -3,6 +3,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do This module implements Timex functionality for NaiveDateTime """ alias Timex.AmbiguousDateTime + alias Timex.DateTime.Helpers import Timex.Macros @epoch_seconds :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) @@ -56,7 +57,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do end def end_of_day(%NaiveDateTime{microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) %{datetime | :hour => 23, :minute => 59, :second => 59, :microsecond => us} end @@ -70,7 +71,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do def end_of_week(%NaiveDateTime{microsecond: {_, precision}} = date, weekstart) do with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do date = Timex.Date.end_of_week(date, ws) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(date.year, date.month, date.day, 23, 59, 59, us) end end @@ -80,7 +81,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do end def end_of_year(%NaiveDateTime{year: year, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(year, 12, 31, 23, 59, 59, us) end @@ -99,7 +100,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do def end_of_month(%NaiveDateTime{year: year, month: month, microsecond: {_, precision}} = date) do day = days_in_month(date) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(year, month, day, 23, 59, 59, us) end @@ -141,7 +142,9 @@ defimpl Timex.Protocol, for: NaiveDateTime do def set(%NaiveDateTime{} = date, options) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/test/set_test.exs b/test/set_test.exs index 1377377f..44e6188b 100644 --- a/test/set_test.exs +++ b/test/set_test.exs @@ -125,4 +125,21 @@ defmodule SetTests do assert new_date.minute == 0 assert new_date.second == 0 end + + test "set day 31 and another month from date with month with only 30 days" do + original_date = Timex.to_datetime({{2021, 4, 1}, {12, 0, 0}}) + new_date = Timex.set(original_date, day: 31, month: 5) + + assert new_date.month == 5 + assert new_date.day == 31 + end + + test "set day 29 for February from year without it" do + original_date = Timex.to_datetime({{2023, 2, 28}, {12, 0, 0}}) + new_date = Timex.set(original_date, day: 29, month: 2, year: 2024) + + assert new_date.month == 2 + assert new_date.day == 29 + assert new_date.year == 2024 + end end From 4c01a71ba6d158c9e34b337a97aefa90c38a09f6 Mon Sep 17 00:00:00 2001 From: Oliver Kriska Date: Wed, 2 Jun 2021 21:58:15 +0200 Subject: [PATCH 2/3] Set correct day when year or month is changed too - when you call `Timex.set/2` function from date which has less days than new month it should apply month or year changes before day changes so validation will be correct. So options have to be in exact order: year, month, day - added alias for Helpers module --- lib/datetime/datetime.ex | 27 +++++++++++++++------------ lib/datetime/erlang.ex | 9 ++++++--- lib/datetime/helpers.ex | 21 +++++++++++++++++++++ lib/datetime/naivedatetime.ex | 13 ++++++++----- test/set_test.exs | 17 +++++++++++++++++ 5 files changed, 67 insertions(+), 20 deletions(-) diff --git a/lib/datetime/datetime.ex b/lib/datetime/datetime.ex index a4201efc..50ba3167 100644 --- a/lib/datetime/datetime.ex +++ b/lib/datetime/datetime.ex @@ -13,6 +13,7 @@ defimpl Timex.Protocol, for: DateTime do alias Timex.{Duration, AmbiguousDateTime} alias Timex.{Timezone, TimezoneInfo} + alias Timex.DateTime.Helpers def to_julian(%DateTime{:year => y, :month => m, :day => d}) do Timex.Calendar.Julian.julian_date(y, m, d) @@ -41,7 +42,7 @@ defimpl Timex.Protocol, for: DateTime do end def to_naive_datetime(%DateTime{} = d) do - # NOTE: For legacy reasons we shift DateTimes to UTC when making them naive, + # NOTE: For legacy reasons we shift DateTimes to UTC when making them naive, # but the standard library just drops the timezone info d |> Timex.DateTime.shift_zone!("Etc/UTC", Timex.Timezone.Database) @@ -57,7 +58,7 @@ defimpl Timex.Protocol, for: DateTime do def is_leap?(%DateTime{year: year}), do: :calendar.is_leap_year(year) def beginning_of_day(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -80,7 +81,7 @@ defimpl Timex.Protocol, for: DateTime do end def end_of_day(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- @@ -106,7 +107,7 @@ defimpl Timex.Protocol, for: DateTime do %DateTime{time_zone: time_zone, microsecond: {_, precision}} = date, weekstart ) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with weekstart when is_atom(weekstart) <- Timex.standardize_week_start(weekstart), @@ -129,7 +130,7 @@ defimpl Timex.Protocol, for: DateTime do def end_of_week(%DateTime{time_zone: time_zone, microsecond: {_, precision}} = date, weekstart) do with weekstart when is_atom(weekstart) <- Timex.standardize_week_start(weekstart), date = Timex.Date.end_of_week(DateTime.to_date(date), weekstart), - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision), + us = Helpers.construct_microseconds(999_999, precision), time = Timex.Time.new!(23, 59, 59, us), {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do datetime @@ -147,7 +148,7 @@ defimpl Timex.Protocol, for: DateTime do end def beginning_of_year(%DateTime{year: year, time_zone: time_zone, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -169,7 +170,7 @@ defimpl Timex.Protocol, for: DateTime do end def end_of_year(%DateTime{year: year, time_zone: time_zone, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- @@ -197,7 +198,7 @@ defimpl Timex.Protocol, for: DateTime do microsecond: {_, precision} }) do month = 1 + 3 * (Timex.quarter(month) - 1) - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -226,7 +227,7 @@ defimpl Timex.Protocol, for: DateTime do }) do month = 3 * Timex.quarter(month) date = Timex.Date.end_of_month(Timex.Date.new!(year, month, 1)) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do @@ -247,7 +248,7 @@ defimpl Timex.Protocol, for: DateTime do time_zone: time_zone, microsecond: {_, precision} }) do - us = Timex.DateTime.Helpers.construct_microseconds(0, precision) + us = Helpers.construct_microseconds(0, precision) time = Timex.Time.new!(0, 0, 0, us) with {:ok, datetime} <- @@ -275,7 +276,7 @@ defimpl Timex.Protocol, for: DateTime do microsecond: {_, precision} }) do date = Timex.Date.end_of_month(Timex.Date.new!(year, month, 1)) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) time = Timex.Time.new!(23, 59, 59, us) with {:ok, datetime} <- Timex.DateTime.new(date, time, time_zone, Timex.Timezone.Database) do @@ -325,7 +326,9 @@ defimpl Timex.Protocol, for: DateTime do def set(%DateTime{} = date, options) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/lib/datetime/erlang.ex b/lib/datetime/erlang.ex index cf915270..41741538 100644 --- a/lib/datetime/erlang.ex +++ b/lib/datetime/erlang.ex @@ -1,5 +1,6 @@ defimpl Timex.Protocol, for: Tuple do alias Timex.AmbiguousDateTime + alias Timex.DateTime.Helpers import Timex.Macros @epoch :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) @@ -40,7 +41,7 @@ defimpl Timex.Protocol, for: Tuple do end def to_datetime({{y, m, d}, {h, mm, s, us}}, timezone) when is_datetime(y, m, d, h, mm, s) do - us = Timex.DateTime.Helpers.construct_microseconds(us) + us = Helpers.construct_microseconds(us) dt = Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us) with %DateTime{} = datetime <- Timex.Timezone.convert(dt, timezone) do @@ -57,7 +58,7 @@ defimpl Timex.Protocol, for: Tuple do def to_datetime(_, _), do: {:error, :invalid_date} def to_naive_datetime({{y, m, d}, {h, mm, s, us}}) when is_datetime(y, m, d, h, mm, s) do - us = Timex.DateTime.Helpers.construct_microseconds(us) + us = Helpers.construct_microseconds(us) Timex.NaiveDateTime.new!(y, m, d, h, mm, s, us) end @@ -285,7 +286,9 @@ defimpl Timex.Protocol, for: Tuple do defp do_set(date, options, datetime_type) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/lib/datetime/helpers.ex b/lib/datetime/helpers.ex index b85c804d..f9096234 100644 --- a/lib/datetime/helpers.ex +++ b/lib/datetime/helpers.ex @@ -145,4 +145,25 @@ defmodule Timex.DateTime.Helpers do new_p end end + + def sort_options(options) when is_list(options) do + options + |> Keyword.pop(:day) + |> case do + {nil, options} -> options + {day, opts} -> Keyword.put(opts, :day, day) + end + |> Keyword.pop(:month) + |> case do + {nil, options} -> options + {month, opts} -> Keyword.put(opts, :month, month) + end + |> Keyword.pop(:year) + |> case do + {nil, options} -> options + {year, opts} -> Keyword.put(opts, :year, year) + end + end + + def sort_options(options), do: options end diff --git a/lib/datetime/naivedatetime.ex b/lib/datetime/naivedatetime.ex index 00735afc..1ba5e0b2 100644 --- a/lib/datetime/naivedatetime.ex +++ b/lib/datetime/naivedatetime.ex @@ -3,6 +3,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do This module implements Timex functionality for NaiveDateTime """ alias Timex.AmbiguousDateTime + alias Timex.DateTime.Helpers import Timex.Macros @epoch_seconds :calendar.datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}) @@ -56,7 +57,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do end def end_of_day(%NaiveDateTime{microsecond: {_, precision}} = datetime) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) %{datetime | :hour => 23, :minute => 59, :second => 59, :microsecond => us} end @@ -70,7 +71,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do def end_of_week(%NaiveDateTime{microsecond: {_, precision}} = date, weekstart) do with ws when is_atom(ws) <- Timex.standardize_week_start(weekstart) do date = Timex.Date.end_of_week(date, ws) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(date.year, date.month, date.day, 23, 59, 59, us) end end @@ -80,7 +81,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do end def end_of_year(%NaiveDateTime{year: year, microsecond: {_, precision}}) do - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(year, 12, 31, 23, 59, 59, us) end @@ -99,7 +100,7 @@ defimpl Timex.Protocol, for: NaiveDateTime do def end_of_month(%NaiveDateTime{year: year, month: month, microsecond: {_, precision}} = date) do day = days_in_month(date) - us = Timex.DateTime.Helpers.construct_microseconds(999_999, precision) + us = Helpers.construct_microseconds(999_999, precision) Timex.NaiveDateTime.new!(year, month, day, 23, 59, 59, us) end @@ -141,7 +142,9 @@ defimpl Timex.Protocol, for: NaiveDateTime do def set(%NaiveDateTime{} = date, options) do validate? = Keyword.get(options, :validate, true) - Enum.reduce(options, date, fn + options + |> Helpers.sort_options() + |> Enum.reduce(date, fn _option, {:error, _} = err -> err diff --git a/test/set_test.exs b/test/set_test.exs index 1377377f..44e6188b 100644 --- a/test/set_test.exs +++ b/test/set_test.exs @@ -125,4 +125,21 @@ defmodule SetTests do assert new_date.minute == 0 assert new_date.second == 0 end + + test "set day 31 and another month from date with month with only 30 days" do + original_date = Timex.to_datetime({{2021, 4, 1}, {12, 0, 0}}) + new_date = Timex.set(original_date, day: 31, month: 5) + + assert new_date.month == 5 + assert new_date.day == 31 + end + + test "set day 29 for February from year without it" do + original_date = Timex.to_datetime({{2023, 2, 28}, {12, 0, 0}}) + new_date = Timex.set(original_date, day: 29, month: 2, year: 2024) + + assert new_date.month == 2 + assert new_date.day == 29 + assert new_date.year == 2024 + end end From 59faee05047673ea68e24125579b6503b7cc2d96 Mon Sep 17 00:00:00 2001 From: oliver-kriska Date: Tue, 3 Aug 2021 10:14:02 +0200 Subject: [PATCH 3/3] Sort options by specific hardcoded order --- lib/datetime/helpers.ex | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/datetime/helpers.ex b/lib/datetime/helpers.ex index f9096234..10268d48 100644 --- a/lib/datetime/helpers.ex +++ b/lib/datetime/helpers.ex @@ -4,6 +4,14 @@ defmodule Timex.DateTime.Helpers do alias Timex.{Types, Timezone, TimezoneInfo, AmbiguousDateTime, AmbiguousTimezoneInfo} @type precision :: -1 | 0..6 + @set_option_priority %{ + year: 1, + month: 2, + day: 3, + hour: 4, + minute: 5, + second: 6 + } @doc """ Constructs an empty NaiveDateTime, for internal use only @@ -147,22 +155,7 @@ defmodule Timex.DateTime.Helpers do end def sort_options(options) when is_list(options) do - options - |> Keyword.pop(:day) - |> case do - {nil, options} -> options - {day, opts} -> Keyword.put(opts, :day, day) - end - |> Keyword.pop(:month) - |> case do - {nil, options} -> options - {month, opts} -> Keyword.put(opts, :month, month) - end - |> Keyword.pop(:year) - |> case do - {nil, options} -> options - {year, opts} -> Keyword.put(opts, :year, year) - end + Enum.sort_by(options, fn {k, _} -> Map.get(@set_option_priority, k, 99) end) end def sort_options(options), do: options