File: //usr/local/rvm/gems/ruby-2.5.9/gems/tzinfo-2.0.4/lib/tzinfo/transition_rule.rb
# encoding: UTF-8
# frozen_string_literal: true
module TZInfo
# Base class for rules definining the transition between standard and daylight
# savings time.
#
# @abstract
# @private
class TransitionRule #:nodoc:
# Returns the number of seconds after midnight local time on the day
# identified by the rule at which the transition occurs. Can be negative to
# denote a time on the prior day. Can be greater than or equal to 86,400 to
# denote a time of the following day.
#
# @return [Integer] the time in seconds after midnight local time at which
# the transition occurs.
attr_reader :transition_at
# Initializes a new {TransitionRule}.
#
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
def initialize(transition_at)
raise ArgumentError, 'Invalid transition_at' unless transition_at.kind_of?(Integer)
@transition_at = transition_at
end
# Calculates the time of the transition from a given offset on a given year.
#
# @param offset [TimezoneOffset] the current offset at the time the rule
# will transition.
# @param year [Integer] the year in which the transition occurs (local
# time).
# @return [TimestampWithOffset] the time at which the transition occurs.
def at(offset, year)
day = get_day(offset, year)
TimestampWithOffset.set_timezone_offset(Timestamp.for(day + @transition_at), offset)
end
# Determines if this {TransitionRule} is equal to another instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {TransitionRule} with the same
# {transition_at} as this {TransitionRule}, otherwise `false`.
def ==(r)
r.kind_of?(TransitionRule) && @transition_at == r.transition_at
end
alias eql? ==
# @return [Integer] a hash based on {hash_args} (defaulting to
# {transition_at}).
def hash
hash_args.hash
end
protected
# @return [Array] an `Array` of parameters that will influence the output of
# {hash}.
def hash_args
[@transition_at]
end
end
private_constant :TransitionRule
# A base class for transition rules that activate based on an integer day of
# the year.
#
# @abstract
# @private
class DayOfYearTransitionRule < TransitionRule #:nodoc:
# Initializes a new {DayOfYearTransitionRule}.
#
# @param day [Integer] the day of the year on which the transition occurs.
# The precise meaning is defined by subclasses.
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `day` is not an `Integer`.
def initialize(day, transition_at)
super(transition_at)
raise ArgumentError, 'Invalid day' unless day.kind_of?(Integer)
@seconds = day * 86400
end
# Determines if this {DayOfYearTransitionRule} is equal to another instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {DayOfYearTransitionRule} with the
# same {transition_at} and day as this {DayOfYearTransitionRule},
# otherwise `false`.
def ==(r)
super(r) && r.kind_of?(DayOfYearTransitionRule) && @seconds == r.seconds
end
alias eql? ==
protected
# @return [Integer] the day multipled by the number of seconds in a day.
attr_reader :seconds
# (see TransitionRule#hash_args)
def hash_args
[@seconds] + super
end
end
private_constant :DayOfYearTransitionRule
# Defines transitions that occur on the zero-based nth day of the year.
#
# Day 0 is 1 January.
#
# Leap days are counted. Day 59 will be 29 February on a leap year and 1 March
# on a non-leap year. Day 365 will be 31 December on a leap year and 1 January
# the following year on a non-leap year.
#
# @private
class AbsoluteDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
# Initializes a new {AbsoluteDayOfYearTransitionRule}.
#
# @param day [Integer] the zero-based day of the year on which the
# transition occurs (0 to 365 inclusive).
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `day` is not an `Integer`.
# @raise [ArgumentError] if `day` is less than 0 or greater than 365.
def initialize(day, transition_at = 0)
super(day, transition_at)
raise ArgumentError, 'Invalid day' unless day >= 0 && day <= 365
end
# @return [Boolean] `true` if the day specified by this transition is the
# first in the year (a day number of 0), otherwise `false`.
def is_always_first_day_of_year?
seconds == 0
end
# @return [Boolean] `false`.
def is_always_last_day_of_year?
false
end
# Determines if this {AbsoluteDayOfYearTransitionRule} is equal to another
# instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {AbsoluteDayOfYearTransitionRule}
# with the same {transition_at} and day as this
# {AbsoluteDayOfYearTransitionRule}, otherwise `false`.
def ==(r)
super(r) && r.kind_of?(AbsoluteDayOfYearTransitionRule)
end
alias eql? ==
protected
# Returns a `Time` representing midnight local time on the day specified by
# the rule for the given offset and year.
#
# @param offset [TimezoneOffset] the current offset at the time of the
# transition.
# @param year [Integer] the year in which the transition occurs.
# @return [Time] midnight local time on the day specified by the rule for
# the given offset and year.
def get_day(offset, year)
Time.new(year, 1, 1, 0, 0, 0, offset.observed_utc_offset) + seconds
end
# (see TransitionRule#hash_args)
def hash_args
[AbsoluteDayOfYearTransitionRule] + super
end
end
# Defines transitions that occur on the one-based nth Julian day of the year.
#
# Leap days are not counted. Day 1 is 1 January. Day 60 is always 1 March.
# Day 365 is always 31 December.
#
# @private
class JulianDayOfYearTransitionRule < DayOfYearTransitionRule #:nodoc:
# The 60 days in seconds.
LEAP = 60 * 86400
private_constant :LEAP
# The length of a non-leap year in seconds.
YEAR = 365 * 86400
private_constant :YEAR
# Initializes a new {JulianDayOfYearTransitionRule}.
#
# @param day [Integer] the one-based Julian day of the year on which the
# transition occurs (1 to 365 inclusive).
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `day` is not an `Integer`.
# @raise [ArgumentError] if `day` is less than 1 or greater than 365.
def initialize(day, transition_at = 0)
super(day, transition_at)
raise ArgumentError, 'Invalid day' unless day >= 1 && day <= 365
end
# @return [Boolean] `true` if the day specified by this transition is the
# first in the year (a day number of 1), otherwise `false`.
def is_always_first_day_of_year?
seconds == 86400
end
# @return [Boolean] `true` if the day specified by this transition is the
# last in the year (a day number of 365), otherwise `false`.
def is_always_last_day_of_year?
seconds == YEAR
end
# Determines if this {JulianDayOfYearTransitionRule} is equal to another
# instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {JulianDayOfYearTransitionRule} with
# the same {transition_at} and day as this
# {JulianDayOfYearTransitionRule}, otherwise `false`.
def ==(r)
super(r) && r.kind_of?(JulianDayOfYearTransitionRule)
end
alias eql? ==
protected
# Returns a `Time` representing midnight local time on the day specified by
# the rule for the given offset and year.
#
# @param offset [TimezoneOffset] the current offset at the time of the
# transition.
# @param year [Integer] the year in which the transition occurs.
# @return [Time] midnight local time on the day specified by the rule for
# the given offset and year.
def get_day(offset, year)
# Returns 1 March on non-leap years.
leap = Time.new(year, 2, 29, 0, 0, 0, offset.observed_utc_offset)
diff = seconds - LEAP
diff += 86400 if diff >= 0 && leap.mday == 29
leap + diff
end
# (see TransitionRule#hash_args)
def hash_args
[JulianDayOfYearTransitionRule] + super
end
end
private_constant :JulianDayOfYearTransitionRule
# A base class for rules that transition on a particular day of week of a
# given week (subclasses specify which week of the month).
#
# @abstract
# @private
class DayOfWeekTransitionRule < TransitionRule #:nodoc:
# Initializes a new {DayOfWeekTransitionRule}.
#
# @param month [Integer] the month of the year when the transition occurs.
# @param day_of_week [Integer] the day of the week when the transition
# occurs. 0 is Sunday, 6 is Saturday.
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `month` is not an `Integer`.
# @raise [ArgumentError] if `month` is less than 1 or greater than 12.
# @raise [ArgumentError] if `day_of_week` is not an `Integer`.
# @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
def initialize(month, day_of_week, transition_at)
super(transition_at)
raise ArgumentError, 'Invalid month' unless month.kind_of?(Integer) && month >= 1 && month <= 12
raise ArgumentError, 'Invalid day_of_week' unless day_of_week.kind_of?(Integer) && day_of_week >= 0 && day_of_week <= 6
@month = month
@day_of_week = day_of_week
end
# @return [Boolean] `false`.
def is_always_first_day_of_year?
false
end
# @return [Boolean] `false`.
def is_always_last_day_of_year?
false
end
# Determines if this {DayOfWeekTransitionRule} is equal to another
# instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {DayOfWeekTransitionRule} with the
# same {transition_at}, month and day of week as this
# {DayOfWeekTransitionRule}, otherwise `false`.
def ==(r)
super(r) && r.kind_of?(DayOfWeekTransitionRule) && @month == r.month && @day_of_week == r.day_of_week
end
alias eql? ==
protected
# @return [Integer] the month of the year (1 to 12).
attr_reader :month
# @return [Integer] the day of the week (0 to 6 for Sunday to Monday).
attr_reader :day_of_week
# (see TransitionRule#hash_args)
def hash_args
[@month, @day_of_week] + super
end
end
private_constant :DayOfWeekTransitionRule
# A rule that transitions on the nth occurrence of a particular day of week
# of a calendar month.
#
# @private
class DayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
# Initializes a new {DayOfMonthTransitionRule}.
#
# @param month [Integer] the month of the year when the transition occurs.
# @param week [Integer] the week of the month when the transition occurs (1
# to 4).
# @param day_of_week [Integer] the day of the week when the transition
# occurs. 0 is Sunday, 6 is Saturday.
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `month` is not an `Integer`.
# @raise [ArgumentError] if `month` is less than 1 or greater than 12.
# @raise [ArgumentError] if `week` is not an `Integer`.
# @raise [ArgumentError] if `week` is less than 1 or greater than 4.
# @raise [ArgumentError] if `day_of_week` is not an `Integer`.
# @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
def initialize(month, week, day_of_week, transition_at = 0)
super(month, day_of_week, transition_at)
raise ArgumentError, 'Invalid week' unless week.kind_of?(Integer) && week >= 1 && week <= 4
@offset_start = (week - 1) * 7 + 1
end
# Determines if this {DayOfMonthTransitionRule} is equal to another
# instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {DayOfMonthTransitionRule} with the
# same {transition_at}, month, week and day of week as this
# {DayOfMonthTransitionRule}, otherwise `false`.
def ==(r)
super(r) && r.kind_of?(DayOfMonthTransitionRule) && @offset_start == r.offset_start
end
alias eql? ==
protected
# @return [Integer] the day the week starts on for a month starting on a
# Sunday.
attr_reader :offset_start
# Returns a `Time` representing midnight local time on the day specified by
# the rule for the given offset and year.
#
# @param offset [TimezoneOffset] the current offset at the time of the
# transition.
# @param year [Integer] the year in which the transition occurs.
# @return [Time] midnight local time on the day specified by the rule for
# the given offset and year.
def get_day(offset, year)
candidate = Time.new(year, month, @offset_start, 0, 0, 0, offset.observed_utc_offset)
diff = day_of_week - candidate.wday
if diff < 0
candidate + (7 + diff) * 86400
elsif diff > 0
candidate + diff * 86400
else
candidate
end
end
# (see TransitionRule#hash_args)
def hash_args
[@offset_start] + super
end
end
private_constant :DayOfMonthTransitionRule
# A rule that transitions on the last occurrence of a particular day of week
# of a calendar month.
#
# @private
class LastDayOfMonthTransitionRule < DayOfWeekTransitionRule #:nodoc:
# Initializes a new {LastDayOfMonthTransitionRule}.
#
# @param month [Integer] the month of the year when the transition occurs.
# @param day_of_week [Integer] the day of the week when the transition
# occurs. 0 is Sunday, 6 is Saturday.
# @param transition_at [Integer] the time in seconds after midnight local
# time at which the transition occurs.
# @raise [ArgumentError] if `transition_at` is not an `Integer`.
# @raise [ArgumentError] if `month` is not an `Integer`.
# @raise [ArgumentError] if `month` is less than 1 or greater than 12.
# @raise [ArgumentError] if `day_of_week` is not an `Integer`.
# @raise [ArgumentError] if `day_of_week` is less than 0 or greater than 6.
def initialize(month, day_of_week, transition_at = 0)
super(month, day_of_week, transition_at)
end
# Determines if this {LastDayOfMonthTransitionRule} is equal to another
# instance.
#
# @param r [Object] the instance to test for equality.
# @return [Boolean] `true` if `r` is a {LastDayOfMonthTransitionRule} with
# the same {transition_at}, month and day of week as this
# {LastDayOfMonthTransitionRule}, otherwise `false`.
def ==(r)
super(r) && r.kind_of?(LastDayOfMonthTransitionRule)
end
alias eql? ==
protected
# Returns a `Time` representing midnight local time on the day specified by
# the rule for the given offset and year.
#
# @param offset [TimezoneOffset] the current offset at the time of the
# transition.
# @param year [Integer] the year in which the transition occurs.
# @return [Time] midnight local time on the day specified by the rule for
# the given offset and year.
def get_day(offset, year)
next_month = month + 1
if next_month == 13
year += 1
next_month = 1
end
candidate = Time.new(year, next_month, 1, 0, 0, 0, offset.observed_utc_offset) - 86400
diff = candidate.wday - day_of_week
if diff < 0
candidate - (diff + 7) * 86400
elsif diff > 0
candidate - diff * 86400
else
candidate
end
end
end
private_constant :LastDayOfMonthTransitionRule
end