[ruby/time] Only allow "T" or a space as the RFC 3339 separator

Time.rfc3339's pattern used [T\s], so it accepted a tab, newline,
vertical tab, form feed or carriage return between the date and the
time. Time.xmlschema's pattern has a bare T.

RFC 3339 section 5.6 defines the separator as "T" and only notes that an
application may use a space for readability. The other \s characters are
not permitted by either.

Since Ruby 3.2 the pattern also disagrees with what the code does with
the string it matched. _xmlschema hands it to Time.new, which rejects
those five characters, so the documented ArgumentError never gets raised
and Time.new's internal message escapes instead:

  Time.rfc3339("2011-10-05\t22:26:12Z")
  #=> ArgumentError: "+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z"
  #   expected for utc_offset: 22:26:12Z

Narrowing the class to [T ] makes the pattern agree with the RFC, with
Time.xmlschema, and with what _xmlschema can actually parse, so these
inputs now raise "invalid rfc3339 format:" as documented.

https://github.com/ruby/time/commit/03c5cc82d1
This commit is contained in:
youdie006 authored and git committed 2026-09-11 00:47:31 +00:00
1 parent fef20ce27c
commit 2a0323f26b
2 files changed
+24 -1

No files matched your search

+1 -1
View File
@@ -658,7 +658,7 @@ class Time
def rfc3339(time)
pattern = /\A\s*
(-?\d{4})-(\d\d)-(\d\d)
[T\s]
[T ]
(\d\d):(\d\d):(\d\d)
(\.\d+)?
(Z|[+-]\d\d:\d\d)
+23
View File
@@ -607,6 +607,29 @@ class TestTimeExtension < Test::Unit::TestCase # :nodoc:
define_method(test.sub(/xmlschema/, 'rfc3339')) {__send__(sub, :rfc3339)}
end
def test_rfc3339_separator
# RFC 3339 section 5.6 defines the separator as "T", and only notes that an
# application may use a space for readability. The other \s characters are
# not permitted, and Time.xmlschema has never accepted any of them.
t = Time.utc(2011, 10, 5, 22, 26, 12)
assert_equal(t, Time.rfc3339("2011-10-05T22:26:12Z"))
assert_equal(t, Time.rfc3339("2011-10-05 22:26:12Z"))
["\t", "\n", "\v", "\f", "\r"].each do |sep|
s = "2011-10-05#{sep}22:26:12Z"
e = assert_raise(ArgumentError, "separator #{sep.inspect}") { Time.rfc3339(s) }
assert_match(/invalid rfc3339 format/, e.message, "separator #{sep.inspect}")
end
# Time.xmlschema keeps rejecting every separator but "T".
assert_equal(t, Time.xmlschema("2011-10-05T22:26:12Z"))
["\t", "\n", "\v", "\f", "\r", " "].each do |sep|
assert_raise(ArgumentError, "separator #{sep.inspect}") do
Time.xmlschema("2011-10-05#{sep}22:26:12Z")
end
end
end
def test_parse_with_various_object
d = Date.new(2010, 10, 28)
dt = DateTime.new(2010, 10, 28)