From 50dfe367d30db78bbbe8331a84510f59c7ebda05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Tue, 20 May 2025 23:14:40 +0200 Subject: [PATCH 01/11] Format `Time#inspect` with Internet Extended Date/Time Format --- spec/std/time/time_spec.cr | 12 ++++++------ src/time.cr | 7 +++---- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index 75c6d9d925ba..2282c6747925 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -651,18 +651,18 @@ describe Time do 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" + Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02T03:04:05.0Z" + Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789).inspect.should eq "2014-01-02T03:04:05.123456789Z" with_zoneinfo do location = Time::Location.load("Europe/Berlin") - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05.0 +01:00 Europe/Berlin" - Time.local(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" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05.0+01:00[Europe/Berlin]" + Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00[Europe/Berlin]" end location = Time::Location.fixed(3601) - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05.0 +01:00:01" - Time.local(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" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05.0+01:00:01" + Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00:01" end it "at methods" do diff --git a/src/time.cr b/src/time.cr index 0c5f43c226dc..b8ac67c914fa 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1086,7 +1086,7 @@ struct Time # # The name of the location is appended unless it is a fixed zone offset. def inspect(io : IO, with_nanoseconds = true) : Nil - to_s io, "%F %T" + to_s io, "%FT%T" if with_nanoseconds if @nanoseconds == 0 @@ -1097,11 +1097,10 @@ struct Time end if utc? - io << " UTC" + io << "Z" else - io << ' ' zone.format(io) - io << ' ' << location.name unless location.fixed? + io << '[' << location.name << ']' unless location.fixed? end end From de3f9bc8493981d127e3ea9a308262a58f9d46c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 21 May 2025 13:10:29 +0200 Subject: [PATCH 02/11] Optimize `#inspect` --- src/time.cr | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/time.cr b/src/time.cr index b8ac67c914fa..d120f525c13d 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1086,22 +1086,22 @@ struct Time # # The name of the location is appended unless it is a fixed zone offset. def inspect(io : IO, with_nanoseconds = true) : Nil - to_s io, "%FT%T" + formatter = Format::Formatter.new(self, io) + formatter.year_month_day + io << "T" + formatter.twenty_four_hour_time_with_seconds if with_nanoseconds if @nanoseconds == 0 io << ".0" else - to_s io, ".%N" + io << "." + formatter.nanoseconds end end - if utc? - io << "Z" - else - zone.format(io) - io << '[' << location.name << ']' unless location.fixed? - end + formatter.time_zone_z_or_offset(force_colon: true, format_seconds: :auto) + io << '[' << location.name << ']' unless location.fixed? end # Prints this `Time` to *io*. From d473fc24366d159fb939fd317e70cea20c69ac5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 31 Jul 2025 22:31:44 +0200 Subject: [PATCH 03/11] Adjust doctest values --- src/file.cr | 4 ++-- src/http/common.cr | 6 +++--- src/json/to_json.cr | 10 +++++----- src/time.cr | 26 +++++++++++++------------- src/time/format/custom/http_date.cr | 8 ++++---- src/time/location.cr | 2 +- src/yaml/to_yaml.cr | 4 ++-- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/file.cr b/src/file.cr index 5bfea8218460..f4213c51e376 100644 --- a/src/file.cr +++ b/src/file.cr @@ -40,7 +40,7 @@ require "crystal/system/file" # tempfile = File.tempfile("foo") # # File.size(tempfile.path) # => 6 -# File.info(tempfile.path).modification_time # => 2015-10-20 13:11:12 UTC +# File.info(tempfile.path).modification_time # => 2015-10-20T13:11:12Z # File.exists?(tempfile.path) # => true # File.read_lines(tempfile.path) # => ["foobar"] # ``` @@ -202,7 +202,7 @@ class File < IO::FileDescriptor # ``` # File.write("foo", "foo") # File.info("foo").size # => 3 - # File.info("foo").modification_time # => 2015-09-23 06:24:19 UTC + # File.info("foo").modification_time # => 2015-09-23T06:24:19Z # # File.symlink("foo", "bar") # File.info("bar", follow_symlinks: false).type.symlink? # => true diff --git a/src/http/common.cr b/src/http/common.cr index b82455d1df54..f3e11a6a1896 100644 --- a/src/http/common.cr +++ b/src/http/common.cr @@ -343,9 +343,9 @@ module HTTP # ``` # require "http" # - # HTTP.parse_time("Sun, 14 Feb 2016 21:00:00 GMT") # => "2016-02-14 21:00:00 UTC" - # HTTP.parse_time("Sunday, 14-Feb-16 21:00:00 GMT") # => "2016-02-14 21:00:00 UTC" - # HTTP.parse_time("Sun Feb 14 21:00:00 2016") # => "2016-02-14 21:00:00 UTC" + # HTTP.parse_time("Sun, 14 Feb 2016 21:00:00 GMT") # => "2016-02-14T21:00:00Z" + # HTTP.parse_time("Sunday, 14-Feb-16 21:00:00 GMT") # => "2016-02-14T21:00:00Z" + # HTTP.parse_time("Sun Feb 14 21:00:00 2016") # => "2016-02-14T21:00:00Z" # ``` # # Uses `Time::Format::HTTP_DATE` as parser. diff --git a/src/json/to_json.cr b/src/json/to_json.cr index b4d0881f79e9..ad103e7120f0 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -310,7 +310,7 @@ end # end # # timestamp = TimestampArray.from_json(%({"dates":[1459859781,1567628762]})) -# timestamp.dates # => [2016-04-05 12:36:21 UTC, 2019-09-04 20:26:02 UTC] +# timestamp.dates # => [2016-04-05T12:36:21Z, 2019-09-04T20:26:02Z] # timestamp.to_json # => %({"dates":[1459859781,1567628762]}) # ``` # @@ -328,7 +328,7 @@ end # end # # timestamp = TimestampArray.from_json(%({"dates":["Apr 5, 2016","Sep 4, 2019"]})) -# timestamp.dates # => [2016-04-05 00:00:00 UTC, 2019-09-04 00:00:00 UTC] +# timestamp.dates # => [2016-04-05T00:00:00Z, 2019-09-04T00:00:00Z] # timestamp.to_json # => %({"dates":["Apr 5, 2016","Sep 4, 2019"]}) # ``` # @@ -371,7 +371,7 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":1459859781,"bar":1567628762}})) -# timestamp.birthdays # => {"foo" => 2016-04-05 12:36:21 UTC, "bar" => 2019-09-04 20:26:02 UTC} +# timestamp.birthdays # => {"foo" => 2016-04-05T12:36:21Z, "bar" => 2019-09-04T20:26:02Z} # timestamp.to_json # => %({"birthdays":{"foo":1459859781,"bar":1567628762}}) # ``` # @@ -389,7 +389,7 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}})) -# timestamp.birthdays # => {"foo" => 2016-04-05 00:00:00 UTC, "bar" => 2019-09-04 00:00:00 UTC} +# timestamp.birthdays # => {"foo" => 2016-04-05T00:00:00Z, "bar" => 2019-09-04T00:00:00Z} # timestamp.to_json # => %({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}}) # ``` # @@ -435,7 +435,7 @@ end # end # # person = Person.from_json(%({"birth_date": 1459859781})) -# person.birth_date # => 2016-04-05 12:36:21 UTC +# person.birth_date # => 2016-04-05T12:36:21Z # person.to_json # => %({"birth_date":1459859781}) # ``` module Time::EpochConverter diff --git a/src/time.cr b/src/time.cr index d120f525c13d..a93457d4d857 100644 --- a/src/time.cr +++ b/src/time.cr @@ -84,7 +84,7 @@ require "crystal/system/time" # # ``` # time = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) -# time # => 2018-03-08 22:05:13 +01:00 Europe/Berlin +# time # => 2018-03-08T22:05:13+01:00[Europe/Berlin] # time.location # => # # time.zone # => # # time.offset # => 3600 @@ -106,8 +106,8 @@ require "crystal/system/time" # ``` # time_de = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) # time_ar = time_de.in Time::Location.load("America/Buenos_Aires") -# time_de # => 2018-03-08 22:05:13 +01:00 Europe/Berlin -# time_ar # => 2018-03-08 18:05:13 -03:00 America/Buenos_Aires +# time_de # => 2018-03-08T22:05:13+01:00[Europe/Berlin] +# time_ar # => 2018-03-08T18:05:13-03:00[America/Buenos_Aires] # ``` # # Both `Time` instances show a different local date-time, but they represent @@ -115,8 +115,8 @@ require "crystal/system/time" # equal: # # ``` -# time_de.to_utc # => 2018-03-08 21:05:13 UTC -# time_ar.to_utc # => 2018-03-08 21:05:13 UTC +# time_de.to_utc # => 2018-03-08T21:05:13Z +# time_ar.to_utc # => 2018-03-08T21:05:13Z # time_de == time_ar # => true # ``` # @@ -517,7 +517,7 @@ struct Time # The time zone is always UTC. # # ``` - # Time.unix(981173106) # => 2001-02-03 04:05:06 UTC + # Time.unix(981173106) # => 2001-02-03T04:05:06Z # ``` def self.unix(seconds : Int) : Time utc(seconds: UNIX_EPOCH.total_seconds + seconds, nanoseconds: 0) @@ -1028,8 +1028,8 @@ struct Time # time_de == time_ar # => true # # # both times represent the same instant: - # time_de.to_utc # => 2018-03-08 21:05:13 UTC - # time_ar.to_utc # => 2018-03-08 21:05:13 UTC + # time_de.to_utc # => 2018-03-08T21:05:13Z + # time_ar.to_utc # => 2018-03-08T21:05:13Z # ``` def ==(other : Time) : Bool total_seconds == other.total_seconds && nanosecond == other.nanosecond @@ -1337,8 +1337,8 @@ struct Time # ``` # time_de = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) # time_ar = time_de.in Time::Location.load("America/Buenos_Aires") - # time_de # => 2018-03-08 22:05:13 +01:00 Europe/Berlin - # time_ar # => 2018-03-08 18:05:13 -03:00 America/Buenos_Aires + # time_de # => 2018-03-08T22:05:13+01:00[Europe/Berlin] + # time_ar # => 2018-03-08T18:05:13-03:00[America/Buenos_Aires] # ``` # # In contrast, `#to_local_in` changes to a different location while @@ -1422,9 +1422,9 @@ struct Time # # ``` # now = Time.utc(2023, 5, 16, 17, 53, 22) - # now.at_beginning_of_week # => 2023-05-15 00:00:00 UTC - # now.at_beginning_of_week(:sunday) # => 2023-05-14 00:00:00 UTC - # now.at_beginning_of_week(:wednesday) # => 2023-05-10 00:00:00 UTC + # now.at_beginning_of_week # => 2023-05-15T00:00:00Z + # now.at_beginning_of_week(:sunday) # => 2023-05-14T00:00:00Z + # now.at_beginning_of_week(:wednesday) # => 2023-05-10T00:00:00Z # ``` # TODO: Ensure correctness in local time-line. def at_beginning_of_week(start_day : Time::DayOfWeek = :monday) : Time diff --git a/src/time/format/custom/http_date.cr b/src/time/format/custom/http_date.cr index 25847b21aa00..2b82b3675515 100644 --- a/src/time/format/custom/http_date.cr +++ b/src/time/format/custom/http_date.cr @@ -10,10 +10,10 @@ struct Time::Format # * [asctime](http://en.cppreference.com/w/c/chrono/asctime) # # ``` - # Time::Format::HTTP_DATE.parse("Sun, 14 Feb 2016 21:00:00 GMT") # => 2016-02-14 21:00:00 UTC - # Time::Format::HTTP_DATE.parse("Sunday, 14-Feb-16 21:00:00 GMT") # => 2016-02-14 21:00:00 UTC - # Time::Format::HTTP_DATE.parse("Sun, 14-Feb-2016 21:00:00 GMT") # => 2016-02-14 21:00:00 UTC - # Time::Format::HTTP_DATE.parse("Sun Feb 14 21:00:00 2016") # => 2016-02-14 21:00:00 UTC + # Time::Format::HTTP_DATE.parse("Sun, 14 Feb 2016 21:00:00 GMT") # => 2016-02-14T21:00:00Z + # Time::Format::HTTP_DATE.parse("Sunday, 14-Feb-16 21:00:00 GMT") # => 2016-02-14T21:00:00Z + # Time::Format::HTTP_DATE.parse("Sun, 14-Feb-2016 21:00:00 GMT") # => 2016-02-14T21:00:00Z + # Time::Format::HTTP_DATE.parse("Sun Feb 14 21:00:00 2016") # => 2016-02-14T21:00:00Z # # Time::Format::HTTP_DATE.format(Time.utc(2016, 2, 15)) # => "Mon, 15 Feb 2016 00:00:00 GMT" # ``` diff --git a/src/time/location.cr b/src/time/location.cr index 8bc66b9dcfef..5dd188b0ce31 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -18,7 +18,7 @@ require "./location/loader" # location = Time::Location.load("Europe/Berlin") # location # => # # time = Time.local(2016, 2, 15, 21, 1, 10, location: location) -# time # => 2016-02-15 21:01:10 +01:00 Europe/Berlin +# time # => 2016-02-15T21:01:10+01:00[Europe/Berlin] # ``` # # A custom time zone database can be configured through the environment variable diff --git a/src/yaml/to_yaml.cr b/src/yaml/to_yaml.cr index 7c9fdb2c778e..b7dcb6d91b4e 100644 --- a/src/yaml/to_yaml.cr +++ b/src/yaml/to_yaml.cr @@ -276,7 +276,7 @@ end # end # # timestamp = Timestamp.from_yaml(%({"values":[1459859781,1567628762]})) -# timestamp.values # => [2016-04-05 12:36:21 UTC, 2019-09-04 20:26:02 UTC] +# timestamp.values # => [2016-04-05T12:36:21Z, 2019-09-04T20:26:02Z] # timestamp.to_yaml # => "---\nvalues:\n- 1459859781\n- 1567628762\n" # ``` # @@ -294,7 +294,7 @@ end # end # # timestamp = Timestamp.from_yaml(%({"values":["Apr 5, 2016","Sep 4, 2019"]})) -# timestamp.values # => [2016-04-05 00:00:00 UTC, 2019-09-04 00:00:00 UTC] +# timestamp.values # => [2016-04-05T00:00:00Z, 2019-09-04T00:00:00Z] # timestamp.to_yaml # => "---\nvalues:\n- Apr 5, 2016\n- Sep 4, 2019\n" # ``` # From 3436a2c0454f7f8705179ff90be08382611ac9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Thu, 31 Jul 2025 22:42:04 +0200 Subject: [PATCH 04/11] Adjust more doctest values and other documentation --- src/json/to_json.cr | 2 +- src/time.cr | 42 +++++++++++++++--------------- src/time/format/custom/iso_8601.cr | 6 ++--- src/time/format/custom/rfc_2822.cr | 2 +- src/time/format/custom/rfc_3339.cr | 4 +-- src/time/location.cr | 2 +- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/json/to_json.cr b/src/json/to_json.cr index ad103e7120f0..5eec8b323f70 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -459,7 +459,7 @@ end # end # # timestamp = Timestamp.from_json(%({"value": 1459860483856})) -# timestamp.value # => 2016-04-05 12:48:03.856 UTC +# timestamp.value # => 2016-04-05T12:48:03.856Z # timestamp.to_json # => %({"value":1459860483856}) # ``` module Time::EpochMillisConverter diff --git a/src/time.cr b/src/time.cr index a93457d4d857..338aecc36a6b 100644 --- a/src/time.cr +++ b/src/time.cr @@ -9,7 +9,7 @@ require "crystal/system/time" # Leap seconds are ignored. # # Internally, the time is stored as an `Int64` representing seconds from epoch -# (`0001-01-01 00:00:00.0 UTC`) and an `Int32` representing +# (`0001-01-01T00:00:00.0Z`) and an `Int32` representing # nanosecond-of-second with value range `0..999_999_999`. # # The supported date range is `0001-01-01 00:00:00.0` to @@ -94,7 +94,7 @@ require "crystal/system/time" # # ``` # time = Time.utc(2018, 3, 8, 22, 5, 13) -# time # => 2018-03-08 22:05:13.0 UTC +# time # => 2018-03-08T22:05:13.0Z # time.location # => # # time.zone # => # # time.offset # => 0 @@ -267,7 +267,7 @@ struct Time # :nodoc: DAYS_PER_4_YEARS = 365*4 + 1 - # This constant is defined to be "1970-01-01 00:00:00 UTC". + # This constant is defined as `1970-01-01T00:00:00.0Z`". # Can be used to create a `Time::Span` that represents an Unix Epoch time duration. # # ``` @@ -379,7 +379,7 @@ struct Time # # ``` # time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin")) - # time.inspect # => "2016-02-15 10:20:30.0 +01:00 Europe/Berlin" + # time.inspect # => "2016-02-15T10:20:30.0+01:00[Europe/Berlin]" # ``` # # Valid value ranges for the individual fields: @@ -470,7 +470,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of *seconds* - # and *nanoseconds* elapsed from epoch (`0001-01-01 00:00:00.0 UTC`) + # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00.0`) # observed in *location*. # # Valid range for *seconds* is `0..315_537_897_599`. @@ -493,7 +493,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of *seconds* - # and *nanoseconds* elapsed from epoch (`0001-01-01 00:00:00.0 UTC`) + # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00.0Z`) # in UTC. # # Valid range for *seconds* is `0..315_537_897_599`. @@ -512,7 +512,7 @@ struct Time {% end %} # Creates a new `Time` instance that corresponds to the number of - # *seconds* elapsed since the Unix epoch (`1970-01-01 00:00:00 UTC`). + # *seconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). # # The time zone is always UTC. # @@ -524,12 +524,12 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of - # *milliseconds* elapsed since the Unix epoch (`1970-01-01 00:00:00 UTC`). + # *milliseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). # # The time zone is always UTC. # # ``` - # time = Time.unix_ms(981173106789) # => 2001-02-03 04:05:06.789 UTC + # time = Time.unix_ms(981173106789) # => 2001-02-03T04:05:06.789Z # time.millisecond # => 789 # ``` def self.unix_ms(milliseconds : Int) : Time @@ -540,12 +540,12 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of - # *nanoseconds* elapsed since the Unix epoch (`1970-01-01 00:00:00.000000000 UTC`). + # *nanoseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). # # The time zone is always UTC. # # ``` - # time = Time.unix_ns(981173106789479273) # => 2001-02-03 04:05:06.789479273 UTC + # time = Time.unix_ns(981173106789479273) # => 2001-02-03T04:05:06.789479273Z # time.nanosecond # => 789479273 # ``` def self.unix_ns(nanoseconds : Int) : Time @@ -566,8 +566,8 @@ struct Time # new_year = Time.utc(2019, 1, 1, 0, 0, 0) # tokyo = new_year.to_local_in(Time::Location.load("Asia/Tokyo")) # new_york = new_year.to_local_in(Time::Location.load("America/New_York")) - # tokyo.inspect # => "2019-01-01 00:00:00.0 +09:00 Asia/Tokyo" - # new_york.inspect # => "2019-01-01 00:00:00.0 -05:00 America/New_York" + # tokyo.inspect # => "2019-01-01T00:00:00.0+09:00[Asia/Tokyo]" + # new_york.inspect # => "2019-01-01T00:00:00.0-05:00[America/New_York]" # ``` def to_local_in(location : Location) : Time local_seconds = offset_seconds @@ -1168,7 +1168,7 @@ struct Time # Parse time format specified by [RFC 3339](https://tools.ietf.org/html/rfc3339) ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50.0 UTC + # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z # ``` def self.parse_rfc3339(time : String) : self Format::RFC_3339.parse(time) @@ -1182,7 +1182,7 @@ struct Time # Use `#to_rfc3339` to format a `Time` according to . # # ``` - # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50.0 UTC + # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z # ``` def self.parse_iso8601(time : String) Format::ISO_8601_DATE_TIME.parse(time) @@ -1212,7 +1212,7 @@ struct Time # This is also compatible to [RFC 882](https://tools.ietf.org/html/rfc882) and [RFC 1123](https://tools.ietf.org/html/rfc1123#page-55). # # ``` - # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15 04:35:50.0 UTC + # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50.0Z # ``` def self.parse_rfc2822(time : String) : self Format::RFC_2822.parse(time) @@ -1223,7 +1223,7 @@ struct Time # See `Time::Format` for details. # # ``` - # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05 00:00:00.0 +02:00 Europe/Berlin + # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05T00:00:00.0+02:00[Europe/Berlin] # ``` # # If there is no time zone information in the formatted time, *location* will @@ -1281,7 +1281,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01 00:00:00 UTC`). + # (`1970-01-01T00:00:00.0Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5) @@ -1292,7 +1292,7 @@ struct Time end # Returns the number of milliseconds since the Unix epoch - # (`1970-01-01 00:00:00 UTC`). + # (`1970-01-01T00:00:00.0Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) @@ -1303,7 +1303,7 @@ struct Time end # Returns the number of nanoseconds since the Unix epoch - # (`1970-01-01 00:00:00.000000000 UTC`). + # (`1970-01-01T00:00:00.0Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_910_123) @@ -1314,7 +1314,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01 00:00:00 UTC`) as `Float64` with nanosecond precision. + # (`1970-01-01T00:00:00.0Z`) as `Float64` with nanosecond precision. # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr index b6e634c7a4de..471f597110db 100644 --- a/src/time/format/custom/iso_8601.cr +++ b/src/time/format/custom/iso_8601.cr @@ -118,7 +118,7 @@ struct Time::Format # The ISO 8601 date format. # # ``` - # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15 00:00:00.0 UTC + # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15T00:00:00.0Z # Time::Format::ISO_8601_DATE.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15" # ``` module ISO_8601_DATE @@ -148,7 +148,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_DATE_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" - # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50.0 UTC + # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z # ``` module ISO_8601_DATE_TIME # Parses a string into a `Time`. @@ -177,7 +177,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "04:35:50Z" - # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01 04:35:50.0 UTC + # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01T04:35:50.0Z # ``` module ISO_8601_TIME # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_2822.cr b/src/time/format/custom/rfc_2822.cr index 3397cce8e471..41b3a9212354 100644 --- a/src/time/format/custom/rfc_2822.cr +++ b/src/time/format/custom/rfc_2822.cr @@ -7,7 +7,7 @@ struct Time::Format # Time::Format::RFC_2822.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "Mon, 15 Feb 2016 04:35:50 +0000" # # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 +0000") # => 2016-02-15 04:35:50.0 +00:00 - # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15 04:35:50.0 UTC + # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50.0Z # ``` module RFC_2822 # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_3339.cr b/src/time/format/custom/rfc_3339.cr index 3295d9ecacaf..23ae15c04d80 100644 --- a/src/time/format/custom/rfc_3339.cr +++ b/src/time/format/custom/rfc_3339.cr @@ -4,8 +4,8 @@ struct Time::Format # ``` # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" # - # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50.0 UTC - # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50.0 UTC + # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z + # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15T04:35:50.0Z # ``` module RFC_3339 # Parses a string into a `Time`. diff --git a/src/time/location.cr b/src/time/location.cr index 5dd188b0ce31..3e1a9637cd73 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -447,7 +447,7 @@ class Time::Location # Returns the time zone offset observed at *unix_seconds*. # # *unix_seconds* expresses the number of seconds since UNIX epoch - # (`1970-01-01 00:00:00 UTC`). + # (`1970-01-01T00:00:00.0Z`). def lookup(unix_seconds : Int) : Zone unless @cached_range[0] <= unix_seconds < @cached_range[1] @cached_zone, @cached_range = lookup_with_boundaries(unix_seconds) From f5ff6562e68a9412af95f1e2487067d023ec0547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 10:18:28 +0200 Subject: [PATCH 05/11] Omit zero nanoseconds entirely --- spec/std/time/time_spec.cr | 6 ++-- src/time.cr | 46 ++++++++++++++---------------- src/time/format/custom/iso_8601.cr | 6 ++-- src/time/format/custom/rfc_2822.cr | 2 +- src/time/format/custom/rfc_3339.cr | 4 +-- src/time/location.cr | 2 +- 6 files changed, 31 insertions(+), 35 deletions(-) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index 2282c6747925..73100144e05e 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -651,17 +651,17 @@ describe Time do end it "#inspect" do - Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02T03:04:05.0Z" + Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02T03:04:05Z" Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789).inspect.should eq "2014-01-02T03:04:05.123456789Z" with_zoneinfo do location = Time::Location.load("Europe/Berlin") - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05.0+01:00[Europe/Berlin]" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05+01:00[Europe/Berlin]" Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00[Europe/Berlin]" end location = Time::Location.fixed(3601) - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05.0+01:00:01" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05+01:00:01" Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00:01" end diff --git a/src/time.cr b/src/time.cr index 338aecc36a6b..7a10e80e3ebf 100644 --- a/src/time.cr +++ b/src/time.cr @@ -9,7 +9,7 @@ require "crystal/system/time" # Leap seconds are ignored. # # Internally, the time is stored as an `Int64` representing seconds from epoch -# (`0001-01-01T00:00:00.0Z`) and an `Int32` representing +# (`0001-01-01T00:00:00Z`) and an `Int32` representing # nanosecond-of-second with value range `0..999_999_999`. # # The supported date range is `0001-01-01 00:00:00.0` to @@ -94,7 +94,7 @@ require "crystal/system/time" # # ``` # time = Time.utc(2018, 3, 8, 22, 5, 13) -# time # => 2018-03-08T22:05:13.0Z +# time # => 2018-03-08T22:05:13Z # time.location # => # # time.zone # => # # time.offset # => 0 @@ -267,7 +267,7 @@ struct Time # :nodoc: DAYS_PER_4_YEARS = 365*4 + 1 - # This constant is defined as `1970-01-01T00:00:00.0Z`". + # This constant is defined as `1970-01-01T00:00:00Z`". # Can be used to create a `Time::Span` that represents an Unix Epoch time duration. # # ``` @@ -379,7 +379,7 @@ struct Time # # ``` # time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin")) - # time.inspect # => "2016-02-15T10:20:30.0+01:00[Europe/Berlin]" + # time.inspect # => "2016-02-15T10:20:30+01:00[Europe/Berlin]" # ``` # # Valid value ranges for the individual fields: @@ -493,7 +493,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of *seconds* - # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00.0Z`) + # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00Z`) # in UTC. # # Valid range for *seconds* is `0..315_537_897_599`. @@ -512,7 +512,7 @@ struct Time {% end %} # Creates a new `Time` instance that corresponds to the number of - # *seconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). + # *seconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). # # The time zone is always UTC. # @@ -524,7 +524,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of - # *milliseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). + # *milliseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). # # The time zone is always UTC. # @@ -540,7 +540,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of - # *nanoseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00.0Z`). + # *nanoseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). # # The time zone is always UTC. # @@ -566,8 +566,8 @@ struct Time # new_year = Time.utc(2019, 1, 1, 0, 0, 0) # tokyo = new_year.to_local_in(Time::Location.load("Asia/Tokyo")) # new_york = new_year.to_local_in(Time::Location.load("America/New_York")) - # tokyo.inspect # => "2019-01-01T00:00:00.0+09:00[Asia/Tokyo]" - # new_york.inspect # => "2019-01-01T00:00:00.0-05:00[America/New_York]" + # tokyo.inspect # => "2019-01-01T00:00:00+09:00[Asia/Tokyo]" + # new_york.inspect # => "2019-01-01T00:00:00-05:00[America/New_York]" # ``` def to_local_in(location : Location) : Time local_seconds = offset_seconds @@ -1091,13 +1091,9 @@ struct Time io << "T" formatter.twenty_four_hour_time_with_seconds - if with_nanoseconds - if @nanoseconds == 0 - io << ".0" - else - io << "." - formatter.nanoseconds - end + if with_nanoseconds && !@nanoseconds.zero? + io << "." + formatter.nanoseconds end formatter.time_zone_z_or_offset(force_colon: true, format_seconds: :auto) @@ -1168,7 +1164,7 @@ struct Time # Parse time format specified by [RFC 3339](https://tools.ietf.org/html/rfc3339) ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z + # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z # ``` def self.parse_rfc3339(time : String) : self Format::RFC_3339.parse(time) @@ -1182,7 +1178,7 @@ struct Time # Use `#to_rfc3339` to format a `Time` according to . # # ``` - # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z + # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z # ``` def self.parse_iso8601(time : String) Format::ISO_8601_DATE_TIME.parse(time) @@ -1212,7 +1208,7 @@ struct Time # This is also compatible to [RFC 882](https://tools.ietf.org/html/rfc882) and [RFC 1123](https://tools.ietf.org/html/rfc1123#page-55). # # ``` - # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50.0Z + # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50Z # ``` def self.parse_rfc2822(time : String) : self Format::RFC_2822.parse(time) @@ -1223,7 +1219,7 @@ struct Time # See `Time::Format` for details. # # ``` - # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05T00:00:00.0+02:00[Europe/Berlin] + # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05T00:00:00+02:00[Europe/Berlin] # ``` # # If there is no time zone information in the formatted time, *location* will @@ -1281,7 +1277,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01T00:00:00.0Z`). + # (`1970-01-01T00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5) @@ -1292,7 +1288,7 @@ struct Time end # Returns the number of milliseconds since the Unix epoch - # (`1970-01-01T00:00:00.0Z`). + # (`1970-01-01T00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) @@ -1303,7 +1299,7 @@ struct Time end # Returns the number of nanoseconds since the Unix epoch - # (`1970-01-01T00:00:00.0Z`). + # (`1970-01-01T00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_910_123) @@ -1314,7 +1310,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01T00:00:00.0Z`) as `Float64` with nanosecond precision. + # (`1970-01-01T00:00:00Z`) as `Float64` with nanosecond precision. # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr index 471f597110db..58bb4c4c32fb 100644 --- a/src/time/format/custom/iso_8601.cr +++ b/src/time/format/custom/iso_8601.cr @@ -118,7 +118,7 @@ struct Time::Format # The ISO 8601 date format. # # ``` - # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15T00:00:00.0Z + # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15T00:00:00Z # Time::Format::ISO_8601_DATE.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15" # ``` module ISO_8601_DATE @@ -148,7 +148,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_DATE_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" - # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z + # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z # ``` module ISO_8601_DATE_TIME # Parses a string into a `Time`. @@ -177,7 +177,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "04:35:50Z" - # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01T04:35:50.0Z + # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01T04:35:50Z # ``` module ISO_8601_TIME # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_2822.cr b/src/time/format/custom/rfc_2822.cr index 41b3a9212354..b23678823b1d 100644 --- a/src/time/format/custom/rfc_2822.cr +++ b/src/time/format/custom/rfc_2822.cr @@ -7,7 +7,7 @@ struct Time::Format # Time::Format::RFC_2822.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "Mon, 15 Feb 2016 04:35:50 +0000" # # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 +0000") # => 2016-02-15 04:35:50.0 +00:00 - # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50.0Z + # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50Z # ``` module RFC_2822 # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_3339.cr b/src/time/format/custom/rfc_3339.cr index 23ae15c04d80..ae298f4e0496 100644 --- a/src/time/format/custom/rfc_3339.cr +++ b/src/time/format/custom/rfc_3339.cr @@ -4,8 +4,8 @@ struct Time::Format # ``` # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" # - # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50.0Z - # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15T04:35:50.0Z + # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z + # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15T04:35:50Z # ``` module RFC_3339 # Parses a string into a `Time`. diff --git a/src/time/location.cr b/src/time/location.cr index 3e1a9637cd73..efa3f418ed92 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -447,7 +447,7 @@ class Time::Location # Returns the time zone offset observed at *unix_seconds*. # # *unix_seconds* expresses the number of seconds since UNIX epoch - # (`1970-01-01T00:00:00.0Z`). + # (`1970-01-01T00:00:00Z`). def lookup(unix_seconds : Int) : Zone unless @cached_range[0] <= unix_seconds < @cached_range[1] @cached_zone, @cached_range = lookup_with_boundaries(unix_seconds) From c45f3163f6ea6a5529a9dc1b89d34867886931d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 10:47:22 +0200 Subject: [PATCH 06/11] Replace separator char by space for readability --- spec/std/time/time_spec.cr | 12 ++--- src/file.cr | 4 +- src/http/common.cr | 6 +-- src/json/to_json.cr | 12 ++--- src/log/format.cr | 6 +-- src/time.cr | 72 ++++++++++++++--------------- src/time/format/custom/http_date.cr | 8 ++-- src/time/format/custom/iso_8601.cr | 8 ++-- src/time/format/custom/rfc_2822.cr | 2 +- src/time/format/custom/rfc_3339.cr | 6 +-- src/time/location.cr | 4 +- src/yaml/to_yaml.cr | 4 +- 12 files changed, 72 insertions(+), 72 deletions(-) diff --git a/spec/std/time/time_spec.cr b/spec/std/time/time_spec.cr index 73100144e05e..2a46ded70349 100644 --- a/spec/std/time/time_spec.cr +++ b/spec/std/time/time_spec.cr @@ -651,18 +651,18 @@ describe Time do end it "#inspect" do - Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02T03:04:05Z" - Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789).inspect.should eq "2014-01-02T03:04:05.123456789Z" + Time.utc(2014, 1, 2, 3, 4, 5).inspect.should eq "2014-01-02 03:04:05Z" + Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789).inspect.should eq "2014-01-02 03:04:05.123456789Z" with_zoneinfo do location = Time::Location.load("Europe/Berlin") - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05+01:00[Europe/Berlin]" - Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00[Europe/Berlin]" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05+01:00[Europe/Berlin]" + Time.local(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]" end location = Time::Location.fixed(3601) - Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02T03:04:05+01:00:01" - Time.local(2014, 1, 2, 3, 4, 5, nanosecond: 123_456_789, location: location).inspect.should eq "2014-01-02T03:04:05.123456789+01:00:01" + Time.local(2014, 1, 2, 3, 4, 5, location: location).inspect.should eq "2014-01-02 03:04:05+01:00:01" + Time.local(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 "at methods" do diff --git a/src/file.cr b/src/file.cr index f4213c51e376..fe24193b3db5 100644 --- a/src/file.cr +++ b/src/file.cr @@ -40,7 +40,7 @@ require "crystal/system/file" # tempfile = File.tempfile("foo") # # File.size(tempfile.path) # => 6 -# File.info(tempfile.path).modification_time # => 2015-10-20T13:11:12Z +# File.info(tempfile.path).modification_time # => 2015-10-20 13:11:12Z # File.exists?(tempfile.path) # => true # File.read_lines(tempfile.path) # => ["foobar"] # ``` @@ -202,7 +202,7 @@ class File < IO::FileDescriptor # ``` # File.write("foo", "foo") # File.info("foo").size # => 3 - # File.info("foo").modification_time # => 2015-09-23T06:24:19Z + # File.info("foo").modification_time # => 2015-09-23 06:24:19Z # # File.symlink("foo", "bar") # File.info("bar", follow_symlinks: false).type.symlink? # => true diff --git a/src/http/common.cr b/src/http/common.cr index f3e11a6a1896..66963e669fe1 100644 --- a/src/http/common.cr +++ b/src/http/common.cr @@ -343,9 +343,9 @@ module HTTP # ``` # require "http" # - # HTTP.parse_time("Sun, 14 Feb 2016 21:00:00 GMT") # => "2016-02-14T21:00:00Z" - # HTTP.parse_time("Sunday, 14-Feb-16 21:00:00 GMT") # => "2016-02-14T21:00:00Z" - # HTTP.parse_time("Sun Feb 14 21:00:00 2016") # => "2016-02-14T21:00:00Z" + # HTTP.parse_time("Sun, 14 Feb 2016 21:00:00 GMT") # => "2016-02-14 21:00:00Z" + # HTTP.parse_time("Sunday, 14-Feb-16 21:00:00 GMT") # => "2016-02-14 21:00:00Z" + # HTTP.parse_time("Sun Feb 14 21:00:00 2016") # => "2016-02-14 21:00:00Z" # ``` # # Uses `Time::Format::HTTP_DATE` as parser. diff --git a/src/json/to_json.cr b/src/json/to_json.cr index 5eec8b323f70..979709de7adf 100644 --- a/src/json/to_json.cr +++ b/src/json/to_json.cr @@ -310,7 +310,7 @@ end # end # # timestamp = TimestampArray.from_json(%({"dates":[1459859781,1567628762]})) -# timestamp.dates # => [2016-04-05T12:36:21Z, 2019-09-04T20:26:02Z] +# timestamp.dates # => [2016-04-05 12:36:21Z, 2019-09-04 20:26:02Z] # timestamp.to_json # => %({"dates":[1459859781,1567628762]}) # ``` # @@ -328,7 +328,7 @@ end # end # # timestamp = TimestampArray.from_json(%({"dates":["Apr 5, 2016","Sep 4, 2019"]})) -# timestamp.dates # => [2016-04-05T00:00:00Z, 2019-09-04T00:00:00Z] +# timestamp.dates # => [2016-04-05 00:00:00Z, 2019-09-04 00:00:00Z] # timestamp.to_json # => %({"dates":["Apr 5, 2016","Sep 4, 2019"]}) # ``` # @@ -371,7 +371,7 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":1459859781,"bar":1567628762}})) -# timestamp.birthdays # => {"foo" => 2016-04-05T12:36:21Z, "bar" => 2019-09-04T20:26:02Z} +# timestamp.birthdays # => {"foo" => 2016-04-05 12:36:21Z, "bar" => 2019-09-04 20:26:02Z} # timestamp.to_json # => %({"birthdays":{"foo":1459859781,"bar":1567628762}}) # ``` # @@ -389,7 +389,7 @@ end # end # # timestamp = TimestampHash.from_json(%({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}})) -# timestamp.birthdays # => {"foo" => 2016-04-05T00:00:00Z, "bar" => 2019-09-04T00:00:00Z} +# timestamp.birthdays # => {"foo" => 2016-04-05 00:00:00Z, "bar" => 2019-09-04 00:00:00Z} # timestamp.to_json # => %({"birthdays":{"foo":"Apr 5, 2016","bar":"Sep 4, 2019"}}) # ``` # @@ -435,7 +435,7 @@ end # end # # person = Person.from_json(%({"birth_date": 1459859781})) -# person.birth_date # => 2016-04-05T12:36:21Z +# person.birth_date # => 2016-04-05 12:36:21Z # person.to_json # => %({"birth_date":1459859781}) # ``` module Time::EpochConverter @@ -459,7 +459,7 @@ end # end # # timestamp = Timestamp.from_json(%({"value": 1459860483856})) -# timestamp.value # => 2016-04-05T12:48:03.856Z +# timestamp.value # => 2016-04-05 12:48:03.856Z # timestamp.to_json # => %({"value":1459860483856}) # ``` module Time::EpochMillisConverter diff --git a/src/log/format.cr b/src/log/format.cr index d3039ab4ffc6..34285c2e6ff4 100644 --- a/src/log/format.cr +++ b/src/log/format.cr @@ -184,17 +184,17 @@ end # # It writes log entries with the following format: # ``` -# 2020-05-07T17:40:07.994508000Z INFO - my.source: Initializing everything +# 2020-05-07 17:40:07.994508000Z INFO - my.source: Initializing everything # ``` # # When the entries have context data it's also written to the output: # ``` -# 2020-05-07T17:40:07.994508000Z INFO - my.source: Initializing everything -- {"data" => 123} +# 2020-05-07 17:40:07.994508000Z INFO - my.source: Initializing everything -- {"data" => 123} # ``` # # Exceptions are written in a separate line: # ``` -# 2020-05-07T17:40:07.994508000Z ERROR - my.source: Something failed +# 2020-05-07 17:40:07.994508000Z ERROR - my.source: Something failed # Oh, no (Exception) # from ... # ``` diff --git a/src/time.cr b/src/time.cr index 7a10e80e3ebf..036859fc5199 100644 --- a/src/time.cr +++ b/src/time.cr @@ -9,7 +9,7 @@ require "crystal/system/time" # Leap seconds are ignored. # # Internally, the time is stored as an `Int64` representing seconds from epoch -# (`0001-01-01T00:00:00Z`) and an `Int32` representing +# (`0001-01-01 00:00:00Z`) and an `Int32` representing # nanosecond-of-second with value range `0..999_999_999`. # # The supported date range is `0001-01-01 00:00:00.0` to @@ -84,7 +84,7 @@ require "crystal/system/time" # # ``` # time = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) -# time # => 2018-03-08T22:05:13+01:00[Europe/Berlin] +# time # => 2018-03-08 22:05:13+01:00[Europe/Berlin] # time.location # => # # time.zone # => # # time.offset # => 3600 @@ -94,7 +94,7 @@ require "crystal/system/time" # # ``` # time = Time.utc(2018, 3, 8, 22, 5, 13) -# time # => 2018-03-08T22:05:13Z +# time # => 2018-03-08 22:05:13Z # time.location # => # # time.zone # => # # time.offset # => 0 @@ -106,8 +106,8 @@ require "crystal/system/time" # ``` # time_de = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) # time_ar = time_de.in Time::Location.load("America/Buenos_Aires") -# time_de # => 2018-03-08T22:05:13+01:00[Europe/Berlin] -# time_ar # => 2018-03-08T18:05:13-03:00[America/Buenos_Aires] +# time_de # => 2018-03-08 22:05:13+01:00[Europe/Berlin] +# time_ar # => 2018-03-08 18:05:13-03:00[America/Buenos_Aires] # ``` # # Both `Time` instances show a different local date-time, but they represent @@ -115,8 +115,8 @@ require "crystal/system/time" # equal: # # ``` -# time_de.to_utc # => 2018-03-08T21:05:13Z -# time_ar.to_utc # => 2018-03-08T21:05:13Z +# time_de.to_utc # => 2018-03-08 21:05:13Z +# time_ar.to_utc # => 2018-03-08 21:05:13Z # time_de == time_ar # => true # ``` # @@ -267,7 +267,7 @@ struct Time # :nodoc: DAYS_PER_4_YEARS = 365*4 + 1 - # This constant is defined as `1970-01-01T00:00:00Z`". + # This constant is defined as `1970-01-01 00:00:00Z`". # Can be used to create a `Time::Span` that represents an Unix Epoch time duration. # # ``` @@ -379,7 +379,7 @@ struct Time # # ``` # time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin")) - # time.inspect # => "2016-02-15T10:20:30+01:00[Europe/Berlin]" + # time.inspect # => "2016-02-15 10:20:30+01:00[Europe/Berlin]" # ``` # # Valid value ranges for the individual fields: @@ -470,7 +470,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of *seconds* - # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00.0`) + # and *nanoseconds* elapsed from epoch (`0001-01-01 00:00:00.0`) # observed in *location*. # # Valid range for *seconds* is `0..315_537_897_599`. @@ -493,7 +493,7 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of *seconds* - # and *nanoseconds* elapsed from epoch (`0001-01-01T00:00:00Z`) + # and *nanoseconds* elapsed from epoch (`0001-01-01 00:00:00Z`) # in UTC. # # Valid range for *seconds* is `0..315_537_897_599`. @@ -512,24 +512,24 @@ struct Time {% end %} # Creates a new `Time` instance that corresponds to the number of - # *seconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). + # *seconds* elapsed since the Unix epoch (`1970-01-01 00:00:00Z`). # # The time zone is always UTC. # # ``` - # Time.unix(981173106) # => 2001-02-03T04:05:06Z + # Time.unix(981173106) # => 2001-02-03 04:05:06Z # ``` def self.unix(seconds : Int) : Time utc(seconds: UNIX_EPOCH.total_seconds + seconds, nanoseconds: 0) end # Creates a new `Time` instance that corresponds to the number of - # *milliseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). + # *milliseconds* elapsed since the Unix epoch (`1970-01-01 00:00:00Z`). # # The time zone is always UTC. # # ``` - # time = Time.unix_ms(981173106789) # => 2001-02-03T04:05:06.789Z + # time = Time.unix_ms(981173106789) # => 2001-02-03 04:05:06.789Z # time.millisecond # => 789 # ``` def self.unix_ms(milliseconds : Int) : Time @@ -540,12 +540,12 @@ struct Time end # Creates a new `Time` instance that corresponds to the number of - # *nanoseconds* elapsed since the Unix epoch (`1970-01-01T00:00:00Z`). + # *nanoseconds* elapsed since the Unix epoch (`1970-01-01 00:00:00Z`). # # The time zone is always UTC. # # ``` - # time = Time.unix_ns(981173106789479273) # => 2001-02-03T04:05:06.789479273Z + # time = Time.unix_ns(981173106789479273) # => 2001-02-03 04:05:06.789479273Z # time.nanosecond # => 789479273 # ``` def self.unix_ns(nanoseconds : Int) : Time @@ -566,8 +566,8 @@ struct Time # new_year = Time.utc(2019, 1, 1, 0, 0, 0) # tokyo = new_year.to_local_in(Time::Location.load("Asia/Tokyo")) # new_york = new_year.to_local_in(Time::Location.load("America/New_York")) - # tokyo.inspect # => "2019-01-01T00:00:00+09:00[Asia/Tokyo]" - # new_york.inspect # => "2019-01-01T00:00:00-05:00[America/New_York]" + # tokyo.inspect # => "2019-01-01 00:00:00+09:00[Asia/Tokyo]" + # new_york.inspect # => "2019-01-01 00:00:00-05:00[America/New_York]" # ``` def to_local_in(location : Location) : Time local_seconds = offset_seconds @@ -1028,8 +1028,8 @@ struct Time # time_de == time_ar # => true # # # both times represent the same instant: - # time_de.to_utc # => 2018-03-08T21:05:13Z - # time_ar.to_utc # => 2018-03-08T21:05:13Z + # time_de.to_utc # => 2018-03-08 21:05:13Z + # time_ar.to_utc # => 2018-03-08 21:05:13Z # ``` def ==(other : Time) : Bool total_seconds == other.total_seconds && nanosecond == other.nanosecond @@ -1088,7 +1088,7 @@ struct Time def inspect(io : IO, with_nanoseconds = true) : Nil formatter = Format::Formatter.new(self, io) formatter.year_month_day - io << "T" + io << " " formatter.twenty_four_hour_time_with_seconds if with_nanoseconds && !@nanoseconds.zero? @@ -1138,7 +1138,7 @@ struct Time # Format this time using the format specified by [RFC 3339](https://tools.ietf.org/html/rfc3339) ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time.utc(2016, 2, 15).to_rfc3339 # => "2016-02-15T00:00:00Z" + # Time.utc(2016, 2, 15).to_rfc3339 # => "2016-02-15 00:00:00Z" # ``` # # ISO 8601 allows some freedom over the syntax and RFC 3339 exercises that @@ -1164,7 +1164,7 @@ struct Time # Parse time format specified by [RFC 3339](https://tools.ietf.org/html/rfc3339) ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z + # Time.parse_rfc3339("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z # ``` def self.parse_rfc3339(time : String) : self Format::RFC_3339.parse(time) @@ -1178,7 +1178,7 @@ struct Time # Use `#to_rfc3339` to format a `Time` according to . # # ``` - # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z + # Time.parse_iso8601("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z # ``` def self.parse_iso8601(time : String) Format::ISO_8601_DATE_TIME.parse(time) @@ -1208,7 +1208,7 @@ struct Time # This is also compatible to [RFC 882](https://tools.ietf.org/html/rfc882) and [RFC 1123](https://tools.ietf.org/html/rfc1123#page-55). # # ``` - # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50Z + # Time.parse_rfc2822("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15 04:35:50Z # ``` def self.parse_rfc2822(time : String) : self Format::RFC_2822.parse(time) @@ -1219,7 +1219,7 @@ struct Time # See `Time::Format` for details. # # ``` - # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05T00:00:00+02:00[Europe/Berlin] + # Time.parse("2016-04-05", "%F", Time::Location.load("Europe/Berlin")) # => 2016-04-05 00:00:00+02:00[Europe/Berlin] # ``` # # If there is no time zone information in the formatted time, *location* will @@ -1277,7 +1277,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01T00:00:00Z`). + # (`1970-01-01 00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5) @@ -1288,7 +1288,7 @@ struct Time end # Returns the number of milliseconds since the Unix epoch - # (`1970-01-01T00:00:00Z`). + # (`1970-01-01 00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) @@ -1299,7 +1299,7 @@ struct Time end # Returns the number of nanoseconds since the Unix epoch - # (`1970-01-01T00:00:00Z`). + # (`1970-01-01 00:00:00Z`). # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_910_123) @@ -1310,7 +1310,7 @@ struct Time end # Returns the number of seconds since the Unix epoch - # (`1970-01-01T00:00:00Z`) as `Float64` with nanosecond precision. + # (`1970-01-01 00:00:00Z`) as `Float64` with nanosecond precision. # # ``` # time = Time.utc(2016, 1, 12, 3, 4, 5, nanosecond: 678_000_000) @@ -1333,8 +1333,8 @@ struct Time # ``` # time_de = Time.local(2018, 3, 8, 22, 5, 13, location: Time::Location.load("Europe/Berlin")) # time_ar = time_de.in Time::Location.load("America/Buenos_Aires") - # time_de # => 2018-03-08T22:05:13+01:00[Europe/Berlin] - # time_ar # => 2018-03-08T18:05:13-03:00[America/Buenos_Aires] + # time_de # => 2018-03-08 22:05:13+01:00[Europe/Berlin] + # time_ar # => 2018-03-08 18:05:13-03:00[America/Buenos_Aires] # ``` # # In contrast, `#to_local_in` changes to a different location while @@ -1418,9 +1418,9 @@ struct Time # # ``` # now = Time.utc(2023, 5, 16, 17, 53, 22) - # now.at_beginning_of_week # => 2023-05-15T00:00:00Z - # now.at_beginning_of_week(:sunday) # => 2023-05-14T00:00:00Z - # now.at_beginning_of_week(:wednesday) # => 2023-05-10T00:00:00Z + # now.at_beginning_of_week # => 2023-05-15 00:00:00Z + # now.at_beginning_of_week(:sunday) # => 2023-05-14 00:00:00Z + # now.at_beginning_of_week(:wednesday) # => 2023-05-10 00:00:00Z # ``` # TODO: Ensure correctness in local time-line. def at_beginning_of_week(start_day : Time::DayOfWeek = :monday) : Time diff --git a/src/time/format/custom/http_date.cr b/src/time/format/custom/http_date.cr index 2b82b3675515..dd2268f5b760 100644 --- a/src/time/format/custom/http_date.cr +++ b/src/time/format/custom/http_date.cr @@ -10,10 +10,10 @@ struct Time::Format # * [asctime](http://en.cppreference.com/w/c/chrono/asctime) # # ``` - # Time::Format::HTTP_DATE.parse("Sun, 14 Feb 2016 21:00:00 GMT") # => 2016-02-14T21:00:00Z - # Time::Format::HTTP_DATE.parse("Sunday, 14-Feb-16 21:00:00 GMT") # => 2016-02-14T21:00:00Z - # Time::Format::HTTP_DATE.parse("Sun, 14-Feb-2016 21:00:00 GMT") # => 2016-02-14T21:00:00Z - # Time::Format::HTTP_DATE.parse("Sun Feb 14 21:00:00 2016") # => 2016-02-14T21:00:00Z + # Time::Format::HTTP_DATE.parse("Sun, 14 Feb 2016 21:00:00 GMT") # => 2016-02-14 21:00:00Z + # Time::Format::HTTP_DATE.parse("Sunday, 14-Feb-16 21:00:00 GMT") # => 2016-02-14 21:00:00Z + # Time::Format::HTTP_DATE.parse("Sun, 14-Feb-2016 21:00:00 GMT") # => 2016-02-14 21:00:00Z + # Time::Format::HTTP_DATE.parse("Sun Feb 14 21:00:00 2016") # => 2016-02-14 21:00:00Z # # Time::Format::HTTP_DATE.format(Time.utc(2016, 2, 15)) # => "Mon, 15 Feb 2016 00:00:00 GMT" # ``` diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr index 58bb4c4c32fb..3551992fed95 100644 --- a/src/time/format/custom/iso_8601.cr +++ b/src/time/format/custom/iso_8601.cr @@ -118,7 +118,7 @@ struct Time::Format # The ISO 8601 date format. # # ``` - # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15T00:00:00Z + # Time::Format::ISO_8601_DATE.parse("2016-02-15") # => 2016-02-15 00:00:00Z # Time::Format::ISO_8601_DATE.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15" # ``` module ISO_8601_DATE @@ -147,8 +147,8 @@ struct Time::Format # The ISO 8601 date time format. # # ``` - # Time::Format::ISO_8601_DATE_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" - # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z + # Time::Format::ISO_8601_DATE_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15 04:35:50Z" + # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z # ``` module ISO_8601_DATE_TIME # Parses a string into a `Time`. @@ -177,7 +177,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "04:35:50Z" - # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01T04:35:50Z + # Time::Format::ISO_8601_TIME.parse("04:35:50Z") # => 0001-01-01 04:35:50Z # ``` module ISO_8601_TIME # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_2822.cr b/src/time/format/custom/rfc_2822.cr index b23678823b1d..43236d616fd1 100644 --- a/src/time/format/custom/rfc_2822.cr +++ b/src/time/format/custom/rfc_2822.cr @@ -7,7 +7,7 @@ struct Time::Format # Time::Format::RFC_2822.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "Mon, 15 Feb 2016 04:35:50 +0000" # # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 +0000") # => 2016-02-15 04:35:50.0 +00:00 - # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15T04:35:50Z + # Time::Format::RFC_2822.parse("Mon, 15 Feb 2016 04:35:50 UTC") # => 2016-02-15 04:35:50Z # ``` module RFC_2822 # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_3339.cr b/src/time/format/custom/rfc_3339.cr index ae298f4e0496..6b27fc00f6f2 100644 --- a/src/time/format/custom/rfc_3339.cr +++ b/src/time/format/custom/rfc_3339.cr @@ -2,10 +2,10 @@ struct Time::Format # The [RFC 3339](https://tools.ietf.org/html/rfc3339) datetime format ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" + # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15 04:35:50Z" # - # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15T04:35:50Z - # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15T04:35:50Z + # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z + # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z # ``` module RFC_3339 # Parses a string into a `Time`. diff --git a/src/time/location.cr b/src/time/location.cr index efa3f418ed92..4c56db2bbf0b 100644 --- a/src/time/location.cr +++ b/src/time/location.cr @@ -18,7 +18,7 @@ require "./location/loader" # location = Time::Location.load("Europe/Berlin") # location # => # # time = Time.local(2016, 2, 15, 21, 1, 10, location: location) -# time # => 2016-02-15T21:01:10+01:00[Europe/Berlin] +# time # => 2016-02-15 21:01:10+01:00[Europe/Berlin] # ``` # # A custom time zone database can be configured through the environment variable @@ -447,7 +447,7 @@ class Time::Location # Returns the time zone offset observed at *unix_seconds*. # # *unix_seconds* expresses the number of seconds since UNIX epoch - # (`1970-01-01T00:00:00Z`). + # (`1970-01-01 00:00:00Z`). def lookup(unix_seconds : Int) : Zone unless @cached_range[0] <= unix_seconds < @cached_range[1] @cached_zone, @cached_range = lookup_with_boundaries(unix_seconds) diff --git a/src/yaml/to_yaml.cr b/src/yaml/to_yaml.cr index b7dcb6d91b4e..949e81b7db46 100644 --- a/src/yaml/to_yaml.cr +++ b/src/yaml/to_yaml.cr @@ -276,7 +276,7 @@ end # end # # timestamp = Timestamp.from_yaml(%({"values":[1459859781,1567628762]})) -# timestamp.values # => [2016-04-05T12:36:21Z, 2019-09-04T20:26:02Z] +# timestamp.values # => [2016-04-05 12:36:21Z, 2019-09-04 20:26:02Z] # timestamp.to_yaml # => "---\nvalues:\n- 1459859781\n- 1567628762\n" # ``` # @@ -294,7 +294,7 @@ end # end # # timestamp = Timestamp.from_yaml(%({"values":["Apr 5, 2016","Sep 4, 2019"]})) -# timestamp.values # => [2016-04-05T00:00:00Z, 2019-09-04T00:00:00Z] +# timestamp.values # => [2016-04-05 00:00:00Z, 2019-09-04 00:00:00Z] # timestamp.to_yaml # => "---\nvalues:\n- Apr 5, 2016\n- Sep 4, 2019\n" # ``` # From 650635a4e3db0bbb4dc86d7af8458bde0defa837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 10:20:07 +0200 Subject: [PATCH 07/11] Add documentation --- src/time.cr | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/time.cr b/src/time.cr index 036859fc5199..4d3813981fce 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1080,11 +1080,26 @@ struct Time # 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`. + # It's formatted according to the Internet Extended Date/Time Format (IXDTF) + # as specified in [RFC 9557](https://datatracker.ietf.org/doc/html/rfc9557): + # An [RFC 3339](https://tools.ietf.org/html/rfc3339) formatted local date-time + # string with nanosecond precision followed by a time zone name suffix. # - # The name of the location is appended unless it is a fixed zone offset. + # It is similar to the format `%FT%T.%N%::z[%Z]`. Some parts may be omitted or + # shortened. + # + # Nanoseconds are omitted if *with_nanoseconds* is `false` or `nanoseconds` + # are zero. Zero offset seconds are omitted. The name of the location is + # omitted for fixed zone offset. + # + # ``` + # Time.utc(2014, 1, 2, 3, 4, 5) # => 2014-01-02 03:04:05Z + # Time.utc(2014, 1, 2, 3, 4, 5, nanosecond: 123_456) # => 2014-01-02 03:04:05.123456000Z + # + # Time.local(2014, 1, 2, 3, 4, 5, location: Time::Location.load("Europe/Berlin")) # => 2014-01-02 03:04:05+01:00[Europe/Berlin] + # Time.local(2014, 1, 2, 3, 4, 5, location: Time::Location.fixed(3600)) # => 2014-01-02 03:04:05+01:00 + # Time.local(2014, 1, 2, 3, 4, 5, location: Time::Location.fixed(3601)) # => 2014-01-02 03:04:05+01:00:01 + # ``` def inspect(io : IO, with_nanoseconds = true) : Nil formatter = Format::Formatter.new(self, io) formatter.year_month_day From 8d78843c135bc3d4db9da92196ee34bff3cd2d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 10:20:20 +0200 Subject: [PATCH 08/11] Simplify implementation with `Format::RFC_3339` --- src/time.cr | 12 +----------- src/time/format/custom/rfc_3339.cr | 14 +++++++------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/time.cr b/src/time.cr index 4d3813981fce..bbc38446fd18 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1101,17 +1101,7 @@ struct Time # Time.local(2014, 1, 2, 3, 4, 5, location: Time::Location.fixed(3601)) # => 2014-01-02 03:04:05+01:00:01 # ``` def inspect(io : IO, with_nanoseconds = true) : Nil - formatter = Format::Formatter.new(self, io) - formatter.year_month_day - io << " " - formatter.twenty_four_hour_time_with_seconds - - if with_nanoseconds && !@nanoseconds.zero? - io << "." - formatter.nanoseconds - end - - formatter.time_zone_z_or_offset(force_colon: true, format_seconds: :auto) + Format::RFC_3339.format(self, io, fraction_digits: (with_nanoseconds && !nanosecond.zero?) ? 9 : 0, preferred_separator: ' ') io << '[' << location.name << ']' unless location.fixed? end diff --git a/src/time/format/custom/rfc_3339.cr b/src/time/format/custom/rfc_3339.cr index 6b27fc00f6f2..dfe0eeabecc8 100644 --- a/src/time/format/custom/rfc_3339.cr +++ b/src/time/format/custom/rfc_3339.cr @@ -16,27 +16,27 @@ struct Time::Format end # Formats a `Time` into the given *io*. - def self.format(time : Time, io : IO, fraction_digits = 0) + def self.format(time : Time, io : IO, fraction_digits = 0, *, preferred_separator = 'T') formatter = Formatter.new(time, io) - formatter.rfc_3339(fraction_digits: fraction_digits) + formatter.rfc_3339(fraction_digits: fraction_digits, preferred_separator: preferred_separator) io end # Formats a `Time` into a `String`. - def self.format(time : Time, fraction_digits = 0) : String + def self.format(time : Time, fraction_digits = 0, *, preferred_separator = 'T') : String String.build do |io| - format(time, io, fraction_digits: fraction_digits) + format(time, io, fraction_digits: fraction_digits, preferred_separator: preferred_separator) end end end module Pattern - def rfc_3339(fraction_digits = 0) + def rfc_3339(fraction_digits = 0, preferred_separator = 'T') year_month_day - char 'T', 't', ' ' + char preferred_separator, 'T', 't', ' ' twenty_four_hour_time_with_seconds second_fraction?(fraction_digits: fraction_digits) - time_zone_z_or_offset(force_colon: true) + time_zone_z_or_offset(force_colon: true, format_seconds: :auto) end end end From ec03b577adc87b917365d831fe42ed1453ba326e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 13:54:26 +0200 Subject: [PATCH 09/11] Apply suggestions from code review Co-authored-by: Julien Portalier --- src/time.cr | 4 ++-- src/time/format/custom/iso_8601.cr | 2 +- src/time/format/custom/rfc_3339.cr | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/time.cr b/src/time.cr index bbc38446fd18..c437f4b2fcad 100644 --- a/src/time.cr +++ b/src/time.cr @@ -1169,7 +1169,7 @@ struct Time # Parse time format specified by [RFC 3339](https://tools.ietf.org/html/rfc3339) ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time.parse_rfc3339("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z + # Time.parse_rfc3339("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50Z # ``` def self.parse_rfc3339(time : String) : self Format::RFC_3339.parse(time) @@ -1183,7 +1183,7 @@ struct Time # Use `#to_rfc3339` to format a `Time` according to . # # ``` - # Time.parse_iso8601("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z + # Time.parse_iso8601("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50Z # ``` def self.parse_iso8601(time : String) Format::ISO_8601_DATE_TIME.parse(time) diff --git a/src/time/format/custom/iso_8601.cr b/src/time/format/custom/iso_8601.cr index 3551992fed95..8da6ce6b06ac 100644 --- a/src/time/format/custom/iso_8601.cr +++ b/src/time/format/custom/iso_8601.cr @@ -148,7 +148,7 @@ struct Time::Format # # ``` # Time::Format::ISO_8601_DATE_TIME.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15 04:35:50Z" - # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z + # Time::Format::ISO_8601_DATE_TIME.parse("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50Z # ``` module ISO_8601_DATE_TIME # Parses a string into a `Time`. diff --git a/src/time/format/custom/rfc_3339.cr b/src/time/format/custom/rfc_3339.cr index dfe0eeabecc8..37b35c780797 100644 --- a/src/time/format/custom/rfc_3339.cr +++ b/src/time/format/custom/rfc_3339.cr @@ -2,9 +2,9 @@ struct Time::Format # The [RFC 3339](https://tools.ietf.org/html/rfc3339) datetime format ([ISO 8601](https://web.archive.org/web/20250306154328/http://xml.coverpages.org/ISO-FDIS-8601.pdf) profile). # # ``` - # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15 04:35:50Z" + # Time::Format::RFC_3339.format(Time.utc(2016, 2, 15, 4, 35, 50)) # => "2016-02-15T04:35:50Z" # - # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z + # Time::Format::RFC_3339.parse("2016-02-15T04:35:50Z") # => 2016-02-15 04:35:50Z # Time::Format::RFC_3339.parse("2016-02-15 04:35:50Z") # => 2016-02-15 04:35:50Z # ``` module RFC_3339 From 9ad546aaece174fc1ef87189b04d979c8ea684e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 4 Aug 2025 13:55:02 +0200 Subject: [PATCH 10/11] Revert unintended changes in log format --- src/log/format.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/log/format.cr b/src/log/format.cr index 34285c2e6ff4..d3039ab4ffc6 100644 --- a/src/log/format.cr +++ b/src/log/format.cr @@ -184,17 +184,17 @@ end # # It writes log entries with the following format: # ``` -# 2020-05-07 17:40:07.994508000Z INFO - my.source: Initializing everything +# 2020-05-07T17:40:07.994508000Z INFO - my.source: Initializing everything # ``` # # When the entries have context data it's also written to the output: # ``` -# 2020-05-07 17:40:07.994508000Z INFO - my.source: Initializing everything -- {"data" => 123} +# 2020-05-07T17:40:07.994508000Z INFO - my.source: Initializing everything -- {"data" => 123} # ``` # # Exceptions are written in a separate line: # ``` -# 2020-05-07 17:40:07.994508000Z ERROR - my.source: Something failed +# 2020-05-07T17:40:07.994508000Z ERROR - my.source: Something failed # Oh, no (Exception) # from ... # ``` From b9ef13a3b22b57b20a8c30dd83b6efd225c4905c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Wed, 6 Aug 2025 14:21:29 +0200 Subject: [PATCH 11/11] Adapt `Time#inspect` format --- src/time.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/time.cr b/src/time.cr index cf5e25834382..a457f425bf90 100644 --- a/src/time.cr +++ b/src/time.cr @@ -582,10 +582,10 @@ struct Time # nyc = Time::Location.load("America/New_York") # # # gap on 2025-03-09 local time: 02:00:00 STD -> 03:00:00 DST - # Time.utc(2025, 3, 9, 2, 12, 34).to_local_in(nyc) # => 2025-03-09 01:12:34.0 -05:00 America/New_York + # Time.utc(2025, 3, 9, 2, 12, 34).to_local_in(nyc) # => 2025-03-09 01:12:34.0-05:00[America/New_York] # # # overlap on 2025-11-02 local time: 02:00:00 DST -> 01:00:00 STD - # Time.utc(2025, 11, 2, 1, 12, 34).to_local_in(nyc) # => 2025-11-02 01:12:34.0 -04:00 America/New_York + # Time.utc(2025, 11, 2, 1, 12, 34).to_local_in(nyc) # => 2025-11-02 01:12:34.0-04:00[America/New_York] # ``` def to_local_in(location : Location) : Time local_seconds = offset_seconds