From 76cd40efaab4690d755ad40506a51178c9ae4ea6 Mon Sep 17 00:00:00 2001 From: youdie006 Date: Fri, 11 Sep 2026 10:55:34 +0900 Subject: [PATCH] Deprecate lenient RFC 3339 date time separators RFC 3339 allows only "T", and by convention a space, between the date and the time, but Date._rfc3339 also accepts \t, \n, \v, \f and \r. Warn on those under Warning[:deprecated] before they become an error. The separator is now captured so rfc3339_cb can inspect it, which shifts the remaining group indices. --- ext/date/date_parse.c | 30 +++++++++++++++++++++--------- test/date/test_date_parse.rb | 18 ++++++++++++++++++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/ext/date/date_parse.c b/ext/date/date_parse.c index 37748a1..fcc9e33 100644 --- a/ext/date/date_parse.c +++ b/ext/date/date_parse.c @@ -2590,7 +2590,11 @@ date__iso8601(VALUE str) } #undef SNUM -#define SNUM 8 +#define SNUM 9 + +#ifndef HAVE_RB_CATEGORY_WARN +#define rb_category_warn(category, fmt) rb_warn(fmt) +#endif static int rfc3339_cb(VALUE m, VALUE hash) @@ -2604,16 +2608,24 @@ rfc3339_cb(VALUE m, VALUE hash) s[i] = rb_reg_nth_match(i, m); } + { + int c = RSTRING_PTR(s[4])[0]; + if (c != 't' && c != 'T' && c != ' ') + rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, + "Invalid RFC3339 date time separator provided, " + "this will raise an error in a future version."); + } + set_hash("year", str2num(s[1])); set_hash("mon", str2num(s[2])); set_hash("mday", str2num(s[3])); - set_hash("hour", str2num(s[4])); - set_hash("min", str2num(s[5])); - set_hash("sec", str2num(s[6])); - set_hash("zone", s[8]); - set_hash("offset", date_zone_to_diff(s[8])); - if (!NIL_P(s[7])) - set_hash("sec_fraction", sec_fraction(s[7])); + set_hash("hour", str2num(s[5])); + set_hash("min", str2num(s[6])); + set_hash("sec", str2num(s[7])); + set_hash("zone", s[9]); + set_hash("offset", date_zone_to_diff(s[9])); + if (!NIL_P(s[8])) + set_hash("sec_fraction", sec_fraction(s[8])); return 1; } @@ -2623,7 +2635,7 @@ rfc3339(VALUE str, VALUE hash) { static const char pat_source[] = "\\A\\s*(-?\\d{4})-(\\d{2})-(\\d{2})" - "(?:t|\\s)" + "(t|\\s)" "(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?" "(z|[-+]\\d{2}:\\d{2})\\s*\\z"; static VALUE pat = Qnil; diff --git a/test/date/test_date_parse.rb b/test/date/test_date_parse.rb index 8308f25..1364a8a 100644 --- a/test/date/test_date_parse.rb +++ b/test/date/test_date_parse.rb @@ -889,6 +889,24 @@ def test__rfc3339 assert_raise(TypeError) {Date._rfc3339('2001-02-03T04:05:06Z'.to_sym)} end + def test__rfc3339__deprecated_separator + pat = /Invalid RFC3339 date time separator/ + + ["\t", "\n", "\v", "\f", "\r"].each do |sep| + h = assert_deprecated_warn(pat) do + Date._rfc3339("2001-02-03#{sep}04:05:06Z") + end + assert_equal([2001, 2, 3, 4, 5, 6, 0], + h.values_at(:year, :mon, :mday, :hour, :min, :sec, :offset)) + end + + ["T", "t", " "].each do |sep| + assert_deprecated_warn("") do + Date._rfc3339("2001-02-03#{sep}04:05:06Z") + end + end + end + def test__xmlschema h = Date._xmlschema('2001-02-03') assert_equal([2001, 2, 3, nil, nil, nil, nil],