struct Time

Overview

Time represents a date-time instant in incremental time observed in a specific time zone.

The calendaric calculations are based on the rules of the proleptic Gregorian calendar as specified in ISO 8601. 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 nanosecond-of-second with value range 0..999_999_999.

The supported date range is 0001-01-01 00:00:00.0 to 9999-12-31 23:59:59.999_999_999 in any local time zone.

Telling the Time

There are several methods to retrieve a Time instance representing the current time:

Time.utc                                        # returns the current time in UTC
Time.local Time::Location.load("Europe/Berlin") # returns the current time in time zone Europe/Berlin
Time.local                                      # returns the current time in current time zone

It is generally recommended to keep instances in UTC and only apply a local time zone when formatting for user display, unless the domain logic requires having a specific time zone (for example for calendaric operations).

Creating a Specific Instant

Time instances representing a specific instant can be created by .utc or .new with the date-time specified as individual arguments:

time = Time.utc(2016, 2, 15, 10, 20, 30)
time.to_s # => "2016-02-15 10:20:30 UTC"
time = Time.local(2016, 2, 15, 10, 20, 30, location: Time::Location.load("Europe/Berlin"))
time.to_s # => "2016-02-15 10:20:30 +01:00"
# The time-of-day can be omitted and defaults to midnight (start of day):
time = Time.utc(2016, 2, 15)
time.to_s # => "2016-02-15 00:00:00 UTC"

Retrieving Time Information

Each Time instance allows querying calendar data:

time = Time.utc(2016, 2, 15, 10, 20, 30)
time.year        # => 2016
time.month       # => 2
time.day         # => 15
time.hour        # => 10
time.minute      # => 20
time.second      # => 30
time.millisecond # => 0
time.nanosecond  # => 0
time.day_of_week # => Time::DayOfWeek::Monday
time.day_of_year # => 46
time.monday?     # => true
time.time_of_day # => 10:20:30

For querying if a time is at a specific day of week, Time offers named predicate methods, delegating to #day_of_week:

time.monday? # => true
# ...
time.sunday? # => false

Time Zones

Each time is attached to a specific time zone, represented by a Location (see #location). #zone returns the time zone observed in this location at the current time (i.e. the instant represented by this Time). #offset returns the offset of the current zone in seconds.

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.location # => #<Time::Location Europe/Berlin>
time.zone     # => #<Time::Location::Zone CET +01:00 (3600s) STD>
time.offset   # => 3600

Using .utc, the location is Time::Location::UTC:

time = Time.utc(2018, 3, 8, 22, 5, 13)
time          # => 2018-03-08 22:05:13.0 UTC
time.location # => #<Time::Location UTC>
time.zone     # => #<Time::Location::Zone UTC +00:00 (0s) STD>
time.offset   # => 0

A Time instance can be transformed to a different time zone while retaining the same instant using #in:

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

Both Time instances show a different local date-time, but they represent the same date-time in the instant time-line, therefore they are considered 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 == time_ar # => true

There are also two special methods for converting to UTC and local time zone:

time.to_utc   # equals time.in(Location::UTC)
time.to_local # equals time.in(Location.local)

#to_local_in allows changing the time zone while keeping the same local date-time (wall clock) which results in a different instant on the time line.

Formatting and Parsing Time

To make date-time instances exchangeable between different computer systems or readable to humans, they are usually converted to and from a string representation.

The method #to_s formats the date-time according to a specified pattern.

time = Time.utc(2015, 10, 12, 10, 30, 0)
time.to_s("%Y-%m-%d %H:%M:%S %:z") # => "2015-10-12 10:30:00 +00:00"

Similarly, Time.parse and Time.parse! are used to construct a Time instance from date-time information in a string, according to a specified pattern:

Time.parse("2015-10-12 10:30:00 +00:00", "%Y-%m-%d %H:%M:%S %z", Time::Location::UTC)
Time.parse!("2015-10-12 10:30:00 +00:00", "%Y-%m-%d %H:%M:%S %z")

See Time::Format for all directives.

Calculations

Time.utc(2015, 10, 10) - 5.days # => 2015-10-05 00:00:00 +00:00

span = Time.utc(2015, 10, 10) - Time.utc(2015, 9, 10)
span.days          # => 30
span.total_hours   # => 720
span.total_minutes # => 43200

Measuring Time

The typical time representation provided by the operating system is based on a "wall clock" which is subject to changes for clock synchronization. This can result in discontinuous jumps in the time-line making it not suitable for accurately measuring elapsed time.

Instances of Time are focused on telling time – using a "wall clock". When Time.local is called multiple times, the difference between the returned instances is not guaranteed to equal to the time elapsed between making the calls; even the order of the returned Time instances might not reflect invocation order.

t1 = Time.utc
# operation that takes 1 minute
t2 = Time.utc
t2 - t1 # => ?

The resulting Time::Span could be anything, even negative, if the computer's wall clock has changed between both calls.

As an alternative, the operating system also provides a monotonic clock. Its time-line has no specified starting point but is strictly linearly increasing.

This monotonic clock should always be used for measuring elapsed time.

A reading from this clock can be taken using .monotonic:

t1 = Time.monotonic
# operation that takes 1 minute
t2 = Time.monotonic
t2 - t1 # => 1.minute (approximately)

The execution time of a block can be measured using .measure:

elapsed_time = Time.measure do
  # operation that takes 20 milliseconds
end
elapsed_time # => 20.milliseconds (approximately)

Included Modules

Defined in:

time-ext.cr

Instance methods inherited from module Time::KHExt

internal_nanoseconds internal_nanoseconds, internal_seconds internal_seconds, nanoseconds_since_unix_epoch nanoseconds_since_unix_epoch