Skip to content

Conversation

@tiagoefmoraes
Copy link

Elixir DateTime.add/4 supports a variety of units:

unit can be :day, :hour, :minute, :second or any subsecond precision from System.time_unit/0.

The :second, :millisecond, :microsecond and :nanosecond time units controls the return value of the functions that accept a time unit.

A time unit can also be a strictly positive integer. In this case, it represents the "parts per second": the time will be returned in 1 / parts_per_second seconds. For example, using the :millisecond time unit is equivalent to using 1000 as the time unit (as the time will be returned in 1/1000 seconds - milliseconds).

Currently Tz works with :day, :hour, :minute, :second, :millisecond and :microsecond, but it can't work when the unit is :nanosecond or some "strictly positive integer" as we can see in the test.exs script below:

Mix.install([:tzdata, :tz])

# Elixir UTC

datetime = ~U[2025-04-15 00:00:01.000000Z]
DateTime.add(datetime, +1, :second) |> IO.inspect(label: "Elixir +1 second")

amount = System.convert_time_unit(1, :second, :nanosecond)
DateTime.add(datetime, amount, :nanosecond) |> IO.inspect(label: "Elixir +#{amount} nanosecond")

# Tzdata

timezone = "America/Fortaleza"
datetime = DateTime.from_naive!(~N[2025-04-15 00:00:01.000000], timezone, Tzdata.TimeZoneDatabase)
DateTime.add(datetime, +1, :second, Tzdata.TimeZoneDatabase) |> IO.inspect(label: "Tzdata +1 second")

amount = System.convert_time_unit(1, :second, :nanosecond)
DateTime.add(datetime, amount, :nanosecond, Tzdata.TimeZoneDatabase) |> IO.inspect(label: "Tzdata +#{amount} nanosecond")

# Tz

timezone = "America/Fortaleza"
datetime = DateTime.from_naive!(~N[2025-04-15 00:00:01.000000], timezone, Tz.TimeZoneDatabase)
DateTime.add(datetime, +1, :second, Tz.TimeZoneDatabase) |> IO.inspect(label: "Tz +1 second")

amount = System.convert_time_unit(1, :second, :nanosecond)
DateTime.add(datetime, amount, :nanosecond, Tz.TimeZoneDatabase) |> IO.inspect(label: "Tz +#{amount} nanosecond")
Elixir +1 second: ~U[2025-04-15 00:00:02.000000Z]
Elixir +1000000000 nanosecond: ~U[2025-04-15 00:00:02.000000Z]
Tzdata +1 second: #DateTime<2025-04-15 00:00:02.000000-03:00 -03 America/Fortaleza>
Tzdata +1000000000 nanosecond: #DateTime<2025-04-15 00:00:02.000000-03:00 -03 America/Fortaleza>
Tz +1 second: #DateTime<2025-04-15 00:00:02.000000-03:00 -03 America/Fortaleza>
** (FunctionClauseError) no function clause matching in Tz.TimeZoneDatabase.iso_days_to_gregorian_seconds/1    
    
    The following arguments were given to Tz.TimeZoneDatabase.iso_days_to_gregorian_seconds/1:
    
        # 1
        {739721, {10802000000000, 86400000000000}}
    
    Attempted function clauses (showing 1 out of 1):
    
        defp iso_days_to_gregorian_seconds({days, {parts_in_day, 86_400_000_000}})
    
    (tz 0.28.1) lib/tz/time_zone_database.ex:135: Tz.TimeZoneDatabase.iso_days_to_gregorian_seconds/1
    (tz 0.28.1) lib/tz/time_zone_database.ex:14: Tz.TimeZoneDatabase.time_zone_period_from_utc_iso_days/2
    (elixir 1.18.3) lib/calendar/datetime.ex:754: DateTime.shift_zone_for_iso_days_utc/5
    (elixir 1.18.3) lib/calendar/datetime.ex:1680: DateTime.add/4
    test.exs:28: (file)

This PR adds support for these other units.

P.S. I added a commit flagging the tests as async: true and UpdaterTest with updated so we can run all other tests excluding it with mix test --exclude updater, if that's not desirable let me know and I can revert that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant