module OpenConferenceWare module TimeRangeHelper def normalize_time(start_time, end_time=nil, opts=nil) TimeRange.new(start_time, end_time, opts).to_s # datetime_format(time,time.min == 0 ? '%I%p' : '%I:%M%p').downcase end end class TimeRange # A representation of a time or range of time that can format itself # in a meaningful way. Examples: # "Thursday, April 3, 2008" # "Thursday, April 3, 2008 at 4pm" # "Thursday, April 3, 2008 from 4:30-6pm" # (context: in the list for today) "11:30am-2pm" # "Thursday-Friday, April 3-5, 2008" # (context: during 2008) "Thursday April 5, 2009 at 3:30pm through Friday, April 5 at 8:45pm, 2009" # (same, context: during 2009) "Thursday April 5 at 3:30pm through Friday, April 5 at 8:45pm" @@to_s_cache = {} def initialize(start_time, end_time=nil, opts=nil) # Initialize with a single DateTime, a pair of DateTimes, # or an object that responds_to start_time and end_time, and # several options # # By default (unless format: :text) include tags # for hCalendar, and (unless relative: false) refer to # "today", "yesterday", and "tomorrow" using those labels; # if a :context date is provided, omit unnecessary date parts. if end_time.is_a? Hash opts = end_time end_time = nil else opts ||= {} end if end_time.nil? and start_time.respond_to?(:start_time) @start_time = start_time.start_time @end_time = start_time.end_time else @start_time = start_time @end_time = end_time end @format = opts[:format] || :hcal @relative = opts[:relative] || false @context_date = opts[:context] end def cache_key return [@start_time, @end_time, @format, @relative, @context_date].hash end def to_s return @@to_s_cache[cache_key] ||= self.to_s_raw.html_safe end def to_s_raw # Assume one date only, equal start/end start_format_list = [nil, :wday, :month, :day, :year, :at, :hour, :min, :suffix, nil] start_details = time_details(@start_time) if @end_time.nil? or @start_time == @end_time # One date only, or equal dates. end_format_list = conjunction = nil else end_details = time_details(@end_time) if @start_time.to_date == @end_time.to_date start_format_list[start_format_list.index(:at)] = :from conjunction = @format == :text ? " - " : " – " if start_details[:suffix] == end_details[:suffix] # same day & am/pm # Tuesday, April 1, 2008 from 9-11am start_format_list.delete(:suffix) end_format_list = [nil, :hour, :min, :suffix, nil] else # same day, different am/pm # Tuesday, April 1, 2008 from 9am-1:30pm end_format_list = [nil, :hour, :min, :suffix, nil] end else # different days: # Tuesday, April 1, 2008 at 9am through Wednesday, April 1, 2009 at 1:30pm end_format_list = start_format_list.clone conjunction = " through " end end # Remove stuff implied by our context if @context_date # Do it to both start & end lists [[@start_time, start_format_list], [@end_time, end_format_list]].each do |t, list| if t and list list.delete(:year) if @context_date.year == t.year # same year [:wday, :month, :day, :at, :from].each do |k| list.delete(k) end if @context_date == t.to_date end end end # Combine the pieces results = [] results << %Q|| if @format == :hcal results << format_details_by_list(start_details, start_format_list) results << %Q|| if @format == :hcal if end_format_list results << conjunction results << %Q|| if @format == :hcal results << format_details_by_list(end_details, end_format_list) results << %Q|| if @format == :hcal end results.join end protected PREFIXES = { hour: " ", [nil, :hour] => "", year: ", ", end_hour: " ", end_year: ", ", } SUFFIXES = { month: " ", wday: ", ", } STRINGS = { from: " from", at: " at", } def format_details_by_list(details, format_list) # Given a hash of date details, and a format_list of the keys # that should be emitted, produce a list of the pieces. # # Include any extra pieces implied by juxtaposition: eg, # if PREFIXES[:hour] is " ", include a " " piece just # before the hour, unless nil immediately # preceded :hour and we have a PREFIXES[[nil, :hour]], in # which case we'll emit that instead. results = [] format_list.each_cons(3) do |before, part, after| results << (PREFIXES[[before, part]] || PREFIXES[part]) results << (details[part] || STRINGS[part]) results << (SUFFIXES[[part, after]] || SUFFIXES[part]) end results end def date_details(d) # Get the parts for formatting a date, as a hash of # strings: keys (roughly) match the equivalent methods on Date, but only # relevant keys will be filled in. If relative is true (the default): # - if it's today, tomorrow, or yesterday, :wday will be eg "today" # (with no other date fields) case @relative && d.to_date - Date.today when 1 { wday: "tomorrow" } when 0 { wday: "today" } when -1 { wday: "yesterday" } else { wday: Date::DAYNAMES[d.wday], month: Date::MONTHNAMES[d.month], day: d.day.to_s, year: d.year.to_s } end end def time_details(t) # Get the parts for formatting this time, as a hash of # strings: keys (roughly) match the equivalent methods on DateTime, but only # relevant keys will be filled in. If relative is true (the default): # - if it's today, tomorrow, or yesterday, :wday will be eg "today" # (with no other date fields) # - if it's exactly noon or midnight, :hour will be eg "noon" # (with no other time fields) details = date_details(t) if t.min == 0 return details.merge(hour: "Midnight") if t.hour == 0 return details.merge(hour: "Noon") if t.hour == 12 end if t.hour >= 12 suffix = "pm" h = t.hour - (t.hour > 12 ? 12 : 0) else suffix = "am" h = t.hour == 0 ? 12 : t.hour end m = ":%02d" % t.min details.merge(hour: h.to_s, min: m, suffix: suffix) end end end