1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
|
# encoding: UTF-8
# frozen_string_literal: true
require 'date'
module TZInfo
# A subclass of `DateTime` used to represent local times. {DateTimeWithOffset}
# holds a reference to the related {TimezoneOffset} and overrides various
# methods to return results appropriate for the {TimezoneOffset}. Certain
# operations will clear the associated {TimezoneOffset} (if the
# {TimezoneOffset} would not necessarily be valid for the result). Once the
# {TimezoneOffset} has been cleared, {DateTimeWithOffset} behaves identically
# to `DateTime`.
#
# Arithmetic performed on {DateTimeWithOffset} instances is _not_ time
# zone-aware. Regardless of whether transitions in the time zone are crossed,
# results of arithmetic operations will always maintain the same offset from
# UTC (`offset`). The associated {TimezoneOffset} will aways be cleared.
class DateTimeWithOffset < DateTime
include WithOffset
# @return [TimezoneOffset] the {TimezoneOffset} associated with this
# instance.
attr_reader :timezone_offset
# Sets the associated {TimezoneOffset}.
#
# @param timezone_offset [TimezoneOffset] a {TimezoneOffset} valid at the
# time and for the offset of this {DateTimeWithOffset}.
# @return [DateTimeWithOffset] `self`.
# @raise [ArgumentError] if `timezone_offset` is `nil`.
# @raise [ArgumentError] if `timezone_offset.observed_utc_offset` does not
# equal `self.offset * 86400`.
def set_timezone_offset(timezone_offset)
raise ArgumentError, 'timezone_offset must be specified' unless timezone_offset
raise ArgumentError, 'timezone_offset.observed_utc_offset does not match self.utc_offset' if offset * 86400 != timezone_offset.observed_utc_offset
@timezone_offset = timezone_offset
self
end
# An overridden version of `DateTime#to_time` that, if there is an
# associated {TimezoneOffset}, returns a {DateTimeWithOffset} with that
# offset.
#
# @return [Time] if there is an associated {TimezoneOffset}, a
# {TimeWithOffset} representation of this {DateTimeWithOffset}, otherwise
# a `Time` representation.
def to_time
if_timezone_offset(super) do |o,t|
# Ruby 2.4.0 changed the behaviour of to_time so that it preserves the
# offset instead of converting to the system local timezone.
#
# When self has an associated TimezonePeriod, this implementation will
# preserve the offset on all versions of Ruby.
TimeWithOffset.at(t.to_i, t.subsec * 1_000_000).set_timezone_offset(o)
end
end
# An overridden version of `DateTime#downto` that clears the associated
# {TimezoneOffset} of the returned or yielded instances.
def downto(min)
if block_given?
super {|dt| yield dt.clear_timezone_offset }
else
enum = super
enum.each {|dt| dt.clear_timezone_offset }
enum
end
end
# An overridden version of `DateTime#england` that preserves the associated
# {TimezoneOffset}.
#
# @return [DateTime]
def england
# super doesn't call #new_start on MRI, so each method has to be
# individually overridden.
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
end
# An overridden version of `DateTime#gregorian` that preserves the
# associated {TimezoneOffset}.
#
# @return [DateTime]
def gregorian
# super doesn't call #new_start on MRI, so each method has to be
# individually overridden.
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
end
# An overridden version of `DateTime#italy` that preserves the associated
# {TimezoneOffset}.
#
# @return [DateTime]
def italy
# super doesn't call #new_start on MRI, so each method has to be
# individually overridden.
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
end
# An overridden version of `DateTime#julian` that preserves the associated
# {TimezoneOffset}.
#
# @return [DateTime]
def julian
# super doesn't call #new_start on MRI, so each method has to be
# individually overridden.
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
end
# An overridden version of `DateTime#new_start` that preserves the
# associated {TimezoneOffset}.
#
# @return [DateTime]
def new_start(start = Date::ITALY)
if_timezone_offset(super) {|o,dt| dt.set_timezone_offset(o) }
end
# An overridden version of `DateTime#step` that clears the associated
# {TimezoneOffset} of the returned or yielded instances.
def step(limit, step = 1)
if block_given?
super {|dt| yield dt.clear_timezone_offset }
else
enum = super
enum.each {|dt| dt.clear_timezone_offset }
enum
end
end
# An overridden version of `DateTime#upto` that clears the associated
# {TimezoneOffset} of the returned or yielded instances.
def upto(max)
if block_given?
super {|dt| yield dt.clear_timezone_offset }
else
enum = super
enum.each {|dt| dt.clear_timezone_offset }
enum
end
end
protected
# Clears the associated {TimezoneOffset}.
#
# @return [DateTimeWithOffset] `self`.
def clear_timezone_offset
@timezone_offset = nil
self
end
end
end
|