lib/edtf/interval.rb in edtf-2.3.1 vs lib/edtf/interval.rb in edtf-3.0.0
- old
+ new
@@ -1,7 +1,7 @@
module EDTF
-
+
# An interval behaves like a regular Range but is dedicated to EDTF dates.
# Most importantly, intervals use the date's precision when generating
# the set of contained values and for membership tests. All tests are
# implemented without iteration and should therefore be considerably faster
# than if you were to use a regular Range.
@@ -19,62 +19,80 @@
# Date.edtf('2003/2006').cover? Date.edtf('2004-03') -> true
#
class Interval
extend Forwardable
-
+
include Comparable
include Enumerable
# Intervals delegate hash calculation to Ruby Range
def_delegators :to_range, :eql?, :hash
def_delegators :to_a, :length, :empty?
-
- attr_accessor :from, :to
+ attr_reader :from, :to
+
def initialize(from = Date.today, to = :open)
- @from, @to = from, to
+ self.from, self.to = from, to
end
-
+
+ def from=(date)
+ case date
+ when Date, :unknown
+ @from = date
+ else
+ throw ArgumentError.new("Intervals cannot start with: #{date}")
+ end
+ end
+
+ def to=(date)
+ case date
+ when Date, :unknown, :open
+ @to = date
+ else
+ throw ArgumentError.new("Intervals cannot end with: #{date}")
+ end
+ end
+
[:open, :unknown].each do |method_name|
define_method("#{method_name}_end!") do
@to = method_name
self
end
define_method("#{method_name}_end?") do
@to == method_name
end
end
-
+
alias open! open_end!
alias open? open_end?
-
+
def unknown_start?
from == :unknown
end
-
+
def unknown_start!
@from = :unknown
self
end
-
+
def unknown?
unknown_start? || unknown_end?
end
-
+
# Returns the intervals precision. Mixed precisions are currently not
# supported; in that case, the start date's precision takes precedence.
def precision
min.precision || max.precision
end
-
+
# Returns true if the precisions of start and end date are not the same.
def mixed_precision?
min.precision != max.precision
end
-
+
def each(&block)
step(1, &block)
end
@@ -88,67 +106,67 @@
# 2 months, or 2 years.
#
# If not block is given, returns an enumerator instead.
#
def step(by = 1)
- raise ArgumentError unless by.respond_to?(:to_i)
-
+ raise ArgumentError unless by.respond_to?(:to_i)
+
if block_given?
f, t, by = min, max, by.to_i
unless f.nil? || t.nil? || by < 1
by = { Date::PRECISIONS[precision] => by }
-
+
until f > t do
yield f
f = f.advance(by)
end
end
-
+
self
else
enum_for(:step, by)
end
end
-
+
# This method always returns false for Range compatibility. EDTF intervals
# always include the last date.
def exclude_end?
false
end
-
-
+
+
# TODO how to handle +/- Infinity for Dates?
# TODO we can't delegate to Ruby range for mixed precision intervals
-
+
# Returns the Interval as a Range.
def to_range
case
when open?, unknown?
nil
else
Range.new(unknown_start? ? Date.new : @from, max)
end
end
-
+
# Returns true if other is an element of the Interval, false otherwise.
# Comparision is done according to the Interval's min/max date and
# precision.
def include?(other)
cover?(other) && precision == other.precision
end
alias member? include?
-
+
# Returns true if other is an element of the Interval, false otherwise.
# In contrast to #include? and #member? this method does not take into
- # account the date's precision.
+ # account the date's precision.
def cover?(other)
return false unless other.is_a?(Date)
-
+
other = other.day_precision
-
+
case
when unknown_start?
max.day_precision! == other
when unknown_end?
min.day_precision! == other
@@ -156,37 +174,37 @@
min.day_precision! <= other
else
min.day_precision! <= other && other <= max.day_precision!
end
end
-
+
# call-seq:
# interval.first -> Date or nil
# interval.first(n) -> Array
#
# Returns the first date in the interval, or the first n dates.
def first(n = 1)
if n > 1
- (ds = Array(min)).empty? ? ds : ds.concat(ds[0].next(n - 1))
+ (ds = Array(min)).empty? ? ds : ds.concat(ds[0].next(n - 1))
else
min
end
end
-
+
# call-seq:
# interval.last -> Date or nil
# interval.last(n) -> Array
#
# Returns the last date in the interval, or the last n dates.
def last(n = 1)
if n > 1
- (ds = Array(max)).empty? ? ds : ds.concat(ds[0].prev(n - 1))
+ (ds = Array(max)).empty? ? ds : ds.concat(ds[0].prev(n - 1))
else
max
end
end
-
+
# call-seq:
# interval.min -> Date or nil
# interval.min { |a,b| block } -> Date or nil
#
# Returns the minimum value in the interval. If a block is given, it is
@@ -207,15 +225,15 @@
else
from.beginning_of_year
end
end
end
-
+
def begin
min
end
-
+
# call-seq:
# interval.max -> Date or nil
# interval.max { |a,b| block } -> Date or nil
#
# Returns the maximum value in the interval. If a block is given, it is
@@ -231,55 +249,55 @@
to_a.max(&Proc.new)
else
case
when open_end?, unknown_end?, !unknown_start? && to < from
nil
- when to.day_precision?
+ when to.day_precision?
to
when to.month_precision?
to.end_of_month
else
to.end_of_year
end
end
end
-
+
def end
max
end
-
+
def <=>(other)
case other
when Interval, Season, Epoch
[min, max] <=> [other.min, other.max]
when Date
cover?(other) ? min <=> other : 0
else
nil
end
end
-
+
def ===(other)
case other
when Interval
cover?(other.min) && cover?(other.max)
when Date
cover?(other)
else
false
end
end
-
-
+
+
# Returns the Interval as an EDTF string.
def edtf
[
from.send(from.respond_to?(:edtf) ? :edtf : :to_s),
to.send(to.respond_to?(:edtf) ? :edtf : :to_s)
] * '/'
end
-
+
alias to_s edtf
-
+
end
-
+
end