Bug #22375
open`Time.new(String)` can disagree with itself about the date
Description
Time.new("2026-02-30T00:00:00Z") returns a Time object that is internally inconsistent.
It shows as Feb 30 or Mar 2 depending on what methods are called:
t = Time.new("2026-02-30T00:00:00Z") # => 2026-02-30 00:00:00 UTC
t.mon # => 2
t.day # => 30 (February 30 is invalid)
t.to_i # => 1772409600
Time.utc(2026, 3, 2).to_i # => 1772409600 # (Mar 2 gives same #to_i result)
t == Time.utc(2026, 3, 2) # => true
t + 0 # => 2026-03-02 00:00:00 UTC # plus zero normalizes
t # => 2026-02-30 00:00:00 UTC # still invalid
t.to_a # => [0, 0, 0, 2, 3, 2026, 1, 61, false, "UTC"] # to_a shows Mar 2
t # => 2026-03-02 00:00:00 UTC # the to_a call appears to have normalized the object
The numeric constructor normalizes Time.new(2026, 2, 30) to March 2.
A string with an offset other than Z (+02:00) also normalizes:
This has been the behavior since at least Ruby 3.3.0.
However, since commit 77d0d22b
(2026-06-22), Time.iso8601, Time.rfc3339, and Time.xmlschema all parse by calling
Time.new(string) instead of building from Time.utc(y, m, d, ...). So they now inherit
this behavior:
require "time"
Time.iso8601("2026-02-30T00:00:00.000000000Z").iso8601(9) # => "2026-02-30T00:00:00.000000000Z"
Currently the test suite has a test that raises ArgumentError for an invalid month
but we don't appear to have tests for invalid days.
I think we should add a test for this case and fix Time.new(string) to normalize consistently.
No data to display