Skip to content

Commit

Permalink
Consistently merge precisions in Calendar subsecond operations, closes
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Dec 18, 2022
1 parent ec31cc2 commit 5a583c7
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 12 deletions.
8 changes: 8 additions & 0 deletions lib/elixir/lib/calendar/datetime.ex
Expand Up @@ -1493,6 +1493,13 @@ defmodule DateTime do
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
This operation merges the precision of the naive date time with the given unit:
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
~U[2014-10-02 00:29:10.021Z]
iex> result.microsecond
{21000, 3}
"""
@doc since: "1.8.0"
@spec add(
Expand Down Expand Up @@ -1537,6 +1544,7 @@ defmodule DateTime do

ppd = System.convert_time_unit(86400, :second, unit)
total_offset = System.convert_time_unit(utc_offset + std_offset, :second, unit)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)

result =
datetime
Expand Down
11 changes: 11 additions & 0 deletions lib/elixir/lib/calendar/iso.ex
Expand Up @@ -247,6 +247,17 @@ defmodule Calendar.ISO do
defguardp is_utc_offset(offset) when is_integer(offset)
defguardp is_std_offset(offset) when is_integer(offset)

@doc """
Converts a `t:System.time_unit/0` to precision.
Integer-based time units always get maximum precision.
"""
def time_unit_to_precision(:nanosecond), do: 6
def time_unit_to_precision(:microsecond), do: 6
def time_unit_to_precision(:millisecond), do: 3
def time_unit_to_precision(:second), do: 0
def time_unit_to_precision(_), do: 6

@doc """
Parses a time `string` in the `:extended` format.
Expand Down
17 changes: 7 additions & 10 deletions lib/elixir/lib/calendar/naive_datetime.ex
Expand Up @@ -379,7 +379,7 @@ defmodule NaiveDateTime do
It can also work with subsecond precisions:
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
~N[2014-10-02 00:29:12]
~N[2014-10-02 00:29:12.000]
As well as days/hours/minutes:
Expand All @@ -390,16 +390,12 @@ defmodule NaiveDateTime do
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
~N[2015-02-28 01:29:10]
This operation keeps the precision of the naive date time:
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
~N[2014-10-02 00:29:31.021]
And ignores any changes below the precision:
This operation merges the precision of the naive date time with the given unit:
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
{21000, 0}
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
~N[2014-10-02 00:29:10.021]
iex> result.microsecond
{21000, 3}
Operations on top of gregorian seconds or the Unix epoch are optimized:
Expand Down Expand Up @@ -440,6 +436,7 @@ defmodule NaiveDateTime do
)
when is_integer(amount_to_add) do
ppd = System.convert_time_unit(86400, :second, unit)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)

naive_datetime
|> to_iso_days()
Expand Down
12 changes: 10 additions & 2 deletions lib/elixir/lib/calendar/time.ex
Expand Up @@ -493,6 +493,13 @@ defmodule Time do
iex> Time.add(~T[17:10:05], 30, :minute)
~T[17:40:05]
This operation merges the precision of the time with the given unit:
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
~T[00:29:10.021]
iex> result.microsecond
{21000, 3}
"""
@doc since: "1.6.0"
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
Expand All @@ -511,6 +518,7 @@ defmodule Time do
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
total = time_to_microseconds(time) + amount_to_add
parts = Integer.mod(total, @parts_per_day)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)

{hour, minute, second, {microsecond, _}} =
calendar.time_from_day_fraction({parts, @parts_per_day})
Expand Down Expand Up @@ -736,14 +744,14 @@ defmodule Time do
hour: hour1,
minute: minute1,
second: second1,
microsecond: {microsecond1, @parts_per_day}
microsecond: {microsecond1, _}
},
%{
calendar: Calendar.ISO,
hour: hour2,
minute: minute2,
second: second2,
microsecond: {microsecond2, @parts_per_day}
microsecond: {microsecond2, _}
},
unit
) do
Expand Down

0 comments on commit 5a583c7

Please sign in to comment.