Skip to content

Commit

Permalink
YAML/Time parsers/formatters now take in care nanoseconds (#5070)
Browse files Browse the repository at this point in the history
* YAML/Time parsers/formatters now take in care nanoseconds.

* Describe N format specifier for nanoseconds.

* fix merge conflict (@Sija)

* fix spec

* Spec for %N format specifier.
  • Loading branch information
akzhan authored and RX14 committed Oct 5, 2017
1 parent bd55733 commit bdd038d
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 12 deletions.
2 changes: 2 additions & 0 deletions spec/std/time/time_spec.cr
Expand Up @@ -313,6 +313,7 @@ describe Time do
t.to_s("%M").to_s.should eq("04")
t.to_s("%S").to_s.should eq("05")
t.to_s("%L").to_s.should eq("006")
t.to_s("%N").to_s.should eq("006000000")

Time.utc_now.to_s("%z").should eq("+0000")
Time.utc_now.to_s("%:z").should eq("+00:00")
Expand Down Expand Up @@ -399,6 +400,7 @@ describe Time do
it { Time.parse("09", "%M").minute.should eq(9) }
it { Time.parse("09", "%S").second.should eq(9) }
it { Time.parse("123", "%L").millisecond.should eq(123) }
it { Time.parse("321", "%N").nanosecond.should eq(321) }
it { Time.parse("Fri Oct 31 23:00:24 2014", "%c").to_s.should eq("2014-10-31 23:00:24") }
it { Time.parse("10/31/14", "%D").to_s.should eq("2014-10-31 00:00:00") }
it { Time.parse("10/31/69", "%D").to_s.should eq("1969-10-31 00:00:00") }
Expand Down
2 changes: 1 addition & 1 deletion spec/std/yaml/schema/core_spec.cr
Expand Up @@ -117,7 +117,7 @@ describe YAML::Schema::Core do
it_parses_scalar "2002-1-2T10:11:12.3", Time.new(2002, 1, 2, 10, 11, 12, nanosecond: 300_000_000, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12.34", Time.new(2002, 1, 2, 10, 11, 12, nanosecond: 340_000_000, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12.345", Time.new(2002, 1, 2, 10, 11, 12, nanosecond: 345_000_000, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12.3456", Time.new(2002, 1, 2, 10, 11, 12, nanosecond: 345_000_000, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12.3456", Time.new(2002, 1, 2, 10, 11, 12, nanosecond: 345_600_000, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12Z", Time.new(2002, 1, 2, 10, 11, 12, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12 Z", Time.new(2002, 1, 2, 10, 11, 12, kind: Time::Kind::Utc)
it_parses_scalar "2002-1-2T10:11:12 +3", Time.new(2002, 1, 2, 7, 11, 12, kind: Time::Kind::Utc)
Expand Down
2 changes: 1 addition & 1 deletion spec/std/yaml/serialization_spec.cr
Expand Up @@ -304,7 +304,7 @@ describe "YAML serialization" do
time.to_yaml.should eq("--- 2010-11-12\n...\n")
end

it "does for utc time with milliseconds" do
it "does for utc time with nanoseconds" do
time = Time.new(2010, 11, 12, 1, 2, 3, nanosecond: 456_000_000, kind: Time::Kind::Utc)
time.to_yaml.should eq("--- 2010-11-12 01:02:03.456\n...\n")
end
Expand Down
1 change: 1 addition & 0 deletions src/time/format.cr
Expand Up @@ -28,6 +28,7 @@
# * **%k**: hour of the day, 24-hour clock, blank padded (" 0", " 1", ..., "24")
# * **%l**: hour of the day, 12-hour clock, blank padded (" 0", " 1", ..., "12")
# * **%L**: milliseconds, zero padded (000, 001, ..., 999)
# * **%N**: nanoseconds, zero padded (000000000, 000000001, ..., 999999999)
# * **%m**: month number, zero padded (01, 02, ..., 12)
# * **%_m**: month number, blank padded (" 1", " 2", ..., "12")
# * **%-m**: month number (1, 2, ..., 12)
Expand Down
13 changes: 13 additions & 0 deletions src/time/format/formatter.cr
Expand Up @@ -113,6 +113,10 @@ struct Time::Format
pad3 time.millisecond, '0'
end

def nanoseconds
pad9 time.nanosecond, '0'
end

def am_pm
io << (time.hour < 12 ? "am" : "pm")
end
Expand Down Expand Up @@ -216,5 +220,14 @@ struct Time::Format
io.write_byte padding.ord.to_u8 if value < 1000
pad3 value, padding
end

def pad9(value, padding)
io.write_byte padding.ord.to_u8 if value < 100000000
io.write_byte padding.ord.to_u8 if value < 10000000
io.write_byte padding.ord.to_u8 if value < 1000000
io.write_byte padding.ord.to_u8 if value < 100000
io.write_byte padding.ord.to_u8 if value < 10000
pad4 value, padding
end
end
end
19 changes: 16 additions & 3 deletions src/time/format/parser.cr
Expand Up @@ -166,14 +166,27 @@ struct Time::Format
def milliseconds
# Consume more than 3 digits (12 seems a good maximum),
# and later just use the first 3 digits because Time
# only has microsecond precision.
# need millisecond precision.
pos = @reader.pos
millisecond = consume_number(12)
millisecond = consume_number_i64(12)
digits = @reader.pos - pos
if digits > 3
millisecond /= 10 ** (digits - 3)
end
@nanosecond = millisecond * Time::NANOSECONDS_PER_MILLISECOND
@nanosecond = (millisecond * Time::NANOSECONDS_PER_MILLISECOND).to_i
end

def nanoseconds
# Consume more than 9 digits (12 seems a good maximum),
# and later just use the first 9 digits because Time
# only has nanosecond precision.
pos = @reader.pos
nanosecond = consume_number(12)
digits = @reader.pos - pos
if digits > 9
nanosecond /= 10 ** (digits - 9)
end
@nanosecond = nanosecond
end

def am_pm
Expand Down
2 changes: 2 additions & 0 deletions src/time/format/pattern.cr
Expand Up @@ -49,6 +49,8 @@ struct Time::Format
hour_12_blank_padded
when 'L'
milliseconds
when 'N'
nanoseconds
when 'm'
month_zero_padded
when 'M'
Expand Down
14 changes: 7 additions & 7 deletions src/yaml/schema/core/time_parser.cr
Expand Up @@ -75,13 +75,13 @@ struct YAML::Schema::Core::TimeParser
return new_time(year, month, day, hour, minute, second)
end

millisecond = 0
nanosecond = 0

if current_char == '.'
next_char

millisecond = parse_milliseconds
return nil unless millisecond
nanosecond = parse_nanoseconds
return nil unless nanosecond
end

skip_space
Expand Down Expand Up @@ -109,22 +109,22 @@ struct YAML::Schema::Core::TimeParser

return nil if @reader.has_next?

time = new_time(year, month, day, hour, minute, second, nanosecond: millisecond * 1_000_000)
time = new_time(year, month, day, hour, minute, second, nanosecond: nanosecond)
if time && tz_offset
time = time - tz_offset.minutes
end
time
end

def parse_milliseconds
def parse_nanoseconds
return nil unless current_char.ascii_number?

multiplier = 100
multiplier = Time::NANOSECONDS_PER_SECOND / 10
number = current_char.to_i

next_char

2.times do
8.times do
break unless current_char.ascii_number?

number *= 10
Expand Down

0 comments on commit bdd038d

Please sign in to comment.