app/helpers/open_conference_ware/time_range_helper.rb
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 <abbr> 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|<abbr class="dtstart" title="#{@start_time.strftime('%Y-%m-%dT%H:%M:%S')}">| if @format == :hcal
results << format_details_by_list(start_details, start_format_list)
results << %Q|</abbr>| if @format == :hcal
if end_format_list
results << conjunction
results << %Q|<abbr class="dtend" title="#{@end_time.strftime('%Y-%m-%dT%H:%M:%S')}">| if @format == :hcal
results << format_details_by_list(end_details, end_format_list)
results << %Q|</abbr>| 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