class Timeframe module Iso8601 # Internal use. # # Parses a duration like 'P1Y2M4DT3H4M2S' class Duration attr_reader :seconds def initialize(date_part, time_part) y = parse date_part, :Y m = parse date_part, :M d = parse date_part, :D h = parse time_part, :H minutes = parse time_part, :M s = parse time_part, :S @seconds = (y*31_556_926 + m*2_629_743.83 + d*86_400 + h*3_600 + minutes*60 + s).ceil end def parse(part, indicator) if part =~ /(\d+)#{indicator.to_s}/ $1.to_f else 0 end end end # Internal use. class Side # We add one day because so that it can be excluded per timeframe's conventions. EXCLUDED_LAST_DAY = 86_400 attr_reader :date_part attr_reader :time_part def to_time(counterpart) if date_part.start_with?('P') counterpart.resolve_time(self) + offset + EXCLUDED_LAST_DAY else resolve_time counterpart end end end # Internal use. # # The "A" side of "A/B" class A < Side def initialize(raw) raw = raw.upcase @date_part, @time_part = raw.split('T') @time_part ||= '' end def resolve_time(*) Time.parse [date_part, time_part].join('T') end # When A is a period, it counts as a negative offset to B. def offset 0.0 - Duration.new(date_part, time_part).seconds end end # Internal use. # # The "B" side of "A/B" class B < Side def initialize(raw) raw = raw.upcase if raw.include?(':') and not raw.include?('T') # it's just a shorthand for time @date_part = '' @time_part = raw else @date_part, @time_part = raw.split('T') @time_part ||= '' end end # When shorthand is used, we need to peek at our counterpart (A) in order to steal letters # Shorthand can only be used on the B side, and only in / format. def resolve_time(counterpart) filled_in_date_part = unless date_part.count('-') == 2 counterpart.date_part[0..(0-date_part.length-1)] + date_part else date_part end filled_in_time_part = if time_part.count(':') < 2 counterpart.time_part[0..(0-time_part.length-1)] + time_part else time_part end Time.parse [filled_in_date_part, filled_in_time_part].join('T') end def offset Duration.new(date_part, time_part).seconds end end end end