module TimeCalc::Types

@private Tries to encapsulate all the differences between Time, Date, DateTime

Constants

ATTRS
CLASS_NAME

@private Because AS::TimeWithZone so frigging smart that it returns “Time” from redefined class name.

REAL_TIMEZONE

Public Instance Methods

compare(v1, v2) click to toggle source
# File lib/time_calc/types.rb, line 24
def compare(v1, v2)
  compatible?(v1, v2) ? v1 <=> v2 : v1.to_time <=> v2.to_time
end
compatible?(v1, v2) click to toggle source
# File lib/time_calc/types.rb, line 20
def compatible?(v1, v2)
  [v1, v2].all?(Date) || [v1, v2].all?(Time)
end
convert(v, klass) click to toggle source
# File lib/time_calc/types.rb, line 28
def convert(v, klass)
  return v if v.class == klass

  v.public_send("to_#{klass.name.downcase}")
end
merge_activesupport__timewithzone(value, **attrs) click to toggle source
# File lib/time_calc/types.rb, line 54
def merge_activesupport__timewithzone(value, **attrs)
  # You'd imagine we should be able to use just value.change(...) ActiveSupport's API here...
  # But it is not available if you don't require all the core_ext's of Time, so I decided to
  # be on the safe side and use similar approach everywhere.

  # When we truncate, we use :subsec key as a sign to zeroefy second fractions
  attrs[:sec_fraction] ||= attrs.delete(:subsec) if attrs.key?(:subsec)

  _merge(value, **attrs)
    .then { |components|
      zone = components.delete(:time_zone)
      components.merge!(mday: components.delete(:day), mon: components.delete(:month))
      zone.__send__(:parts_to_time, components, value)
    }
end
merge_date(value, **attrs) click to toggle source
# File lib/time_calc/types.rb, line 41
def merge_date(value, **attrs)
  _merge(value, **attrs).values.then { |components| Date.new(*components) }
end
merge_datetime(value, **attrs) click to toggle source
# File lib/time_calc/types.rb, line 45
def merge_datetime(value, **attrs)
  # When we truncate, we use :subsec key as a sign to zeroefy second fractions
  attrs[:sec_fraction] ||= attrs.delete(:subsec) if attrs.key?(:subsec)

  _merge(value, **attrs)
    .tap { |h| h[:sec] += h.delete(:sec_fraction) }
    .values.then { |components| DateTime.new(*components) }
end
merge_time(value, **attrs) click to toggle source
# File lib/time_calc/types.rb, line 34
def merge_time(value, **attrs)
  _merge(value, **attrs)
    .tap { |h| h[:sec] += h.delete(:subsec) }
    .then { |h| fix_time_zone(h, value) }
    .values.then { |components| Time.new(*components) }
end

Private Instance Methods

_merge(value, attrs) click to toggle source
# File lib/time_calc/types.rb, line 86
def _merge(value, attrs)
  attr_names = ATTRS.fetch(CLASS_NAME.bind(value.class).call)
  attr_names.to_h { |u| [u, value.public_send(u)] }.merge(**attrs.slice(*attr_names))
end
fix_time_zone(attrs, origin) click to toggle source
# File lib/time_calc/types.rb, line 74
def fix_time_zone(attrs, origin)
  case origin.zone
  when nil, '' # "" is JRuby's way to say "no zone known"
    attrs
  when String
    # Y U NO Hash#except, Ruby???
    attrs.slice(*attrs.keys.-([:utc_offset])) # Then it would be default, then it would set system's zone
  when REAL_TIMEZONE
    attrs.merge(utc_offset: origin.zone) # When passed in place of utc_offset, timezone object becomes Time's zone
  end
end