From f542c1e801fa14164515337e1a66c7f426bcc849 Mon Sep 17 00:00:00 2001 From: Simon Waddington Date: Mon, 27 Feb 2017 17:38:54 -0800 Subject: [PATCH] Add fractional second support to timestamps RFC-3339 supports fractional seconds as does the ISO-8601 but the regexp being used for validation of timestamp strings did not support it. Technically the [RFC-3339 specification](https://www.ietf.org/rfc/rfc3339.txt) allows use of a comma `,` as well as decimal point `.` for the whole and fractional seconds separator: > time-fraction = ("," / ".") 1*DIGIT Although use of `,` is common in European countries, in the interests of eliminating equivalent but lexically different encodings which would have a different cryptographic hash, this change only allows the decimal point as separator. Thus `2016-10-02T07:31:51.42Z` is a valid timestamp, but `2016-10-02T07:31:51,42Z` is not. --- lib/tjson/datatype/timestamp.rb | 4 +++- spec/tjson/datatype/timestamp_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/tjson/datatype/timestamp.rb b/lib/tjson/datatype/timestamp.rb index aa704f4..55de468 100644 --- a/lib/tjson/datatype/timestamp.rb +++ b/lib/tjson/datatype/timestamp.rb @@ -10,7 +10,9 @@ def tag def convert(str) raise TJSON::TypeError, "expected String, got #{str.class}: #{str.inspect}" unless str.is_a?(::String) - raise TJSON::ParseError, "invalid timestamp: #{str.inspect}" unless str =~ /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z\z/ + unless str =~ /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z\z/ + raise TJSON::ParseError, "invalid timestamp: #{str.inspect}" + end ::Time.iso8601(str) end diff --git a/spec/tjson/datatype/timestamp_spec.rb b/spec/tjson/datatype/timestamp_spec.rb index 0f6d796..dd440d2 100644 --- a/spec/tjson/datatype/timestamp_spec.rb +++ b/spec/tjson/datatype/timestamp_spec.rb @@ -16,4 +16,20 @@ expect { subject.convert(invalid_timestamp) }.to raise_error(TJSON::ParseError) end end + + context "valid UTC RFC3339 timestamp with fractional seconds" do + let(:example_timestamp) { "2016-10-02T07:31:51.42Z" } + + it "parses successfully" do + expect(subject.convert(example_timestamp)).to be_a Time + end + end + + context "valid UTC RFC3339 timestamp with comma separated fractional seconds" do + let(:invalid_timestamp) { "2016-10-02T07:31:51,42Z" } + + it "raises TJSON::ParseError" do + expect { subject.convert(invalid_timestamp) }.to raise_error(TJSON::ParseError) + end + end end