class TZInfo::Timestamp

A time represented as an ‘Integer` number of seconds since 1970-01-01 00:00:00 UTC (ignoring leap seconds and using the proleptic Gregorian calendar), the fraction through the second (sub_second as a `Rational`) and an optional UTC offset. Like Ruby’s ‘Time` class, {Timestamp} can distinguish between a local time with a zero offset and a time specified explicitly as UTC.

Attributes

@return [Numeric] the fraction of a second elapsed since timestamp as

either a `Rational` or the `Integer` 0. Always greater than or equal to
0 and less than 1.

@return [Integer] the offset from UTC in seconds or ‘nil` if the

{Timestamp} doesn't have a specified offset.

@return [Integer] the number of seconds since 1970-01-01 00:00:00 UTC

ignoring leap seconds (i.e. each day is treated as if it were 86,400
seconds long).

Public Class Methods

Returns a new {Timestamp} representing the (proleptic Gregorian calendar) date and time specified by the supplied parameters.

If ‘utc_offset` is `nil`, `:utc` or 0, the date and time parameters will be interpreted as representing a UTC date and time. Otherwise the date and time parameters will be interpreted as a local date and time with the given offset.

@param year [Integer] the year. @param month [Integer] the month (1-12). @param day [Integer] the day of the month (1-31). @param hour [Integer] the hour (0-23). @param minute [Integer] the minute (0-59). @param second [Integer] the second (0-59). @param sub_second [Numeric] the fractional part of the second as either

a `Rational` that is greater than or equal to 0 and less than 1, or
the `Integer` 0.

@param utc_offset [Object] either ‘nil` for a {Timestamp} without a

specified offset, an offset from UTC specified as an `Integer` number
of seconds or the `Symbol` `:utc`).

@return [Timestamp] a new {Timestamp} representing the specified

(proleptic Gregorian calendar) date and time.

@raise [ArgumentError] if either of ‘year`, `month`, `day`, `hour`,

`minute`, or `second` is not an `Integer`.

@raise [ArgumentError] if ‘sub_second` is not a `Rational`, or the

`Integer` 0.

@raise [ArgumentError] if ‘utc_offset` is not `nil`, not an `Integer`

and not the `Symbol` `:utc`.

@raise [RangeError] if ‘month` is not between 1 and 12. @raise [RangeError] if `day` is not between 1 and 31. @raise [RangeError] if `hour` is not between 0 and 23. @raise [RangeError] if `minute` is not between 0 and 59. @raise [RangeError] if `second` is not between 0 and 59. @raise [RangeError] if `sub_second` is a `Rational` but that is less

than 0 or greater than or equal to 1.
# File lib/tzinfo/timestamp.rb, line 55
def create(year, month = 1, day = 1, hour = 0, minute = 0, second = 0, sub_second = 0, utc_offset = nil)
  raise ArgumentError, 'year must be an Integer' unless year.kind_of?(Integer)
  raise ArgumentError, 'month must be an Integer' unless month.kind_of?(Integer)
  raise ArgumentError, 'day must be an Integer' unless day.kind_of?(Integer)
  raise ArgumentError, 'hour must be an Integer' unless hour.kind_of?(Integer)
  raise ArgumentError, 'minute must be an Integer' unless minute.kind_of?(Integer)
  raise ArgumentError, 'second must be an Integer' unless second.kind_of?(Integer)
  raise RangeError, 'month must be between 1 and 12' if month < 1 || month > 12
  raise RangeError, 'day must be between 1 and 31' if day < 1 || day > 31
  raise RangeError, 'hour must be between 0 and 23' if hour < 0 || hour > 23
  raise RangeError, 'minute must be between 0 and 59' if minute < 0 || minute > 59
  raise RangeError, 'second must be between 0 and 59' if second < 0 || second > 59

  # Based on days_from_civil from https://howardhinnant.github.io/date_algorithms.html#days_from_civil
  after_february = month > 2
  year -= 1 unless after_february
  era = year / 400
  year_of_era = year - era * 400
  day_of_year = (153 * (month + (after_february ? -3 : 9)) + 2) / 5 + day - 1
  day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year
  days_since_epoch = era * 146097 + day_of_era - 719468
  value = ((days_since_epoch * 24 + hour) * 60 + minute) * 60 + second
  value -= utc_offset if utc_offset.kind_of?(Integer)

  new(value, sub_second, utc_offset)
end

When used without a block, returns a {Timestamp} representation of a given ‘Time`, `DateTime` or {Timestamp}.

When called with a block, the {Timestamp} representation of ‘value` is passed to the block. The block must then return a {Timestamp}, which will be converted back to the type of the initial value. If the initial value was a {Timestamp}, the block result will be returned. If the initial value was a `DateTime`, a Gregorian `DateTime` will be returned.

The UTC offset of ‘value` can either be preserved (the {Timestamp} representation will have the same UTC offset as `value`), ignored (the {Timestamp} representation will have no defined UTC offset), or treated as though it were UTC (the {Timestamp} representation will have a {utc_offset} of 0 and {utc?} will return `true`).

@param value [Object] a ‘Time`, `DateTime` or {Timestamp}. @param offset [Symbol] either `:preserve` to preserve the offset of

`value`, `:ignore` to ignore the offset of `value` and create a
{Timestamp} with an unspecified offset, or `:treat_as_utc` to treat
the offset of `value` as though it were UTC and create a UTC
{Timestamp}.

@yield [timestamp] if a block is provided, the {Timestamp}

representation is passed to the block.

@yieldparam timestamp [Timestamp] the {Timestamp} representation of

`value`.

@yieldreturn [Timestamp] a {Timestamp} to be converted back to the type

of `value`.

@return [Object] if called without a block, the {Timestamp}

representation of `value`, otherwise the result of the block,
converted back to the type of `value`.
# File lib/tzinfo/timestamp.rb, line 112
def for(value, offset = :preserve)
  raise ArgumentError, 'value must be specified' unless value

  case offset
    when :ignore
      ignore_offset = true
      target_utc_offset = nil
    when :treat_as_utc
      ignore_offset = true
      target_utc_offset = :utc
    when :preserve
      ignore_offset = false
      target_utc_offset = nil
    else
      raise ArgumentError, 'offset must be :preserve, :ignore or :treat_as_utc'
  end

  time_like = false
  timestamp = case value
    when Time
      for_time(value, ignore_offset, target_utc_offset)
    when DateTime
      for_datetime(value, ignore_offset, target_utc_offset)
    when Timestamp
      for_timestamp(value, ignore_offset, target_utc_offset)
    else
      raise ArgumentError, "#{value.class} values are not supported" unless is_time_like?(value)
      time_like = true
      for_time_like(value, ignore_offset, target_utc_offset)
  end

  if block_given?
    result = yield timestamp
    raise ArgumentError, 'block must return a Timestamp' unless result.kind_of?(Timestamp)

    case value
      when Time
        result.to_time
      when DateTime
        result.to_datetime
      else # A Time-like value or a Timestamp
        time_like ? result.to_time : result
    end
  else
    timestamp
  end
end

Initializes a new {Timestamp}.

@param value [Integer] the number of seconds since 1970-01-01 00:00:00 UTC

ignoring leap seconds.

@param sub_second [Numeric] the fractional part of the second as either a

`Rational` that is greater than or equal to 0 and less than 1, or
the `Integer` 0.

@param utc_offset [Object] either ‘nil` for a {Timestamp} without a

specified offset, an offset from UTC specified as an `Integer` number of
seconds or the `Symbol` `:utc`).

@raise [ArgumentError] if ‘value` is not an `Integer`. @raise [ArgumentError] if `sub_second` is not a `Rational`, or the

`Integer` 0.

@raise [RangeError] if ‘sub_second` is a `Rational` but that is less

than 0 or greater than or equal to 1.

@raise [ArgumentError] if ‘utc_offset` is not `nil`, not an `Integer` and

not the `Symbol` `:utc`.
# File lib/tzinfo/timestamp.rb, line 344
def initialize(value, sub_second = 0, utc_offset = nil)
  raise ArgumentError, 'value must be an Integer' unless value.kind_of?(Integer)
  raise ArgumentError, 'sub_second must be a Rational or the Integer 0' unless (sub_second.kind_of?(Integer) && sub_second == 0) || sub_second.kind_of?(Rational)
  raise RangeError, 'sub_second must be >= 0 and < 1' if sub_second < 0 || sub_second >= 1
  raise ArgumentError, 'utc_offset must be an Integer, :utc or nil' if utc_offset && utc_offset != :utc && !utc_offset.kind_of?(Integer)
  initialize!(value, sub_second, utc_offset)
end

Creates a new UTC {Timestamp}.

@param value [Integer] the number of seconds since 1970-01-01 00:00:00

UTC ignoring leap seconds.

@param sub_second [Numeric] the fractional part of the second as either

a `Rational` that is greater than or equal to 0 and less than 1, or
the `Integer` 0.

@raise [ArgumentError] if ‘value` is not an `Integer`. @raise [ArgumentError] if `sub_second` is not a `Rational`, or the

`Integer` 0.

@raise [RangeError] if ‘sub_second` is a `Rational` but that is less

than 0 or greater than or equal to 1.
# File lib/tzinfo/timestamp.rb, line 172
def utc(value, sub_second = 0)
  new(value, sub_second, :utc)
end

Public Instance Methods

Compares this {Timestamp} with another.

{Timestamp} instances without a defined UTC offset are not comparable with {Timestamp} instances that have a defined UTC offset.

@param t [Timestamp] the {Timestamp} to compare this instance with. @return [Integer] -1, 0 or 1 depending if this instance is earlier, equal

or later than `t` respectively. Returns `nil` when comparing a
{Timestamp} that does not have a defined UTC offset with a {Timestamp}
that does have a defined UTC offset. Returns `nil` if `t` is not a
{Timestamp}.
# File lib/tzinfo/timestamp.rb, line 454
def <=>(t)
  return nil unless t.kind_of?(Timestamp)
  return nil if utc_offset && !t.utc_offset
  return nil if !utc_offset && t.utc_offset

  result = value <=> t.value
  result = sub_second <=> t.sub_second if result == 0
  result
end

Adds a number of seconds to the {Timestamp} value, setting the UTC offset of the result.

@param seconds [Integer] the number of seconds to be added. @param utc_offset [Object] either ‘nil` for a {Timestamp} without a

specified offset, an offset from UTC specified as an `Integer` number of
seconds or the `Symbol` `:utc`).

@return [Timestamp] the result of adding ‘seconds` to the

{Timestamp} value as a new {Timestamp} instance with the chosen
`utc_offset`.

@raise [ArgumentError] if ‘seconds` is not an `Integer`. @raise [ArgumentError] if `utc_offset` is not `nil`, not an `Integer` and

not the `Symbol` `:utc`.
# File lib/tzinfo/timestamp.rb, line 372
def add_and_set_utc_offset(seconds, utc_offset)
  raise ArgumentError, 'seconds must be an Integer' unless seconds.kind_of?(Integer)
  raise ArgumentError, 'utc_offset must be an Integer, :utc or nil' if utc_offset && utc_offset != :utc && !utc_offset.kind_of?(Integer)
  return self if seconds == 0 && utc_offset == (@utc ? :utc : @utc_offset)
  Timestamp.send(:new!, @value + seconds, @sub_second, utc_offset)
end

@return [Integer] a hash based on the value, sub-second and whether there

is a defined UTC offset.
# File lib/tzinfo/timestamp.rb, line 468
def hash
  [@value, @sub_second, !!@utc_offset].hash
end

@return [String] the internal object state as a programmer-readable

`String`.
# File lib/tzinfo/timestamp.rb, line 474
def inspect
  "#<#{self.class}: @value=#{@value}, @sub_second=#{@sub_second}, @utc_offset=#{@utc_offset.inspect}, @utc=#{@utc.inspect}>"
end

Formats this {Timestamp} according to the directives in the given format string.

@param format [String] the format string. Please refer to ‘Time#strftime`

for a list of supported format directives.

@return [String] the formatted {Timestamp}. @raise [ArgumentError] if ‘format` is not specified.

# File lib/tzinfo/timestamp.rb, line 426
def strftime(format)
  raise ArgumentError, 'format must be specified' unless format
  to_time.strftime(format)
end

Converts this {Timestamp} to a Gregorian ‘DateTime`.

@return [DateTime] a Gregorian ‘DateTime` representation of this

{Timestamp}. If the UTC offset of this {Timestamp} is not specified, a
UTC `DateTime` will be returned.
# File lib/tzinfo/timestamp.rb, line 406
def to_datetime
  new_datetime
end

Converts this {Timestamp} to an ‘Integer` number of seconds since 1970-01-01 00:00:00 UTC (ignoring leap seconds).

@return [Integer] an ‘Integer` representation of this {Timestamp} (the

number of seconds since 1970-01-01 00:00:00 UTC ignoring leap seconds).
# File lib/tzinfo/timestamp.rb, line 415
def to_i
  value
end

@return [String] a ‘String` representation of this {Timestamp}.

# File lib/tzinfo/timestamp.rb, line 432
def to_s
  return value_and_sub_second_to_s unless @utc_offset
  return "#{value_and_sub_second_to_s} UTC" if @utc

  sign = @utc_offset >= 0 ? '+' : '-'
  min, sec = @utc_offset.abs.divmod(60)
  hour, min = min.divmod(60)

  "#{value_and_sub_second_to_s(@utc_offset)} #{sign}#{'%02d' % hour}:#{'%02d' % min}#{sec > 0 ? ':%02d' % sec : nil}#{@utc_offset != 0 ? " (#{value_and_sub_second_to_s} UTC)" : nil}"
end

Converts this {Timestamp} to a ‘Time`.

@return [Time] a ‘Time` representation of this {Timestamp}. If the UTC

offset of this {Timestamp} is not specified, a UTC `Time` will be
returned.
# File lib/tzinfo/timestamp.rb, line 391
def to_time
  time = new_time

  if @utc_offset && !@utc
    time.localtime(@utc_offset)
  else
    time.utc
  end
end

@return [Timestamp] a UTC {Timestamp} equivalent to this instance. Returns

`self` if {#utc? self.utc?} is `true`.
# File lib/tzinfo/timestamp.rb, line 381
def utc
  return self if @utc
  Timestamp.send(:new!, @value, @sub_second, :utc)
end

@return [Boolean] ‘true` if this {Timestamp} represents UTC, `false` if

the {Timestamp} wasn't specified as UTC or `nil` if the {Timestamp} has
no specified offset.
# File lib/tzinfo/timestamp.rb, line 355
def utc?
  @utc
end

Protected Instance Methods

Constructs a new instance of a ‘DateTime` or `DateTime`-like class with the same {value}, {sub_second} and {utc_offset} as this {Timestamp}.

@param klass [Class] the class to instantiate.

@private

# File lib/tzinfo/timestamp.rb, line 496
def new_datetime(klass = DateTime)
  # Can't specify the start parameter unless the jd parameter is an exact number of days.
  # Use #gregorian instead.
  datetime = klass.jd(JD_EPOCH + ((@value.to_r + @sub_second) / 86400)).gregorian
  @utc_offset && @utc_offset != 0 ? datetime.new_offset(Rational(@utc_offset, 86400)) : datetime
end

Creates a new instance of a ‘Time` or `Time`-like class matching the {value} and {sub_second} of this {Timestamp}, but not setting the offset.

@param klass [Class] the class to instantiate.

@private

# File lib/tzinfo/timestamp.rb, line 486
def new_time(klass = Time)
  klass.at(@value, @sub_second * 1_000_000)
end