module IceCube::TimeUtil

Constants

CLOCK_VALUES
DAYS
ICAL_DAYS
MONTHS

Public Class Methods

beginning_of_date(date, reference=Time.now) click to toggle source

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
build_in_zone(args, reference) click to toggle source
# 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
day_of_month(value, date) click to toggle source

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
days_in_month(time) click to toggle source

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
days_in_n_months(time, month_distance) click to toggle source

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
days_in_n_years(time, year_distance) click to toggle source

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
days_in_next_month(time) click to toggle source

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
days_in_year(time) click to toggle source

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
days_to_next_month(time) click to toggle source

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_time(time_or_hash) click to toggle source

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
dst_change(time) click to toggle source
# 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
end_of_date(date, reference=Time.now) click to toggle source

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_date(date) click to toggle source

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_time(time, reference = nil, date_eod = false) click to toggle source

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
hash(time) click to toggle source

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
ical_day_to_symbol(str) click to toggle source
# 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
match_zone(input_time, reference) click to toggle source
# 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
normalize_wday(wday, week_start) click to toggle source

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
now(reference=Time.now) click to toggle source

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
restore_deserialized_offset(time, orig_offset_str) click to toggle source

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_time(time) click to toggle source

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
subsec(time) click to toggle source

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
sym_to_month(sym) click to toggle source

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
sym_to_wday(sym) click to toggle source

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
wday_to_sym(wday) click to toggle source

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
which_occurrence_in_month(time, wday) click to toggle source

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