Skip to content

feat!: migrate to gleam_time for internal Time and Duration representation#54

Merged
massivefermion merged 8 commits intomassivefermion:mainfrom
tylerbutler:feat/gleam-time-internal-migration
Feb 23, 2026
Merged

feat!: migrate to gleam_time for internal Time and Duration representation#54
massivefermion merged 8 commits intomassivefermion:mainfrom
tylerbutler:feat/gleam-time-internal-migration

Conversation

@tylerbutler
Copy link
Contributor

@tylerbutler tylerbutler commented Feb 4, 2026

Summary

This is a major version bump (v2.0.0) that replaces birl's hand-rolled internal representations with types from gleam_time, reducing FFI surface area and aligning birl with the Gleam ecosystem's standard time library.

Breaking Changes

Time internal representation

  • wall_time: Int (microseconds) is now timestamp: timestamp.Timestamp
  • offset: Int (microseconds) is now offset: time_duration.Duration
  • These fields are inside an opaque type, so this only affects internal behavior and interop functions

Duration type replaced

  • Duration(Int) (a wrapper around microseconds) is now a type alias for gleam_time's time_duration.Duration (nanoseconds internally)
  • All duration constants (e.g. milli_second, second, hour) updated from microsecond to nanosecond scale
  • duration.parse treats bare numbers as nanoseconds instead of microseconds
  • duration.decompose / duration.accurate_decompose include NanoSecond in their output

TimeOfDay field renamed

  • TimeOfDay.milli_second field renamed to TimeOfDay.nanosecond with nanosecond-scale values
  • A deprecated get_milli_second helper is provided for backward compatibility

unix_epoch is now a function

  • birl.unix_epoch changed from a const to birl.unix_epoch() because Gleam constants cannot hold opaque types (Timestamp)

NanoSecond unit added

  • New duration.NanoSecond variant added to the Unit type

New APIs

Function Description
birl.to_unix_nano / birl.from_unix_nano Nanosecond-precision unix timestamps
birl.to_gleam_timestamp / birl.from_gleam_timestamp Aliases for to_timestamp / from_timestamp for naming consistency
birl.get_offset_duration Returns the offset as a time_duration.Duration directly
duration.nano_seconds Constructor for nanosecond durations

FFI Reduction

The following FFI functions were removed from both birl_ffi.erl and birl_ffi.mjs, replaced by gleam_time:

  • ffi_nowtimestamp.system_time()
  • ffi_local_offsetcalendar.local_offset()
  • ffi_to_partstimestamp.to_calendar()
  • ffi_from_partstimestamp.from_calendar()

The Erlang FFI also drops calculate_days_from_year, calculate_days_from_month, and the DaysInMonths constant. Only monotonic_now, weekday, and local_timezone remain as FFI.

Gleam_time Interop

  • duration.to_gleam_duration / duration.from_gleam_duration are deprecated no-ops since Duration is time_duration.Duration
  • time_of_day_to_calendar and calendar_to_time_of_day simplified since TimeOfDay stores nanoseconds natively
  • to_timestamp / from_timestamp simplified to direct field access

Code Quality

  • Extracted pad2 / pad3 formatting helpers, eliminating ~30 repetitive int.to_string |> string.pad_start blocks
  • Extracted validate_timezone helper to deduplicate timezone validation in now() and from_erlang_local_datetime()
  • Unified duration.new / duration.accurate_new via shared new_with_constants and duration.decompose / duration.accurate_decompose via shared decompose_with_constants
  • Simplified generate_offset from pattern-match-on-decomposed-duration to direct arithmetic
  • Simplified interval.gleam by replacing case interval { Interval(a, b) -> ... } with let Interval(a, b) = interval throughout
  • Simplified compare and difference to use timestamp.compare / timestamp.difference directly
  • Cleaned up comments to describe current behavior rather than migration history
  • Tests compile with zero deprecation warnings

CI

  • Runner changed from ubuntu-latest to ubuntu-22.04
  • OTP version bumped from 26 to 27

BREAKING CHANGES:
- Time type now uses timestamp.Timestamp and time_duration.Duration internally
- TimeOfDay.milli_second field renamed to nanosecond (now stores nanoseconds)
- unix_epoch changed from constant to function: unix_epoch()

New features:
- Added get_milli_second() deprecated helper for TimeOfDay backward compatibility
- Added NanoSecond to duration.Unit enum
- Added duration.nano_seconds() constructor
- Added gleam_time interop: to_gleam_timestamp(), from_gleam_timestamp(),
  to_unix_nano(), from_unix_nano(), get_offset_duration()
- Added duration interop: to_gleam_duration(), from_gleam_duration()

FFI changes:
- Removed now(), local_offset(), to_parts(), from_parts() (now provided by gleam_time)
- Kept monotonic_now(), weekday(), local_timezone() (not provided by gleam_time)

Version bumped to 2.0.0 for breaking changes.
@tylerbutler tylerbutler mentioned this pull request Feb 4, 2026
@tylerbutler tylerbutler marked this pull request as ready for review February 8, 2026 21:43
@massivefermion
Copy link
Owner

@tylerbutler Could you resolve the conflicts?

@massivefermion
Copy link
Owner

I just realized the Duration type is still defined here in spite of the fact that the official package already has an equivalent. Is there a reason for that?

@tylerbutler
Copy link
Contributor Author

I just realized the Duration type is still defined here in spite of the fact that the official package already has an equivalent. Is there a reason for that?

Hmmm, good catch. I think I might have been thinking about back compat or something... let me take a fresh look. I'll submit an update shortly.

@tylerbutler
Copy link
Contributor Author

Good question — I think it was just an oversight. I focused on migrating Time to use timestamp.Timestamp internally and didn't carry that through to Duration.

I think the right approach is to migrate Duration the same way — replace the internal Duration(Int) (microseconds) with time_duration.Duration (nanoseconds). That would make to_gleam_duration()/from_gleam_duration() trivial or removable.

The one question is what to do with the birl-specific utilities that don't have equivalents in gleam_time — things like parse(), blur(), decompose(), and the Unit type. My instinct is to keep those as helpers that operate on time_duration.Duration directly, but I could also see an argument for dropping the birl/duration module entirely and just keeping the utility functions elsewhere.

I'm going to go ahead and implement the functions to work on time_duration.Duration directly, but let me know if you have a different preference and I can change the approach.

Replace birl's internal Duration(Int) type with a type alias for
gleam_time's duration.Duration. All duration functions now operate on
gleam_time Duration values directly (nanosecond precision).

- Duration is now a type alias for gleam/time/duration.Duration
- All internal constants converted from microseconds to nanoseconds
- to_gleam_duration/from_gleam_duration deprecated as no-ops
- decompose now includes NanoSecond and MicroSecond units
- Updated birl.gleam add/subtract to work without unwrapping Duration
- Updated range negate_step to use time_duration.difference
…ation

- Extract pad2/pad3 formatting helpers in birl.gleam, replacing dozens of
  repeated int.to_string |> string.pad_start patterns
- Extract validate_timezone helper to deduplicate timezone validation
- Reuse existing to_nanoseconds helper in compare, scale_up, scale_down
- Consolidate new/accurate_new and decompose/accurate_decompose via shared
  parameterized helpers in duration.gleam
- Replace single-arm case destructuring with let destructuring in interval.gleam
- Simplify now_with_timezone and set_timezone using result.try
- Simplify is_invalid_date/is_invalid_time with direct boolean expressions
- Remove redundant int.absolute_value call in legible_difference
@tylerbutler tylerbutler changed the title feat!: use gleam_time internally for Time representation feat!: migrate to gleam_time for internal Time representation Feb 23, 2026
@tylerbutler tylerbutler changed the title feat!: migrate to gleam_time for internal Time representation feat!: migrate to gleam_time for internal Time and Duration representation Feb 23, 2026
@tylerbutler
Copy link
Contributor Author

@massivefermion This should be ready now. I did some code cleanup too, since a major release seems like a good time. I can split that out if you prefer.

Remove "now" / historical-change language from comments to describe
current behavior rather than migration history. Fix factual error in
get_milli_second doc comment (multiply → divide). Add test coverage
for all new public functions: get_milli_second, to_unix_nano,
from_unix_nano, get_offset_duration, to_gleam_timestamp,
from_gleam_timestamp, and duration.nano_seconds.
Remove tests that exercised deprecated no-op functions
(to_gleam_duration, from_gleam_duration, get_milli_second) and
replace duration conversion test with one that uses Duration as
gleam_time's Duration type directly.
@massivefermion
Copy link
Owner

Thanks

@massivefermion massivefermion merged commit 616968a into massivefermion:main Feb 23, 2026
1 check passed
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.

2 participants