module IceCube::TimeUtil
Constants
- CLOCK_VALUES
- DAYS
- ICAL_DAYS
- MONTHS
Public Class Methods
Get the beginning of a date
# File lib/ice_cube/time_util.rb, line 142 def self.beginning_of_date(date, reference=Time.now) build_in_zone([date.year, date.month, date.day, 0, 0, 0], reference) end
# File lib/ice_cube/time_util.rb, line 32 def self.build_in_zone(args, reference) if reference.respond_to?(:time_zone) reference.time_zone.local(*args) elsif reference.utc? Time.utc(*args) elsif reference.zone Time.local(*args) else Time.new(*args << reference.utc_offset) end end
Get a day of the month in the month of a given time without overflowing into the next month. Accepts days from positive (start of month forward) or negative (from end of month)
# File lib/ice_cube/time_util.rb, line 223 def self.day_of_month(value, date) if value.to_i > 0 [value, days_in_month(date)].min else [1 + days_in_month(date) + value, 1].max end end
Get the days in the month for +time
# File lib/ice_cube/time_util.rb, line 202 def self.days_in_month(time) date = Date.new(time.year, time.month, 1) ((date >> 1) - date).to_i end
The number of days in n months
# File lib/ice_cube/time_util.rb, line 244 def self.days_in_n_months(time, month_distance) date = Date.new(time.year, time.month, time.day) ((date >> month_distance) - date).to_i end
Number of days to n years
# File lib/ice_cube/time_util.rb, line 238 def self.days_in_n_years(time, year_distance) date = Date.new(time.year, time.month, time.day) ((date >> year_distance * 12) - date).to_i end
Get the days in the following month for +time
# File lib/ice_cube/time_util.rb, line 208 def self.days_in_next_month(time) date = Date.new(time.year, time.month, 1) >> 1 ((date >> 1) - date).to_i end
Number of days in a year
# File lib/ice_cube/time_util.rb, line 232 def self.days_in_year(time) date = Date.new(time.year, 1, 1) ((date >> 12) - date).to_i end
Count the number of days to the same day of the next month without overflowing shorter months
# File lib/ice_cube/time_util.rb, line 215 def self.days_to_next_month(time) date = Date.new(time.year, time.month, time.day) ((date >> 1) - date).to_i end
Deserialize a time serialized with serialize_time
or in ISO8601 string format
# File lib/ice_cube/time_util.rb, line 107 def self.deserialize_time(time_or_hash) case time_or_hash when Time, Date time_or_hash when DateTime Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) when Hash hash = FlexibleHash.new(time_or_hash) hash[:time].in_time_zone(hash[:zone]) when String Time.parse(time_or_hash) end end
# File lib/ice_cube/time_util.rb, line 249 def self.dst_change(time) one_hour_ago = time - ONE_HOUR if time.dst? ^ one_hour_ago.dst? (time.utc_offset - one_hour_ago.utc_offset) / ONE_HOUR end end
Get the end of a date
# File lib/ice_cube/time_util.rb, line 147 def self.end_of_date(date, reference=Time.now) build_in_zone([date.year, date.month, date.day, 23, 59, 59], reference) end
Ensure that this is either nil, or a date
# File lib/ice_cube/time_util.rb, line 82 def self.ensure_date(date) case date when Date then date else return Date.new(date.year, date.month, date.day) end end
Ensure that this is either nil, or a time
# File lib/ice_cube/time_util.rb, line 61 def self.ensure_time(time, reference = nil, date_eod = false) case time when DateTime warn "IceCube: DateTime support is deprecated (please use Time) at: #{ caller[2] }" Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) when Date if date_eod end_of_date(time, reference) else if reference build_in_zone([time.year, time.month, time.day], reference) else time.to_time end end else time end end
Get a more precise equality for time objects Ruby provides a Time#hash method, but it fails to account for UTC offset (so the current date may be different) or DST rules (so the hour may be wrong for different schedule occurrences)
# File lib/ice_cube/time_util.rb, line 125 def self.hash(time) [time, time.utc_offset, time.zone].hash end
# File lib/ice_cube/time_util.rb, line 186 def self.ical_day_to_symbol(str) day = ICAL_DAYS[str] raise ArgumentError, "Invalid day: #{str}" if day.nil? day end
# File lib/ice_cube/time_util.rb, line 44 def self.match_zone(input_time, reference) return unless time = ensure_time(input_time, reference) time = if reference.respond_to? :time_zone time.in_time_zone(reference.time_zone) else if reference.utc? time.getgm elsif reference.zone time.getlocal else time.getlocal(reference.utc_offset) end end (Date === input_time) ? beginning_of_date(time, reference) : time end
Convert weekday from base sunday to the schedule’s week start.
# File lib/ice_cube/time_util.rb, line 181 def self.normalize_wday(wday, week_start) (wday - sym_to_wday(week_start)) % 7 end
Provides a Time.now without the usec, in the reference zone or utc offset
# File lib/ice_cube/time_util.rb, line 28 def self.now(reference=Time.now) match_zone(Time.at(Time.now.to_i), reference) end
Check the deserialized time offset string against actual local time offset to try and preserve the original offset for plain Ruby Time. If the offset is the same as local we can assume the same original zone and keep it. If it was serialized with a different offset than local TZ it will lose the zone and not support DST.
# File lib/ice_cube/time_util.rb, line 134 def self.restore_deserialized_offset(time, orig_offset_str) return time if time.respond_to?(:time_zone) || time.getlocal(orig_offset_str).utc_offset == time.utc_offset warn "IceCube: parsed Time from nonlocal TZ. Use ActiveSupport to fix DST at: #{ caller[0] }" time.localtime(orig_offset_str) end
Serialize a time appropriate for storing
# File lib/ice_cube/time_util.rb, line 91 def self.serialize_time(time) case time when Time, Date if time.respond_to?(:time_zone) {:time => time.utc, :zone => time.time_zone.name} else time end when DateTime Time.local(time.year, time.month, time.day, time.hour, time.min, time.sec) else raise ArgumentError, "cannot serialize #{time.inspect}, expected a Time" end end
Handle discrepancies between various time types
-
Time has subsec
-
DateTime does not
-
ActiveSupport::TimeWithZone can wrap either type, depending on version or if ‘parse` or `now`/`local` was used to build it.
# File lib/ice_cube/time_util.rb, line 261 def self.subsec(time) if time.respond_to?(:subsec) time.subsec elsif time.respond_to?(:sec_fraction) time.sec_fraction else 0.0 end end
Convert a symbol to a numeric month
# File lib/ice_cube/time_util.rb, line 152 def self.sym_to_month(sym) MONTHS.fetch(sym) do |k| MONTHS.values.detect { |i| i.to_s == k.to_s } or raise ArgumentError, "Expecting Integer or Symbol value for month. " \ "No such month: #{k.inspect}" end end
Convert a symbol to a wday number
# File lib/ice_cube/time_util.rb, line 162 def self.sym_to_wday(sym) DAYS.fetch(sym) do |k| DAYS.values.detect { |i| i.to_s == k.to_s } or raise ArgumentError, "Expecting Integer or Symbol value for weekday. " \ "No such weekday: #{k.inspect}" end end
Convert wday number to day symbol
# File lib/ice_cube/time_util.rb, line 172 def self.wday_to_sym(wday) return wday if DAYS.keys.include? wday DAYS.invert.fetch(wday) do |i| raise ArgumentError, "Expecting Integer value for weekday. " \ "No such wday number: #{i.inspect}" end end
Return the count of the number of times wday appears in the month, and which of those time falls on
# File lib/ice_cube/time_util.rb, line 194 def self.which_occurrence_in_month(time, wday) first_occurrence = ((7 - Time.utc(time.year, time.month, 1).wday) + time.wday) % 7 + 1 this_weekday_in_month_count = ((days_in_month(time) - first_occurrence + 1) / 7.0).ceil nth_occurrence_of_weekday = (time.mday - first_occurrence) / 7 + 1 [nth_occurrence_of_weekday, this_weekday_in_month_count] end