Created
July 9, 2010 22:16
-
-
Save TwP/470157 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A time interval represents a period of time between two instants. | |
# Intervals are inclusive of the start instant and exclusive of the end. | |
# The end instant is always greater than or equal to the start instant. | |
# | |
# Intervals have a fixed duration (or length) seconds. This is the | |
# difference between the start and end instants. | |
# | |
# Methods that are passed an interval as a parameter will treat +nil+ as a | |
# zero length interval at the current instant in time. | |
# | |
class TimeInterval | |
# call-seq: | |
# TimeInterval.to_mongo( value ) | |
# | |
# Convert the _value_ into a string representation suitable for storage in | |
# a Mongo database. | |
# | |
def self.to_mongo( value ) | |
return if value.blank? | |
return value unless value.is_a? Nevs::TimeInterval | |
value.to_mongo | |
end | |
# call-seq: | |
# TimeInterval.from_mongo( value ) | |
# | |
# Take the given string _value_ from a mongo database and convert it into | |
# a TimeInterval. | |
# | |
def self.from_mongo( value ) | |
return if value.blank? | |
return value if value.is_a? Nevs::TimeInterval | |
self.new(*value.to_s.unpack('N2')) | |
end | |
# call-seq: | |
# TimeInterval.new( start, end ) | |
# TimeInterval.new( start, duration ) | |
# | |
# Constructs a new time interval from the given _start_ and _end_ | |
# Time objects. The _end_ time must be greater than or equal to the | |
# _start_ time. | |
# | |
# Optionally, the _start_ can be given as a Time and a _duration_ can be | |
# given as an Integer number of seconds to offset from the _start_. | |
# | |
def initialize( s, e = nil ) | |
e = s + e if Time === s and Integer === e | |
@start = convert s | |
@end = e.nil? ? @start : convert(e) | |
if @start > @end | |
raise ArgumentError, | |
"start time cannot be greater than the end time: #{s.inspect} > #{e.inspect}" | |
end | |
end | |
# Is this time interval entirely after the _other_ time interval or | |
# instant? Intervals are inclusive of the start instant and exclusive of | |
# the end instant. | |
# | |
def after?( other = nil ) | |
other ||= Time.now.to_i | |
(self <=> other) == 1 | |
end | |
# Is this time interval entirely before the _other_ time interval or | |
# instant? Intervals are inclusive of the start instant and exclusive of | |
# the end instant. | |
# | |
def before?( other = nil ) | |
other ||= Time.now.to_i | |
(self <=> other) == -1 | |
end | |
# Does this time interval contain the _other_ time interval or instant? | |
# Intervals are inclusive of the start instant and exclusive of the end | |
# instant. | |
# | |
# The _other_ interval is contained if this interval wholly contains, | |
# starts, finishes or equals it. A zero duration interval cannot contain | |
# anything. | |
# | |
# When two intervals are compared the result is one of three states: (a) | |
# they abut, (b) there is a gap between them, (c) they overlap. The | |
# contains method is not related to these states. In particular, a zero | |
# duration interval is contained at the start of a larger interval, but | |
# does not overlap (it abuts instead). | |
# | |
def contains?( other = nil ) | |
other ||= Time.now.to_i | |
case other | |
when Time, Integer | |
return false if instant? | |
other = other.to_i | |
other < @end && other >= @start | |
when TimeInterval | |
return false if instant? | |
other.start >= @start && other.end <= @end | |
else raise ArgumentError, | |
"expecting a TimeInterval, Time, or Integer: `#{other.class}'" | |
end | |
end | |
# Does this time interval overlap with the _other_ time interval or | |
# instant? Intervals are inclusive of the start instant and exclusive of | |
# the end instant. An interval overlaps another if it shares some common | |
# part of the time continuum. | |
# | |
# When two intervals are compared the result is one of three states: (a) | |
# they abut, (b) there is a gap between them, (c) they overlap. The | |
# abuts state takes precedence over the other two, thus a zero duration | |
# interval at the start of a larger interval abuts and does not overlap. | |
# | |
def overlaps?( other = nil ) | |
other ||= Time.now.to_i | |
(self <=> other) == 0 | |
end | |
# Returns +true+ if this time interval represents a single instant in | |
# time. Returns +false+ otherwise. | |
# | |
def instant? | |
@start == @end | |
end | |
# Equality - compares this time interval to the _other_ object and | |
# returns true if the _other_ object is equivalent to this time | |
# interval. If another time interval is given, then the start and end | |
# times must agree with this time interval's start and end times. If a | |
# Time or Integer number of seconds is given, it will be equal if this | |
# time interval is an "instant" in time equivalent to the given time. | |
# | |
def equal?( other ) | |
case other | |
when Time, Integer | |
return false unless instant? | |
@start == other.to_i | |
when TimeInterval | |
@start == other.start && @end == other.end | |
else | |
super other | |
end | |
end | |
alias :== :equal? | |
# Comparison - compares this time interval to the _other_ time interval | |
# or time. Returns <tt>-1</tt> if this time interval lies entirely | |
# before the _other_ time interval. Returns <tt>1</tt> if this time | |
# interval lies entire after the _other_ time interval. Returns | |
# <tt>0</tt> if the two time intervals overlap. | |
# | |
# ==== Examples | |
# | |
# now = Time.now | |
# t1 = TimeInterval.new(now, 3600) | |
# t2 = TimeInterval.new(now+3600, 3600) | |
# t1 <=> t2 #=> -1 | |
# t2 <=> t1 #=> 1 | |
# t1 <=> t1 #=> 0 | |
# | |
def <=>( other ) | |
case other | |
when Time, Integer | |
other = other.to_i | |
return @start <=> other if instant? | |
return -1 if @end <= other | |
return 1 if @start > other | |
return 0 | |
when TimeInterval | |
return 0 if @start == other.start && @end == other.end | |
return -1 if @end <= other.start | |
return 1 if @start >= other.end | |
return 0 | |
else raise ArgumentError, | |
"expecting a TimeInterval, Time, or Integer: `#{other.class}'" | |
end | |
end | |
# Calculates the "distance" between this time interval and some _other_ | |
# time interval. This is the number of seconds between the two time | |
# intervals. This value will be zero if the time intervals overlap. | |
# | |
def distance( other = nil ) | |
other ||= Time.now.to_i | |
return 0 if equal? other | |
return 0 if overlaps? other | |
case other | |
when Time, Integer | |
other = other.to_i | |
return @end - 1 - other if @end <= other | |
@start - other | |
when TimeInterval | |
return @end - 1 - other.start if @end <= other.start | |
@start - other.end + 1 | |
else raise ArgumentError, | |
"expecting a TimeInterval, Time, or Integer: `#{other.class}'" | |
end | |
end | |
attr_reader :start, :end | |
# Sets the start of the time interval. The start _value_ can be either | |
# a Time instance or an Integer number of seconds. The start time must be | |
# less than the end time. | |
# | |
def start=( value ) | |
value = convert value | |
if value > @end | |
raise ArgumentError, | |
"start time cannot be greater than the end time: #{Time.at(value)} > #{end_time}" | |
end | |
@start = value | |
end | |
# Sets the end of the time interval. The end _value_ can be either a | |
# Time instance or an Integer number of seconds. The end time must be | |
# greater than the start time. | |
# | |
def end=( value ) | |
value = convert value | |
if value < @start | |
raise ArgumentError, | |
"start time cannot be greater than the end time: #{start_time.inspect} > #{Time.at(value).inspect}" | |
end | |
@end = value | |
end | |
# Returns the start of the time interval as a Time instance. The start time | |
# is inclusive to the interval. | |
# | |
def start_time | |
Time.at(@start).utc | |
end | |
# Returns the end of the time interval as a Time instance. The end time | |
# is exclusive to the interval. | |
# | |
def end_time | |
Time.at(@end).utc | |
end | |
# The length of the time interval in seconds. | |
# | |
def length | |
@end - @start | |
end | |
alias :duration :length | |
# Returns a string representation suitable for storage in a Mongo | |
# database. | |
# | |
def to_mongo | |
BSON::Binary.new([@start, @end].pack('N2')) | |
end | |
private | |
# Convert the given _time_ reference into an integer number of seconds. | |
# The _time_ reference is checked for type validity and temporal validity. | |
# Returns an integer greater than zero. | |
# | |
def convert( time ) | |
time = case time | |
when Time, Integer; time.to_i | |
else raise ArgumentError, | |
"times must be given as a Time instance or Integer number of seconds" | |
end | |
raise ArgumentError, "times must be greater than zero" if time <= 0 | |
return time | |
end | |
end # class TimeInterval |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment