iyi

Standard library, 0.18.0

std/time

Time, Time::Span and Time::Location: the wall clock, the monotonic clock, time zones, civil-calendar arithmetic, strftime-style formatting and parsing (Time::Format), and the named formats: RFC 3339, ISO 8601, RFC 2822 and the HTTP date of RFC 9110.

import std/time to use it. Source: src/std/time.iyi at v0.18.0.

A language with no clock cannot log, measure, or expire anything. This module ports Crystal's core time types while adhering to the dependency floor (SPEC.md III.9, III.10):

* On Darwin, libSystem's `clock_gettime_nsec_np` provides wall clock and
  monotonic readings with zero new symbols beyond what concurrency already links.
* On Linux, raw `clock_gettime` syscalls (228 on x86_64, 113 on aarch64)
  issue direct kernel requests with zero libc symbols.
* On Windows, kernel32's `GetSystemTimePreciseAsFileTime` and `QueryPerformanceCounter`
  provide nanosecond-scale wall and monotonic time.
* On wasm32-wasi, WASI import `__wasi_clock_time_get` provides both clocks.

A Time is an instant and the Location it is read in, UTC unless it was made in another: Time.utc and Time.unix are UTC, Time.local the machine's zone, time.in(location) the same instant elsewhere. Its date, clock, to_s, to_rfc3339 and %Z are read in its location; ==, <=> and hash are the instant's. A Location is read from the system's own IANA database (TZif files, RFC 8536), so no zone data ships in the binary; see Location for where it looks. An offset passed by hand (to_s(format, offset), Format.new(pattern, offset)) still writes and reads at that offset, as before locations existed.

What was left out from Crystal's 1,626-line src/time.cr:

* `Time::MonthSpan`: `shift(years:, months:)` is the month arithmetic, with
  Crystal's rule for a day the month does not have.
* Localised names: month and day names are English, as Crystal's are.
* A zone read from a `%Z` name: an abbreviation names no single zone,
  so `%Z` reads UTC and offsets, and a text in a known zone is parsed
  at its `Location`.
* Windows' own zone names: the database is not there to read, so a
  Windows machine's local zone is its rule from the system, its zones
  named by their offsets (`+03`), and `Location.load` reads `$ZONEINFO`.

DayOfWeekstruct

std/timeiyi doc src/std/time.iyi
pub struct DayOfWeek
  def ==(other : DayOfWeek) : Bool
  def ===(other : DayOfWeek) : Bool
  def friday? : Bool
  # Hashing follows `==`. Without it a day hashed to its type, `Object#hash`'s
  # default: `Monday.hash == Sunday.hash`, and every day a table held went to
  # one slot.
  def hash : Int32
  def initialize(value : Int32)
  def monday? : Bool
  def saturday? : Bool
  def sunday? : Bool
  def thursday? : Bool
  def to_s : String
  def tuesday? : Bool
  def value : Int32
  def wednesday? : Bool
end

Locationclass

A time zone: the offsets from UTC a place has kept, and when it changed from one to the next. Location.load("Europe/Istanbul") reads the IANA database the machine has; Location.local is the machine's own zone; Location.fixed(19800) is an offset and nothing else; Location::UTC is UTC. A Time carries one, and its date and clock are read in it.

The database is the system's TZif files, RFC 8536: $ZONEINFO when it names a directory, then /usr/share/zoneinfo, /usr/share/lib/zoneinfo and /etc/zoneinfo. Versions 1 to 4 are read. A time before the first change has the file's first offset, and one after the last has its POSIX footer's rule, so a slim file answers for the year 2100 as a fat one does. Leap seconds are not counted, as no clock this module reads counts them.

Location.local is the TZ variable when it is set — the empty string is UTC, a leading : is dropped, an absolute path is a TZif file, a name is looked up in the database, and anything else is read as a POSIX TZ string such as EST5EDT,M3.2.0,M11.1.0 — and /etc/localtime when it is not. Windows has no database to read: there local is the system's zone, its rule from GetTimeZoneInformation, its zones named by their offsets, and load reads $ZONEINFO alone. Where nothing answers, local is UTC. The local zone is read once, the first time it is asked for.

A location is built once and never changed, so a Time that holds one can cross to another task.

std/timeiyi doc src/std/time.iyi
pub class Location
  lib LibKernel32
  end
  # One offset a location keeps: its abbreviation (`CEST`, `+03`), its
  # seconds east of UTC, and whether it is summer time.
  struct Zone
    def ==(other : Zone) : Bool
    def dst? : Bool
    def initialize(name : String, offset : Int32, dst : Bool)
    def name : String
    def offset : Int32
    def to_s : String
  end
  def ==(other : Location) : Bool
  def self.fixed(name : String, offset : Int32) : Location
  # An offset of *offset* seconds east of UTC and nothing else, named as
  # the offset is written: `+05:30`. Less than a day either way.
  def self.fixed(offset : Int32) : Location
  # One offset, never changing.
  def fixed? : Bool
  def hash : Int32
  def initialize(name : String, zones : Array(Zone), transitions : Array(Int64), transition_zones : Array(Int32), rule : TzRule | ::Nil)
  def inspect : String
  # The zone the IANA database names *name*: `Europe/Istanbul`,
  # `America/New_York`. `UTC` and the empty name are UTC and `Local` is
  # `Location.local`, without a file. A name that climbs out of the database
  # (`..`, a leading `/`, a `\`) is refused, and so is one no directory has.
  def self.load(name : String) : Location | LocationError
  # The machine's zone; see the type's comment for where it comes from.
  def self.local : Location
  # The machine's own zone, the one `Location.local` answers.
  def local? : Bool
  # The zone *time* is in here.
  def lookup(time : Time) : Zone
  def lookup(unix_seconds : Int64) : Zone
  def name : String
  # A location from the bytes of a TZif file, RFC 8536, named *name*.
  # Anything that is not one is refused with what was wrong and where.
  def self.read_zoneinfo(name : String, data : String) : Location | LocationError
  # The instant a wall clock here reads *wall* (seconds since 1970 as a
  # UTC clock would count them). A wall time a change repeats is the
  # earlier of the two instants; one a change skips is read on the clock
  # from before the change, which lands it past the gap: 02:30 on the
  # morning summer starts in Berlin is 03:30 CEST. Python's `fold=0` and
  # Go read them the same way. `Time.local` and a parse at a location ask
  # this.
  def resolve(wall : Int64) : Int64
  def to_s : String
  def utc? : Bool
end

LocationErrorstruct

What Location.load and Location.read_zoneinfo answer when there is no zone by that name, or the bytes are not a zone: a name the program was handed or a file on the machine is not a bug in the program, so it is a value and not a panic.

std/timeiyi doc src/std/time.iyi
pub struct LocationError
  def initialize(message : String)
  def to_s : String
end

Spanstruct

std/timeiyi doc src/std/time.iyi
pub struct Span
  def +(other : Span) : Span
  def -(other : Span) : Span
  def - : Span
  def <(other : Span) : Bool
  def <=(other : Span) : Bool
  def <=>(other : Span) : Int32
  def ==(other : Span) : Bool
  def >(other : Span) : Bool
  def >=(other : Span) : Bool
  # The seconds are an Int64, and a span that leaves it is refused here,
  # by name: `Span.seconds(Int64::MAX) + Span.seconds(1)` panicked with the
  # prelude's "arithmetic overflow", a sentence that named no span. The
  # wrapping sum is compared with an addend to see whether it wrapped; the
  # product is bounded before it is taken.
  def self.add_seconds(a : Int64, b : Int64) : Int64
  def days : Int64
  def self.days(n : Int64) : Span
  # Hashing follows `==`, the two parts mixed as a tuple's members are. Without
  # it every span hashed to its type: `Span.seconds(1).hash ==
  # Span.days(9).hash`, and a table of 16,000 spans took 0.95 s to fill where
  # the same pairs as tuples took 1 ms.
  def hash : Int32
  def hours : Int32
  def self.hours(n : Int64) : Span
  def initialize(seconds : Int64 = 0_i64, nanoseconds : Int32 = 0)
  def millisecond : Int32
  def milliseconds : Int32
  def self.milliseconds(n : Int64) : Span
  def minutes : Int32
  def self.minutes(n : Int64) : Span
  def self.mul_seconds(n : Int64, unit : Int64) : Int64
  def nanosecond : Int32
  def nanoseconds : Int32
  def self.nanoseconds(n : Int64) : Span
  def self.new(*, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, milliseconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Span
  def seconds : Int32
  def self.seconds(n : Int64) : Span
  def self.sub_seconds(a : Int64, b : Int64) : Int64
  def to_i : Int64
  def to_s : String
  def total_days : Float64
  def total_hours : Float64
  def total_milliseconds : Float64
  def total_minutes : Float64
  def total_nanoseconds : Float64
  def total_seconds : Float64
  def total_seconds_i64 : Int64
  def self.zero : Span
end

Timestruct

std/timeiyi doc src/std/time.iyi
pub struct Time
  alias DayOfWeek = Std::Time::DayOfWeek
  # A pattern of Crystal's directives, for writing a `Time` and reading one
  # back. Text that is not a directive is written as it is and must be
  # there, byte for byte, when reading; ASCII whitespace in the pattern is
  # written as it is and reads any run of whitespace, none included.
  #
  #   %Y year, four digits or more, year -1 as -0001   %C year // 100
  #   %y year % 100   %G %g %V the ISO 8601 week year, its last two digits,
  #   and the week 01..53   %m month 01..12   %B January   %b %h Jan
  #   %d day 01..31   %e day " 1".."31"   %j day of the year 001..366
  #   %H hour 00..23  %k " 0".."23"  %I 01..12  %l " 1".."12"  %p am  %P AM
  #   %M minute   %S second   %L milliseconds   %3N %6N %9N the fraction to
  #   3, 6 and 9 digits   %N nanoseconds, any number of digits when read
  #   %A Sunday   %a Sun   %u day of the week, Monday 1..7   %w Sunday 0..6
  #   %s seconds since the epoch   %z +0530   %:z +05:30   %::z +05:30:00
  #   %Z and %^Z: UTC at offset 0 and the offset, +05:30, at any other
  #   %F %Y-%m-%d   %T %X %H:%M:%S   %R %H:%M   %D %x %m/%d/%y
  #   %r %I:%M:%S %P   %c %a %b %e %H:%M:%S %Y   %n newline   %t tab   %% %
  #
  # A number takes a flag between the `%` and its letter: `-` for no
  # padding (`%-d` is 5), `_` for spaces (`%_m` is " 5") and `0` for zeros
  # (`%0e` is 05); `^` upcases a name (`%^a` is SUN). The other library knows
  # `%-d`, `%-m`, `%_m` and the `^` names. The same flags on its other numbers
  # are this module's, where that library wrote the `%-` back out as text; a `%`
  # that starts no directive is text here as it is there.
  #
  # Reading takes up to a number's width in digits, fewer when the next byte
  # is not a digit, and a name in any case. `%Y` takes four digits when a
  # directive follows it (`%Y%m%d`), and when text or the end of the
  # pattern does (`%F`), an optional `-` and up to ten, so the years `to_s`
  # writes are the years it reads. A field outside its range, a day the
  # month does not have, a name that is not one and text after the pattern
  # are each a `TimeParseError`, never a panic. A day name is checked to be
  # one and not checked against the date, as Crystal, Go and Python read it.
  struct Format
    # The parts of an instant as they read at an offset, worked out once
    # per `format` rather than once per directive. *zone* is what `%Z`
    # writes, the location's abbreviation; with none it writes `UTC` at
    # offset 0 and the offset at any other.
    struct Fields
      def day : Int32
      def day_of_week : Int32
      def day_of_year : Int32
      def hour : Int32
      def initialize(time : Time, offset : Int32, zone : String | ::Nil)
      def minute : Int32
      def month : Int32
      def nanosecond : Int32
      def offset : Int32
      def second : Int32
      def unix : Int64
      def week : ::Tuple(Int64, Int32)
      def year : Int32
      def zone : String | ::Nil
    end
    # The HTTP date of RFC 9110 section 5.6.7. It is written as the
    # IMF-fixdate, always in GMT, `Sun, 06 Nov 1994 08:49:37 GMT`; it is
    # read in all three of the forms a recipient must accept: that one, the
    # obsolete RFC 850 `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's
    # `Sun Nov  6 08:49:37 1994`.
    module HTTP_DATE
      def self.format(time : Time) : String
      def self.parse(string : String) : Time | TimeParseError
    end
    # ISO 8601's calendar date, written `2016-02-15` as the date reads at
    # *offset* or in the time's own location; read extended or basic, and
    # as an ordinal or a week date, at *offset* since a date names no zone.
    module ISO_8601_DATE
      def self.format(time : Time, offset : Int32 | ::Nil = nil) : String
      def self.parse(string : String, offset : Int32 = 0) : Time | TimeParseError
    end
    # ISO 8601's date and time, written as RFC 3339 writes it and read with
    # every form of `ISO_8601_DATE` and `ISO_8601_TIME` joined by an
    # optional `T`. The zone is required, as Crystal requires it.
    module ISO_8601_DATE_TIME
      def self.format(time : Time, offset : Int32 | ::Nil = nil) : String
      def self.parse(string : String) : Time | TimeParseError
    end
    # ISO 8601's time of day and its zone, `04:35:50Z`; read as on
    # 0001-01-01, the day Crystal reads it on.
    module ISO_8601_TIME
      def self.format(time : Time, offset : Int32 | ::Nil = nil) : String
      def self.parse(string : String) : Time | TimeParseError
    end
    # Reads a text by a pattern or by one of the named grammars. Every step
    # answers whether it matched; the first that did not leaves its sentence
    # in `@error`, and `finish` answers that, so no text a program is handed
    # ends it in a panic.
    class Parser
      def accept(char : Char) : Bool
      def am_pm : Bool
      def at_end : Bool
      # RFC 5322's CFWS: whitespace and parenthesised comments, which nest
      # and in which `\` quotes the next byte. Answers whether there was any;
      # a comment left open is the error, and the parse fails on it.
      def cfws : Bool
      def day_name : Bool
      def self.digit?(b : UInt8) : Bool
      # Up to *max* digits and at least one. Nil, with the sentence, when
      # there are none, or when they are past what an Int64 holds.
      def digits(max : Int32, what : String) : Int64 | ::Nil
      def directive(letter : Char, flag : Char, digits : Int32, wide : Bool) : Bool
      # Exactly *count* digits, or nil, reading nothing and saying nothing:
      # the caller names what was missing.
      def exact(count : Int32) : Int32 | ::Nil
      def expect(char : Char) : Bool
      def fail(what : String) : Bool
      # A numeric directive's field, read as its flag and its padding say.
      def field(width : Int32, pad : Char, flag : Char, what : String) : Int32 | ::Nil
      # The instant the fields name, or the first sentence a step left. The
      # fields are checked the way `Time.utc` checks them, with RFC 3339's
      # allowance: `:60` is a leap second, read as the second after `:59`,
      # the instant it names on every clock that has none. A text that
      # names no zone is read at *default_offset*, or failing that on
      # *location*'s wall clock; with a location the time answered is in it.
      def finish(default_offset : Int32 | ::Nil, location : Location | ::Nil = nil) : Time | TimeParseError
      # A fraction of a second: up to *max* digits, scaled to nanoseconds,
      # and for `%N` any digits past the ninth read and dropped.
      def fraction(max : Int32, rest : Bool) : Bool
      # RFC 9110 section 5.6.7's three forms of an HTTP date: the
      # IMF-fixdate `Sun, 06 Nov 1994 08:49:37 GMT`, the obsolete RFC 850
      # `Sunday, 06-Nov-94 08:49:37 GMT`, and asctime's `Sun Nov  6 08:49:37
      # 1994`, which has no zone and is GMT. Crystal's reading: a day name,
      # short or long, in any of the three; the four-digit year IIS writes
      # in the RFC 850 shape; and the zones RFC 2822 names where RFC 9110
      # writes GMT.
      def http_date : Bool
      def initialize(input : String)
      # ISO 8601's dates, extended or basic: 2016-02-15 and 20160215, the
      # ordinal 2016-046 and 2016046, and the week date 2016-W07-1 and
      # 2016W071.
      def iso_date : Bool
      def iso_date_time : Bool
      # ISO 8601's time of day, extended or basic: 04:35:50 and 043550, a
      # fraction of the second after `.` or `,`, or the minutes or the hour
      # alone with a fraction of their own (04:35.5 is 04:35:30).
      def iso_time : Bool
      def self.letter?(b : UInt8) : Bool
      def literal(b : UInt8) : Bool
      def month_name(short : Bool) : Bool
      # A run of ASCII letters and the index in *names* of the name it
      # spells, in any case: for *short*, the first three letters of a name
      # and no more; otherwise the start of a name, three letters or longer,
      # as Crystal reads `%B` and `%A`.
      def name(names : Array(String), short : Bool, what : String) : Int32 | ::Nil
      def need_cfws : Bool
      def number(max : Int32, what : String) : Int32 | ::Nil
      def offset_number(allow_colon : Bool, force_colon : Bool, allow_seconds : Bool) : Bool
      # A number blank padded, as `%e` writes " 5": one space stands for a
      # digit.
      def padded_number(max : Int32, what : String) : Int32 | ::Nil
      def pattern(pattern : String) : Bool
      # The byte *ahead* of the current one, or 0 past the end, as Crystal's
      # reader answers '\0' there.
      def peek(ahead : Int32 = 0) : UInt8
      # RFC 9110's rule for a two-digit year (section 5.6.7): a year that
      # appears to be more than 50 years in the future is the most recent
      # past year with those last two digits. The candidate is taken in this
      # century and moved a century to land in the 100 years ending 50 years
      # from now, so the rule holds in 2090 too. The other library read 00..49 as 20xx
      # and the rest as 19xx, whatever the year it was read in.
      def self.recent_year(two_digits : Int64) : Int64
      # A sentence about fields that were all read, where no one byte is to
      # blame.
      def refuse(what : String) : TimeParseError
      # RFC 5322 section 3.3's date-time, with section 4.3's obsolete
      # two- and three-digit years and zone names, as Crystal's `RFC_2822`
      # reads it: `Mon, 15 Feb 2016 04:35:50 +0000`, the day name and the
      # seconds optional, comments anywhere whitespace goes.
      def rfc_2822 : Bool
      def same_letters?(start : Int32, name : String, size : Int32) : Bool
      def skip_spaces : Bool
      def self.space?(b : UInt8) : Bool
      def text(start : Int32, stop : Int32) : String
      # HH:MM:SS, as both HTTP and RFC 850 write the time of day.
      def time_of_day : Bool
      # One whitespace byte, as the HTTP grammars put between their parts.
      def whitespace : Bool
      # `%Y`: four digits when a directive follows it, and an optional `-`
      # and up to ten when text does, which is every year an Int32 holds.
      def year(wide : Bool) : Int64 | ::Nil
      # Exactly four digits, as the HTTP and ISO 8601 grammars write a year.
      def year4 : Bool
      # `%z`, `%:z` and `%::z` alike, as Crystal reads them: `Z`, `UTC`, or
      # an offset with or without its colons.
      def zone : Bool
      # `%Z`: an offset, or a name read up to the next whitespace. Only the
      # names of UTC itself are read: an abbreviation names no single zone
      # (CST is three of them), so any other is refused by name rather than
      # read as UTC or guessed at. A text whose zone is known is read at
      # that `Location` instead.
      def zone_name : Bool
      # RFC 5322's zone (section 3.3, and 4.3 for the obsolete names): an
      # offset of four digits, or a name. A military letter is read as
      # `-0000`, the offset of a time whose zone is not known, as section
      # 4.3 says to; a name the RFC does not give is refused, where Crystal
      # read it as UTC in silence. `UTC` is not the RFC's, and is read as
      # Crystal reads it.
      def zone_rfc2822 : Bool
      # ISO 8601's zone: `Z` or an offset.
      def zone_z_or_offset : Bool
    end
    # RFC 2822's date and time, `Mon, 15 Feb 2016 04:35:50 +0000`, as
    # Crystal writes it: the day of the month unpadded, the offset at the
    # end, the time's own unless another is given. RFC 2822 writes an offset
    # in whole minutes, so an offset with seconds is refused rather than
    # written as some other instant.
    module RFC_2822
      def self.format(time : Time, offset : Int32 | ::Nil = nil) : String
      def self.parse(string : String) : Time | TimeParseError
    end
    # RFC 3339, the module's own serialisation: `to_rfc3339` and
    # `parse_rfc3339` are this module's `format` and `parse`, and
    # `parse_rfc3339` panics with the sentence `parse` answers. Written at
    # *offset*, or at the time's own when it is whole minutes and in UTC
    # when it is not.
    module RFC_3339
      def self.format(time : Time, fraction_digits : Int32 = 0, offset : Int32 | ::Nil = nil) : String
      def self.parse(str : String) : Time | TimeParseError
    end
    # An offset is less than a day either side of UTC: the widest RFC 3339
    # and RFC 2822 write is 23:59, and a day or more is a date, not a zone.
    # An offset is the program's own, so a wrong one is a panic.
    def self.check_offset(offset : Int32) : Nil
    def format(time : Time, offset : Int32) : String
    def format(time : Time) : String
    # *offset*, seconds east of UTC, is the one a parse reads a text at when
    # the text names no zone, and the one `format` writes at. Nil, as for
    # Crystal's `Format.new(pattern)`, makes a text without a zone an error,
    # and `format` writes a time in its own location.
    def initialize(pattern : String, offset : Int32 | ::Nil = nil)
    # A text that names no zone is read on *location*'s wall clock, and
    # every time is read and written in *location*.
    def initialize(pattern : String, location : Location)
    # The directive at the `%` at byte *i* of *pattern*: the byte after it,
    # its letter, its flag, the colons of `%:z` and `%::z`, and the digits of
    # `%3N`. A `%` that starts no directive answers the letter '\0' and the
    # byte after the `%`: it is text, and so is what follows it, as Crystal
    # writes `%Q` back out as `%Q`.
    def self.lex(pattern : String, i : Int32) : ::Tuple(Int32, Char, Char, Int32, Int32)
    def location : Location | ::Nil
    def self.name(io : String::Builder, text : String, flag : Char) : Nil
    # A number padded to *width* with *pad*, '\0' for none, unless a flag
    # says otherwise. The sign of a negative year goes in front of the
    # padding, as `-0001`, the way `to_rfc3339` writes it.
    def self.number(io : String::Builder, value : Int64, width : Int32, pad : Char, flag : Char) : Nil
    def self.numeric?(letter : Char) : Bool
    def offset : Int32 | ::Nil
    # `+hh:mm` or `+hhmm`, and the seconds never (0), when there are any (1)
    # or always (2), the way Crystal's `Zone#format` writes an offset.
    def self.offset_text(offset : Int32, colon : Bool, seconds : Int32) : String
    def parse(string : String, offset : Int32 | ::Nil) : Time | TimeParseError
    def parse(string : String) : Time | TimeParseError
    def pattern : String
    def self.plain?(letter : Char) : Bool
    def self.textual?(letter : Char) : Bool
    def self.twelve(hour : Int32) : Int64
    def self.write(io : String::Builder, pattern : String, f : Fields) : Nil
    def self.write_directive(io : String::Builder, letter : Char, flag : Char, colons : Int32, digits : Int32, f : Fields) : Nil
  end
  lib LibKernel32
  end
  alias Location = Std::Time::Location
  alias Span = Std::Time::Span
  # The sum is checked before it is taken, so a span too long for the
  # seconds is refused as one; a sum that fits but leaves the years an
  # Int32 names is refused by the constructor. A span is exact: a day
  # added across the night summer begins is 24 hours, and the clock reads
  # an hour later than it did.
  def +(span : Span) : Time
  def -(span : Span) : Time
  def -(other : Time) : Span
  def <(other : Time) : Bool
  def <=(other : Time) : Bool
  def <=>(other : Time) : Int32
  def ==(other : Time) : Bool
  def >(other : Time) : Bool
  def >=(other : Time) : Bool
  # The ISO 8601 week-numbering year and week this day falls in, as
  # Crystal's `calendar_week` answers: 2021-01-01 is in week 53 of 2020.
  def calendar_week : ::Tuple(Int32, Int32)
  # Converts days since 1970-01-01 into {year, month, day}.
  def self.civil_from_days(z : Int64) : ::Tuple(Int32, Int32, Int32)
  def day : Int32
  def day_of_week : DayOfWeek
  def day_of_year : Int32
  def self.days_from_civil(y : Int64, m : Int32, d : Int32) : Int64
  def self.days_in_month(year : Int32, month : Int32) : Int32
  def self.days_in_year(year : Int32) : Int32
  # Number of days between 1970-01-01 and the given civil date.
  # Follows Howard Hinnant's Gregorian calendar algorithm.
  def self.floor_div(a : Int64, b : Int64) : Int64
  def self.floor_mod(a : Int64, b : Int64) : Int64
  def self.from_unix(seconds : Int64, nanoseconds : Int32 = 0) : Time
  def self.from_unix_ms(milliseconds : Int64) : Time
  # Hashing follows `==`, as `Span`'s does. Without it every time hashed to its
  # type: `Time.unix(1).hash == Time.unix(1000000, 5).hash`, and a table of
  # 16,000 times took 1.2 s to fill where the same pairs as tuples took 1 ms.
  def hash : Int32
  def hour : Int32
  # The same instant read in *location*: `Time.utc(2024, 7, 1, 12).in(berlin)`
  # is 14:00 CEST.
  def in(location : Location) : Time
  def initialize(seconds : Int64, nanoseconds : Int32 = 0, location : Location = Location::UTC)
  # As the other library writes it: the date and time, the nanoseconds
  # when there are any, and `Z`. There was no `inspect`, so it was `to_s`,
  # and `Time.utc(2024, 1, 1, 0, 0, 0, nanosecond: 5)` inspected as
  # `2024-01-01 00:00:00 UTC`, the same as `Time.utc(2024, 1, 1)`, which
  # `==` tells apart from it. Out of UTC the offset follows, and the
  # location's name when it is more than an offset:
  # `2016-02-15 11:20:30 +01:00 Europe/Berlin`.
  def inspect : String
  # The ISO 8601 week-numbering year and week of a date, from its year, day
  # of the month, day of the year and day of the week (Monday is 1). This is
  # Crystal's computation: the week is the count of Mondays up to the day,
  # and the first days of January or the last of December can belong to a
  # week of the year next to it. The year is an Int64 because the first days
  # of the smallest Int32 year belong to the year before it.
  def self.iso_week(year : Int64, day : Int32, day_of_year : Int32, day_of_week : Int32) : ::Tuple(Int64, Int32)
  def self.leap_year?(year : Int32) : Bool
  # The time a clock in *location* shows as the date and time given, the
  # machine's own zone when none is. Its fields are checked as `utc` checks
  # them. A wall time a change of offset repeats, the hour after summer
  # ends, is the earlier of the two; one a change skips, the hour summer
  # begins with, is read past the gap (see `Location#resolve`).
  def self.local(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0, location : Location = Location.local) : Time
  # Now, read in *location*.
  def self.local(location : Location) : Time
  # Now, in the machine's own zone (`Location.local`).
  def self.local : Time
  def local? : Bool
  # The zone the date and clock are read in, and its offset from UTC at
  # this instant in seconds east. `==`, `<=>` and `hash` are the instant's
  # and do not look at either: noon in London and 13:00 in Paris are the
  # same time.
  def location : Location
  def millisecond : Int32
  def milliseconds : Int32
  def minute : Int32
  def self.monotonic : Span
  def self.monotonic_nanoseconds : UInt64
  def month : Int32
  def nanosecond : Int32
  def nanoseconds : Int32
  def self.new(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time
  def offset : Int32
  def self.pad2(n : Int32) : String
  def self.pad3(n : Int32) : String
  # Four digits, and a negative year keeps its sign in front of them rather
  # than inside: written as `n < 10 ? "000" + n.to_s`, year -1 printed
  # `000-1-01-01`, which is neither a year nor a date. ISO 8601 spells it
  # `-0001`, and the conversions themselves were right all along - the epoch
  # round-trips through it.
  def self.pad4(n : Int32) : String
  def self.pad6(n : Int32) : String
  def self.pad9(n : Int32) : String
  # Reads *string* by *pattern*'s directives (see `Time::Format`). A text
  # that names no zone is read at *offset* seconds east of UTC, as Crystal's
  # `Time.parse` reads one at the location it is given.
  def self.parse(string : String, pattern : String, offset : Int32) : Time | TimeParseError
  # Reads *string* by *pattern* in *location*: a text that names no zone is
  # the wall clock there, its offset the one the location kept at that date
  # (a wall time a change repeats or skips is read as `Time.local` reads
  # it), and the time answered is in *location* whether the text named a
  # zone or not.
  def self.parse(string : String, pattern : String, location : Location) : Time | TimeParseError
  def self.parse_iso8601(string : String) : Time | TimeParseError
  # `parse` in the machine's own zone.
  def self.parse_local(string : String, pattern : String) : Time | TimeParseError
  # Any of RFC 9110's three HTTP date forms; see `Time::Format::HTTP_DATE`.
  def self.parse_rfc1123(string : String) : Time | TimeParseError
  def self.parse_rfc2822(string : String) : Time | TimeParseError
  # Parses an RFC 3339 / ISO 8601 string into a UTC Time, and panics with the
  # parser's sentence when it is not one. Handles forms like:
  #   2024-02-29T12:30:45Z
  #   2024-02-29T12:30:45.123Z
  #   2024-02-29T12:30:45.123456789Z
  #   2024-02-29 12:30:45Z
  #   2024-02-29T12:30:45+05:00
  #   2024-02-29T12:30:45-08:00
  #   -0001-01-01T00:00:00Z and 12345-06-07T08:09:10Z, as `to_rfc3339` writes them
  # `Time::Format::RFC_3339.parse` reads the same grammar and answers a
  # `TimeParseError` instead, for text the program did not write itself.
  def self.parse_rfc3339(str : String) : Time
  # `parse` at UTC when the text names no zone.
  def self.parse_utc(string : String, pattern : String) : Time | TimeParseError
  def second : Int32
  def seconds : Int64
  # Moves the date by calendar years and months, then by weeks and days,
  # then by the smaller units, in that order, as Crystal's `shift` does. A
  # day the month does not have becomes its last: `Time.utc(2024, 1,
  # 31).shift(months: 1)` is 2024-02-29. The date moves on the wall clock
  # of the time's location, so the time of day is kept across a change of
  # offset - a day after 12:00 on the Saturday summer begins is 12:00 on
  # Sunday, 23 hours later - and the hours, minutes, seconds and
  # nanoseconds are exact. A year past an Int32 is refused with the
  # constructor's sentence, and a span the Int64 seconds do not hold with
  # `Span`'s.
  def shift(*, years : Int32 = 0, months : Int32 = 0, weeks : Int64 = 0_i64, days : Int64 = 0_i64, hours : Int64 = 0_i64, minutes : Int64 = 0_i64, seconds : Int64 = 0_i64, nanoseconds : Int64 = 0_i64) : Time
  def to_local : Time
  # RFC 1123's date as HTTP writes it, RFC 9110's IMF-fixdate, always in GMT:
  # `Sun, 06 Nov 1994 08:49:37 GMT`.
  def to_rfc1123 : String
  # RFC 2822's date, `Mon, 15 Feb 2016 04:35:50 +0000`, at *offset*, or at
  # the time's own offset when none is given.
  def to_rfc2822(offset : Int32 | ::Nil = nil) : String
  # RFC 3339 at the time's own offset: `Z` at UTC, `+02:00` in CEST. An
  # offset RFC 3339 cannot write, one with seconds in it as local mean time
  # before standard time had, is written as the same instant in UTC.
  def to_rfc3339(fraction_digits : Int32 = 0) : String
  # The instant written as it reads at *offset* seconds east of UTC:
  # `to_s("%F %T %:z", 19800)` is the wall clock in India.
  def to_s(format : String, offset : Int32) : String
  # The time written by *format*'s directives (see `Time::Format`) as its
  # own location reads it; `%Z` is the zone's abbreviation, `CEST`.
  def to_s(format : String) : String
  # `2016-02-15 10:20:30 UTC`, and in any other location the offset in
  # place of `UTC`: `2016-02-15 11:20:30 +01:00`.
  def to_s : String
  def to_unix : Int64
  def to_unix_f : Float64
  # Refused with the module's sentence past the milliseconds an Int64
  # holds, some 292 million years either side of 1970, where the multiply
  # was the prelude's bare "arithmetic overflow" for a year `Time.utc`
  # accepts, 300,000,000. Both edges answer to the last millisecond that
  # fits: the lowest 808 an Int64 holds were refused, so
  # `Time.unix_ms(Int64::MIN).to_unix_ms` panicked. A negative second is
  # taken one up and its milliseconds from 1000 down, so the product fits
  # too.
  def to_unix_ms : Int64
  def to_utc : Time
  def self.unix(seconds : Int64, nanoseconds : Int32 = 0) : Time
  def self.unix_ms(milliseconds : Int64) : Time
  def self.utc(year : Int32, month : Int32, day : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, millisecond : Int32 = 0, nanosecond : Int32 = 0) : Time
  def self.utc : Time
  def utc? : Bool
  def self.wall_clock_nanoseconds : UInt64
  # The instant of a day of an ISO 8601 week, as Crystal's `week_date` makes
  # it: `Time.week_date(2016, 1, 1)` is Monday 2016-01-04. A week the year
  # does not have and a day past Sunday are refused, where the other library answered
  # a day of some other week.
  def self.week_date(year : Int32, week : Int32, day_of_week : Int32, hour : Int32 = 0, minute : Int32 = 0, second : Int32 = 0, nanosecond : Int32 = 0) : Time
  # Days since 1970-01-01 of a day of an ISO 8601 week. Week 1 is the week
  # holding January 4th, so its Monday is the Monday on or before that day.
  def self.week_date_days(year : Int32, week : Int32, day_of_week : Int32) : Int64
  # A year has 53 ISO 8601 weeks when it starts on a Thursday, or on a
  # Wednesday in a leap year: either way it has 53 Thursdays, and a week
  # belongs to the year that holds its Thursday.
  def self.weeks_in_year(year : Int32) : Int32
  def year : Int32
  # The zone this instant is in at its location: its abbreviation, offset
  # and whether it is summer time.
  def zone : Location::Zone
end

TimeParseErrorstruct

What Time::Format#parse and the named parsers answer when the text is not a time the pattern describes. Bad input is the caller's to handle, not a panic: a header or a log line from somewhere else is not a bug in the program reading it. message is the sentence, naming the byte the parser stood at and the text; position is that byte, counted from 0.

std/timeiyi doc src/std/time.iyi
pub struct TimeParseError
  def initialize(message : String, input : String, position : Int32)
  def input : String
  def position : Int32
  def to_s : String
end

Error for TimeParseErrorimpl

std/timeiyi doc src/std/time.iyi
impl Error for TimeParseError
  def message : String
end

Std::Traits::Comparable for Spanimpl

std/timeiyi doc src/std/time.iyi
impl Std::Traits::Comparable for Span
  def <=>(other : self) : Int32
end

Error for LocationErrorimpl

std/timeiyi doc src/std/time.iyi
impl Error for LocationError
  def message : String
end

Std::Traits::Comparable for Timeimpl

std/timeiyi doc src/std/time.iyi
impl Std::Traits::Comparable for Time
  def <=>(other : self) : Int32
end