Skip to content

Commit

Permalink
Improve #inspect format for Time types (#5794)
Browse files Browse the repository at this point in the history
* Improve #inspect format for Time::Location types

* Add Time::Zone#format and use it in Time::Formatter

* Improve format of Time#inspect

`#inspect` now prints `nanoseconds` unless it is `0` and uses `Time::Zone#format`.
Location name is no longer omitted if `"Local"`, otherwise there would be no distinction between a fixed offset time zone and local time zone because both would just show the offset and no name.
`#to_s` still omits nanoseconds and time zone name.
  • Loading branch information
straight-shoota authored and RX14 committed Apr 26, 2018
1 parent fdda990 commit 3afc781
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 105 deletions.
35 changes: 30 additions & 5 deletions spec/std/time/location_spec.cr
Expand Up @@ -176,8 +176,19 @@ class Time::Location
end

describe ".fixed" do
it "accepts a name" do
location = Location.fixed("Fixed", 1800)
it "without name" do
location = Location.fixed -9012
location.name.should eq "-02:30:12"
location.zones.should eq [Zone.new(nil, -9012, false)]
location.transitions.size.should eq 0

location.utc?.should be_false
location.fixed?.should be_true
location.local?.should be_false
end

it "with name" do
location = Location.fixed "Fixed", 1800
location.name.should eq "Fixed"
location.zones.should eq [Zone.new("Fixed", 1800, false)]
location.transitions.size.should eq 0
Expand All @@ -189,13 +200,13 @@ class Time::Location

it "positive" do
location = Location.fixed 8000
location.name.should eq "+02:13"
location.name.should eq "+02:13:20"
location.zones.first.offset.should eq 8000
end

it "ngeative" do
it "negative" do
location = Location.fixed -7539
location.name.should eq "-02:05"
location.name.should eq "-02:05:39"
location.zones.first.offset.should eq -7539
end

Expand Down Expand Up @@ -307,4 +318,18 @@ class Time::Location
end
end
end

describe Time::Location::Zone do
it "#inspect" do
Time::Location::Zone.new("CET", 3600, false).inspect.should eq "Time::Location::Zone(CET +01:00 (3600s) STD)"
Time::Location::Zone.new("CEST", 7200, true).inspect.should eq "Time::Location::Zone(CEST +02:00 (7200s) DST)"
Time::Location::Zone.new(nil, 9000, true).inspect.should eq "Time::Location::Zone(+02:30 (9000s) DST)"
Time::Location::Zone.new(nil, 9012, true).inspect.should eq "Time::Location::Zone(+02:30:12 (9012s) DST)"
end

it "#name" do
Time::Location::Zone.new("CEST", 7200, true).name.should eq "CEST"
Time::Location::Zone.new(nil, 9000, true).name.should eq "+02:30"
end
end
end
85 changes: 47 additions & 38 deletions spec/std/time/time_spec.cr
Expand Up @@ -335,56 +335,65 @@ describe Time do
(Time.now - Time.now(Time::Location.fixed(1234))).should be_close(0.seconds, 1.second)
end

describe "to_s" do
it "prints local time" do
with_env("TZ", nil) do
t = Time.new 2014, 10, 30, 21, 18, 13
t.to_s.should eq("2014-10-30 21:18:13 #{t.to_s("%:z")}")

t = Time.new 2014, 1, 30, 21, 18, 13
t.to_s.should eq("2014-01-30 21:18:13 #{t.to_s("%:z")}")

t = Time.new 2014, 10, 1, 21, 18, 13
t.to_s.should eq("2014-10-01 21:18:13 #{t.to_s("%:z")}")

t = Time.new 2014, 10, 30, 1, 18, 13
t.to_s.should eq("2014-10-30 01:18:13 #{t.to_s("%:z")}")

t = Time.new 2014, 10, 30, 21, 1, 13
t.to_s.should eq("2014-10-30 21:01:13 #{t.to_s("%:z")}")

t = Time.new 2014, 10, 30, 21, 18, 1
t.to_s.should eq("2014-10-30 21:18:01 #{t.to_s("%:z")}")
end
end

it "prints without nanoseconds" do
with_env("TZ", nil) do
t = Time.new 2014, 10, 30, 21, 18, 13, nanosecond: 12345
t.to_s.should eq("2014-10-30 21:18:13 #{t.to_s("%:z")}")
end
describe "#to_s" do
it "prints date-time fields" do
Time.utc(2014, 1, 30, 21, 18, 13).to_s.should eq("2014-01-30 21:18:13 UTC")
Time.utc(2014, 10, 1, 21, 18, 13).to_s.should eq("2014-10-01 21:18:13 UTC")
Time.utc(2014, 10, 30, 1, 18, 13).to_s.should eq("2014-10-30 01:18:13 UTC")
Time.utc(2014, 10, 30, 21, 1, 13).to_s.should eq("2014-10-30 21:01:13 UTC")
Time.utc(2014, 10, 30, 21, 18, 1).to_s.should eq("2014-10-30 21:18:01 UTC")
end

it "prints UTC" do
t = Time.utc 2014, 10, 30, 21, 18, 13
t.to_s.should eq("2014-10-30 21:18:13 UTC")
it "omits nanoseconds" do
Time.utc(2014, 10, 30, 21, 18, 13).to_s.should eq("2014-10-30 21:18:13 UTC")
Time.utc(2014, 10, 30, 21, 18, 13, nanosecond: 12345).to_s.should eq("2014-10-30 21:18:13 UTC")
end

it "prints zone" do
it "prints offset for location" do
with_zoneinfo do
location = Time::Location.load("Europe/Berlin")
t = Time.new 2014, 10, 30, 21, 18, 13, location: location
t.to_s.should eq("2014-10-30 21:18:13 +01:00 Europe/Berlin")
Time.new(2014, 10, 30, 21, 18, 13, location: location).to_s.should eq("2014-10-30 21:18:13 +01:00")
Time.new(2014, 10, 30, 21, 18, 13, nanosecond: 123_456, location: location).to_s.should eq("2014-10-30 21:18:13 +01:00")

t = Time.new 2014, 10, 10, 21, 18, 13, location: location
t.to_s.should eq("2014-10-10 21:18:13 +02:00 Europe/Berlin")
Time.new(2014, 10, 10, 21, 18, 13, location: location).to_s.should eq("2014-10-10 21:18:13 +02:00")
Time.new(2014, 10, 10, 21, 18, 13, nanosecond: 123_456, location: location).to_s.should eq("2014-10-10 21:18:13 +02:00")
end
end

it "prints offset" do
it "prints offset for fixed location" do
location = Time::Location.fixed(3601)
Time.new(2014, 1, 2, 3, 4, 5, location: location).to_s.should eq "2014-01-02 03:04:05 +01:00:01"
Time.new(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).to_s.should eq "2014-01-02 03:04:05 +01:00:01"

t = Time.new 2014, 10, 30, 21, 18, 13, location: Time::Location.fixed(-9000)
t.to_s.should eq("2014-10-30 21:18:13 -02:30")
end

it "prints local time" do
# Simulates loading non-fixed offset local time from /etc/localtime
old_local = Time::Location.local
begin
location = Time::Location.new "Local", [Time::Location::Zone.new("STZ", 3600, false), Time::Location::Zone.new("DTZ", -3600, false)], [] of Time::Location::ZoneTransition
Time::Location.local = location

Time.new(2014, 10, 30, 21, 18, 13).to_s.should eq("2014-10-30 21:18:13 +01:00")
ensure
Time::Location.local = old_local
end
end
end

it "#inspect" do
Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02 03:04:05.0 UTC"
Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789).inspect.should eq "2014-01-02 03:04:05.123456789 UTC"

location = Time::Location.load("Europe/Berlin")
Time.new(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05.0 +01:00 Europe/Berlin"
Time.new(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02 03:04:05.123456789 +01:00 Europe/Berlin"

location = Time::Location.fixed(3601)
Time.new(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05.0 +01:00:01"
Time.new(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02 03:04:05.123456789 +01:00:01"
end

it "formats" do
Expand Down Expand Up @@ -934,7 +943,7 @@ describe Time do

location = Time::Location.load("Europe/Berlin")
time = Time.new(2017, 11, 25, 22, 6, 17, location: location)
time.to_s.should eq "2017-11-25 22:06:17 +01:00 Europe/Berlin"
time.to_s.should eq "2017-11-25 22:06:17 +01:00"
end
end

Expand Down
48 changes: 39 additions & 9 deletions src/time.cr
Expand Up @@ -471,21 +471,51 @@ struct Time
year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
end

def inspect(io : IO)
case
when utc?
to_s "%F %T UTC", io
else
if offset % 60 == 0
to_s "%F %T %:z", io
# Prints this `Time` to *io*.
#
# The local date-time is formatted as date string `YYYY-MM-DD HH:mm:ss.nnnnnnnnn +ZZ:ZZ:ZZ`.
# Nanoseconds are omitted if *with_nanoseconds* is `false`.
# When the location is `UTC`, the offset is omitted. Offset seconds are omitted if `0`.
#
# The name of the location is appended unless it is a fixed zone offset.
def inspect(io : IO, with_nanoseconds = true)
to_s "%F %T", io

if with_nanoseconds
if @nanoseconds == 0
io << ".0"
else
to_s "%F %T %::z", io
to_s ".%N", io
end
io << ' ' << location.name unless location.fixed? || location.name == "Local"
end

if utc?
io << " UTC"
else
io << ' '
zone.format(io)
io << ' ' << location.name unless location.fixed?
end

io
end

# Prints this `Time` to *io*.
#
# The local date-time is formatted as date string `YYYY-MM-DD HH:mm:ss +ZZ:ZZ:ZZ`.
# Nanoseconds are always omitted.
# When the location is `UTC`, the offset is replaced with the string `UTC`.
# Offset seconds are omitted if `0`.
def to_s(io : IO)
to_s("%F %T ", io)

if utc?
io << "UTC"
else
zone.format(io)
end
end

# Formats this time using the given format (see `Time::Format`).
#
# ```
Expand Down
39 changes: 2 additions & 37 deletions src/time/format/formatter.cr
Expand Up @@ -148,52 +148,17 @@ struct Time::Format
end

def time_zone(with_seconds = false)
negative, hours, minutes, seconds = local_time_zone_info
io << (negative ? '-' : '+')
io << '0' if hours < 10
io << hours
io << '0' if minutes < 10
io << minutes
if with_seconds
io << '0' if seconds < 10
io << seconds
end
time.zone.format(io, with_colon: false, with_seconds: with_seconds)
end

def time_zone_colon(with_seconds = false)
negative, hours, minutes, seconds = local_time_zone_info
io << (negative ? '-' : '+')
io << '0' if hours < 10
io << hours
io << ':'
io << '0' if minutes < 10
io << minutes
if with_seconds
io << ':'
io << '0' if seconds < 10
io << seconds
end
time.zone.format(io, with_colon: true, with_seconds: with_seconds)
end

def time_zone_colon_with_seconds
time_zone_colon(with_seconds: true)
end

def local_time_zone_info
offset = time.offset
if offset < 0
offset = -offset
negative = true
else
negative = false
end
seconds = offset % 60
minutes = offset / 60
hours = minutes / 60
minutes = minutes % 60
{negative, hours, minutes, seconds}
end

def char(char)
io << char
end
Expand Down

0 comments on commit 3afc781

Please sign in to comment.