Today I Learned

Interval column in Rails 6.1 and AS' Duration#parts

Rails 6.1 introduces support for PostgreSQL interval datatype by mapping it to the ActiveSupport::Duration:

add_column :triggers, :time_offset, :interval
> Trigger.new(time_offset: 1.day + 2.hours + 3.minutes + 4.seconds)
=> #<Trigger:0x00007fbe18c9af08
=> #  id: nil,
=> #  time_offset: 1 day, 2 hours, 3 minutes, and 4 seconds
=> #>

Under the hood, it serializes duration to iso8601:

> (1.year + 2.days + 3.hours + 4.minutes + 5.seconds).iso8601
=> "P1Y2DT3H4M5S"
> ActiveSupport::Duration.parse("P1Y2DT3H4M5S")
=> 1 year, 2 days, 3 hours, 4 minutes, and 5 seconds

Today I learned, that iso8601 does not support mixing “weeks” part with any other date parts:

> 1.week.iso8601
=> "P1W"
> (1.week + 2.days).iso8601
=> "P9D"

Because of that, if you try to save a duration with weeks and other date parts, those weeks will be converted to days:

> trigger.time_offset = 1.week + 2.days + 3.hours
=> 1 week, 2 days, and 3 hours
> trigger.time_offset.parts
=> {:weeks=>1, :days=>2, :hours=>3}
> trigger.save
=> true
> trigger.time_offset.parts
=> {:days=>9, :hours=>3}

While saving just weeks, or any duration without weeks, will give you consistent results:

> trigger.time_offset = 5.weeks
=> 5 weeks
> trigger.save
=> true
> trigger.time_offset
=> 5 weeks
> trigger.time_offset = 500.days
=> 500 days
> trigger.save
=> true
> trigger.time_offset
=> 500 days