Skip to content

Commit 5a583c7

Browse files
committed
Consistently merge precisions in Calendar subsecond operations, closes #12303
1 parent ec31cc2 commit 5a583c7

File tree

4 files changed

+36
-12
lines changed

4 files changed

+36
-12
lines changed

lib/elixir/lib/calendar/datetime.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1493,6 +1493,13 @@ defmodule DateTime do
14931493
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
14941494
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
14951495
1496+
This operation merges the precision of the naive date time with the given unit:
1497+
1498+
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
1499+
~U[2014-10-02 00:29:10.021Z]
1500+
iex> result.microsecond
1501+
{21000, 3}
1502+
14961503
"""
14971504
@doc since: "1.8.0"
14981505
@spec add(
@@ -1537,6 +1544,7 @@ defmodule DateTime do
15371544

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

15411549
result =
15421550
datetime

lib/elixir/lib/calendar/iso.ex

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,17 @@ defmodule Calendar.ISO do
247247
defguardp is_utc_offset(offset) when is_integer(offset)
248248
defguardp is_std_offset(offset) when is_integer(offset)
249249

250+
@doc """
251+
Converts a `t:System.time_unit/0` to precision.
252+
253+
Integer-based time units always get maximum precision.
254+
"""
255+
def time_unit_to_precision(:nanosecond), do: 6
256+
def time_unit_to_precision(:microsecond), do: 6
257+
def time_unit_to_precision(:millisecond), do: 3
258+
def time_unit_to_precision(:second), do: 0
259+
def time_unit_to_precision(_), do: 6
260+
250261
@doc """
251262
Parses a time `string` in the `:extended` format.
252263

lib/elixir/lib/calendar/naive_datetime.ex

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ defmodule NaiveDateTime do
379379
It can also work with subsecond precisions:
380380
381381
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
382-
~N[2014-10-02 00:29:12]
382+
~N[2014-10-02 00:29:12.000]
383383
384384
As well as days/hours/minutes:
385385
@@ -390,16 +390,12 @@ defmodule NaiveDateTime do
390390
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
391391
~N[2015-02-28 01:29:10]
392392
393-
This operation keeps the precision of the naive date time:
394-
395-
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
396-
~N[2014-10-02 00:29:31.021]
397-
398-
And ignores any changes below the precision:
393+
This operation merges the precision of the naive date time with the given unit:
399394
400-
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
401-
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
402-
{21000, 0}
395+
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
396+
~N[2014-10-02 00:29:10.021]
397+
iex> result.microsecond
398+
{21000, 3}
403399
404400
Operations on top of gregorian seconds or the Unix epoch are optimized:
405401
@@ -440,6 +436,7 @@ defmodule NaiveDateTime do
440436
)
441437
when is_integer(amount_to_add) do
442438
ppd = System.convert_time_unit(86400, :second, unit)
439+
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
443440

444441
naive_datetime
445442
|> to_iso_days()

lib/elixir/lib/calendar/time.ex

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,13 @@ defmodule Time do
493493
iex> Time.add(~T[17:10:05], 30, :minute)
494494
~T[17:40:05]
495495
496+
This operation merges the precision of the time with the given unit:
497+
498+
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
499+
~T[00:29:10.021]
500+
iex> result.microsecond
501+
{21000, 3}
502+
496503
"""
497504
@doc since: "1.6.0"
498505
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
@@ -511,6 +518,7 @@ defmodule Time do
511518
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
512519
total = time_to_microseconds(time) + amount_to_add
513520
parts = Integer.mod(total, @parts_per_day)
521+
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
514522

515523
{hour, minute, second, {microsecond, _}} =
516524
calendar.time_from_day_fraction({parts, @parts_per_day})
@@ -736,14 +744,14 @@ defmodule Time do
736744
hour: hour1,
737745
minute: minute1,
738746
second: second1,
739-
microsecond: {microsecond1, @parts_per_day}
747+
microsecond: {microsecond1, _}
740748
},
741749
%{
742750
calendar: Calendar.ISO,
743751
hour: hour2,
744752
minute: minute2,
745753
second: second2,
746-
microsecond: {microsecond2, @parts_per_day}
754+
microsecond: {microsecond2, _}
747755
},
748756
unit
749757
) do

0 commit comments

Comments
 (0)