edtf 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +49 -19
- data/edtf.gemspec +1 -1
- data/features/parser/dates.feature +23 -10
- data/features/parser/intervals.feature +1 -1
- data/features/print/level_0_edtf.feature +1 -1
- data/features/print/level_1_edtf.feature +93 -2
- data/features/print/level_2_edtf.feature +31 -0
- data/features/print/uncertain_or_approximate.feature +134 -0
- data/features/step_definitions/edtf_steps.rb +35 -1
- data/lib/edtf.rb +16 -5
- data/lib/edtf/compatibility.rb +5 -16
- data/lib/edtf/date.rb +151 -35
- data/lib/edtf/date_time.rb +5 -5
- data/lib/edtf/interval.rb +243 -44
- data/lib/edtf/parser.y +39 -30
- data/lib/edtf/season.rb +16 -15
- data/lib/edtf/uncertainty.rb +57 -21
- data/lib/edtf/version.rb +1 -1
- data/spec/edtf/date_spec.rb +130 -3
- data/spec/edtf/interval_spec.rb +132 -4
- data/spec/edtf/parser_spec.rb +56 -35
- data/spec/edtf/uncertainty_spec.rb +167 -4
- metadata +20 -16
@@ -12,7 +12,7 @@ Then /^the EDTF String should be "([^"]*)"$/i do |edtf|
|
|
12
12
|
end
|
13
13
|
|
14
14
|
When /^I parse the string "([^"]*)"$/ do |string|
|
15
|
-
@date = EDTF.parse(string)
|
15
|
+
@date = EDTF.parse!(string)
|
16
16
|
end
|
17
17
|
|
18
18
|
Then /^the year should be "([^"]*)"$/ do |year|
|
@@ -72,6 +72,10 @@ Then /^the interval should include the date "([^"]*)"$/ do |date|
|
|
72
72
|
@date.should include(Date.parse(date))
|
73
73
|
end
|
74
74
|
|
75
|
+
Then /^the interval should cover the date "([^"]*)"$/ do |date|
|
76
|
+
@date.should cover(Date.parse(date))
|
77
|
+
end
|
78
|
+
|
75
79
|
|
76
80
|
Then /^the date should be uncertain\? "([^"]*)"$/ do |arg1|
|
77
81
|
@date.uncertain?.should == !!(arg1 =~ /y(es)?/i)
|
@@ -109,3 +113,33 @@ end
|
|
109
113
|
Then /^the unspecified string code be "([^"]*)"$/ do |arg1|
|
110
114
|
@date.unspecified.to_s.should == arg1
|
111
115
|
end
|
116
|
+
|
117
|
+
When /^I parse the following strings an error should be raised:$/ do |table|
|
118
|
+
table.raw.each do |row|
|
119
|
+
expect { Date.edtf!(row[0]) }.to raise_error(ArgumentError)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
When /^the year is uncertain: "([^"]*)"$/ do |arg1|
|
124
|
+
@date.uncertain!(:year) if arg1 =~ /y(es)?/i
|
125
|
+
end
|
126
|
+
|
127
|
+
When /^the month is uncertain: "([^"]*)"$/ do |arg1|
|
128
|
+
@date.uncertain!(:month) if arg1 =~ /y(es)?/i
|
129
|
+
end
|
130
|
+
|
131
|
+
When /^the day is uncertain: "([^"]*)"$/ do |arg1|
|
132
|
+
@date.uncertain!(:day) if arg1 =~ /y(es)?/i
|
133
|
+
end
|
134
|
+
|
135
|
+
When /^the year is approximate: "([^"]*)"$/ do |arg1|
|
136
|
+
@date.approximate!(:year) if arg1 =~ /y(es)?/i
|
137
|
+
end
|
138
|
+
|
139
|
+
When /^the month is approximate: "([^"]*)"$/ do |arg1|
|
140
|
+
@date.approximate!(:month) if arg1 =~ /y(es)?/i
|
141
|
+
end
|
142
|
+
|
143
|
+
When /^the day is approximate "([^"]*)"$/ do |arg1|
|
144
|
+
@date.approximate!(:day) if arg1 =~ /y(es)?/i
|
145
|
+
end
|
data/lib/edtf.rb
CHANGED
@@ -28,18 +28,23 @@
|
|
28
28
|
# policies, either expressed or implied, of the copyright holder.
|
29
29
|
#++
|
30
30
|
|
31
|
+
if ENV['DEBUG']
|
32
|
+
require 'ruby-debug'
|
33
|
+
Debugger.start
|
34
|
+
end
|
35
|
+
|
31
36
|
require 'date'
|
32
37
|
require 'time'
|
33
38
|
|
34
39
|
autoload :Rational, 'rational'
|
35
40
|
|
36
41
|
require 'forwardable'
|
42
|
+
require 'enumerator'
|
37
43
|
|
38
44
|
require 'active_support/core_ext/date/calculations'
|
39
45
|
require 'active_support/core_ext/date_time/calculations'
|
40
46
|
require 'active_support/core_ext/time/calculations'
|
41
47
|
|
42
|
-
|
43
48
|
require 'active_support/core_ext/date/conversions'
|
44
49
|
|
45
50
|
require 'edtf/compatibility'
|
@@ -63,11 +68,17 @@ require 'edtf/extensions'
|
|
63
68
|
# To parse EDTF strings use either `Date.edtf` of `EDTF.parse`.
|
64
69
|
#
|
65
70
|
module EDTF
|
66
|
-
|
71
|
+
|
72
|
+
module_function
|
73
|
+
|
67
74
|
def parse(input, options = {})
|
68
|
-
|
75
|
+
parse!(input, options)
|
76
|
+
rescue
|
77
|
+
nil
|
69
78
|
end
|
70
79
|
|
71
|
-
|
72
|
-
|
80
|
+
def parse!(input, options = {})
|
81
|
+
::Date.edtf!(input, options)
|
82
|
+
end
|
83
|
+
|
73
84
|
end
|
data/lib/edtf/compatibility.rb
CHANGED
@@ -1,19 +1,8 @@
|
|
1
1
|
|
2
|
-
# unless DateTime.respond_to?(:to_time)
|
3
|
-
# require 'time'
|
4
|
-
#
|
5
|
-
# class DateTime
|
6
|
-
# def to_time
|
7
|
-
# Time.parse(to_s)
|
8
|
-
# end
|
9
|
-
# end
|
10
|
-
# end
|
11
|
-
|
12
|
-
|
13
2
|
class DateTime
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
3
|
+
|
4
|
+
def iso8601
|
5
|
+
to_time.iso8601
|
6
|
+
end unless method_defined?(:iso8601)
|
7
|
+
|
19
8
|
end
|
data/lib/edtf/date.rb
CHANGED
@@ -1,34 +1,66 @@
|
|
1
1
|
class Date
|
2
2
|
|
3
|
-
|
3
|
+
PRECISION = [:year, :month, :day].freeze
|
4
|
+
PRECISIONS = Hash[*PRECISION.map { |p| [p, "#{p}s".to_sym] }.flatten].freeze
|
5
|
+
|
4
6
|
FORMATS = %w{ %04d %02d %02d }.freeze
|
5
7
|
|
8
|
+
SYMBOLS = {
|
9
|
+
:uncertain => '?',
|
10
|
+
:approximate => '~',
|
11
|
+
:calendar => '^',
|
12
|
+
:unspecified => 'u'
|
13
|
+
}.freeze
|
14
|
+
|
6
15
|
EXTENDED_ATTRIBUTES = %w{ calendar precision uncertain approximate
|
7
16
|
unspecified }.map(&:to_sym).freeze
|
8
17
|
|
9
18
|
extend Forwardable
|
10
19
|
|
11
20
|
class << self
|
21
|
+
|
12
22
|
def edtf(input, options = {})
|
13
|
-
|
23
|
+
edtf!(input, options)
|
24
|
+
rescue
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def edtf!(input, options = {})
|
29
|
+
::EDTF::Parser.new(options).parse!(input)
|
14
30
|
end
|
15
31
|
end
|
16
32
|
|
17
33
|
attr_accessor :calendar
|
18
34
|
|
19
|
-
|
20
|
-
define_method("#{p}_precision?") {
|
21
|
-
|
35
|
+
PRECISION.each do |p|
|
36
|
+
define_method("#{p}_precision?") { precision == p }
|
37
|
+
|
38
|
+
define_method("#{p}_precision!") do
|
39
|
+
self.precision = p
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
define_method("#{p}_precision") do
|
44
|
+
change(:precision => p)
|
45
|
+
end
|
22
46
|
end
|
23
47
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
d.unspecified = unspecified.dup
|
29
|
-
d
|
48
|
+
|
49
|
+
def initialize_copy(other)
|
50
|
+
super
|
51
|
+
copy_extended_attributes(other)
|
30
52
|
end
|
31
53
|
|
54
|
+
|
55
|
+
# Alias advance method from Active Support.
|
56
|
+
alias original_advance advance
|
57
|
+
|
58
|
+
# Provides precise Date calculations for years, months, and days. The +options+ parameter takes a hash with
|
59
|
+
# any of these keys: <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>.
|
60
|
+
def advance(options)
|
61
|
+
original_advance(options).copy_extended_attributes(self)
|
62
|
+
end
|
63
|
+
|
32
64
|
# Alias change method from Active Support.
|
33
65
|
alias original_change change
|
34
66
|
|
@@ -41,6 +73,7 @@ class Date
|
|
41
73
|
d
|
42
74
|
end
|
43
75
|
|
76
|
+
|
44
77
|
# Returns this Date's precision.
|
45
78
|
def precision
|
46
79
|
@precision ||= :day
|
@@ -49,21 +82,17 @@ class Date
|
|
49
82
|
# Sets this Date/Time's precision to the passed-in value.
|
50
83
|
def precision=(precision)
|
51
84
|
precision = precision.to_sym
|
52
|
-
raise ArgumentError, "invalid precision #{precision.inspect}" unless
|
85
|
+
raise ArgumentError, "invalid precision #{precision.inspect}" unless PRECISION.include?(precision)
|
53
86
|
@precision = precision
|
54
87
|
update_precision_filter[-1]
|
55
88
|
end
|
56
89
|
|
57
|
-
def self.included(base)
|
58
|
-
base.extend(ClassMethods)
|
59
|
-
end
|
60
|
-
|
61
90
|
def uncertain
|
62
91
|
@uncertain ||= EDTF::Uncertainty.new
|
63
92
|
end
|
64
93
|
|
65
94
|
def approximate
|
66
|
-
@approximate ||= EDTF::Uncertainty.new
|
95
|
+
@approximate ||= EDTF::Uncertainty.new(nil, nil, nil, 8)
|
67
96
|
end
|
68
97
|
|
69
98
|
def unspecified
|
@@ -108,7 +137,7 @@ class Date
|
|
108
137
|
|
109
138
|
alias precisely! precise!
|
110
139
|
|
111
|
-
def_delegators :unspecified, :unspecified?, :specified?, :
|
140
|
+
def_delegators :unspecified, :unspecified?, :specified?, :unspecific?, :specific?
|
112
141
|
|
113
142
|
def unspecified!(arguments = precision_filter)
|
114
143
|
unspecified.unspecified!(arguments)
|
@@ -124,35 +153,103 @@ class Date
|
|
124
153
|
|
125
154
|
alias specific! specified!
|
126
155
|
|
156
|
+
# Returns false for Dates.
|
127
157
|
def season?; false; end
|
128
158
|
|
159
|
+
# Returns true if the Date has an EDTF calendar string attached.
|
160
|
+
def calendar?; !!@calendar; end
|
161
|
+
|
162
|
+
# Converts the Date into a season.
|
129
163
|
def season
|
130
164
|
Season.new(self)
|
131
165
|
end
|
132
166
|
|
167
|
+
# Returns the Date's EDTF string.
|
133
168
|
def edtf
|
134
|
-
|
169
|
+
return "y#{year}" if long_year?
|
170
|
+
|
171
|
+
s = FORMATS.take(values.length).zip(values).map { |f,v| f % v }
|
172
|
+
s = unspecified.mask(s)
|
173
|
+
|
174
|
+
unless (h = ua_hash).zero?
|
175
|
+
#
|
176
|
+
# To efficiently calculate the uncertain/approximate state we use
|
177
|
+
# the bitmask. The primary flags are:
|
178
|
+
#
|
179
|
+
# Uncertain: 1 - year, 2 - month, 4 - day
|
180
|
+
# Approximate: 8 - year, 16 - month, 32 - day
|
181
|
+
#
|
182
|
+
# Invariant: assumes that uncertain/approximate are not set for values
|
183
|
+
# not covered by precision!
|
184
|
+
#
|
185
|
+
y, m, d = s
|
186
|
+
|
187
|
+
# ?/~ if true-false or true-true and other false-true
|
188
|
+
y << SYMBOLS[:uncertain] if 3&h==1 || 27&h==19
|
189
|
+
y << SYMBOLS[:approximate] if 24&h==8 || 27&h==26
|
190
|
+
|
191
|
+
|
192
|
+
# combine if false-true-true and other m == d
|
193
|
+
if 7&h==6 && (48&h==48 || 48&h==0) || 56&h==48 && (6&h==6 || 6&h==0)
|
194
|
+
m[0,0] = '('
|
195
|
+
d << ')'
|
196
|
+
else
|
197
|
+
case
|
198
|
+
# false-true
|
199
|
+
when 3&h==2 || 24&h==16
|
200
|
+
m[0,0] = '('
|
201
|
+
m << ')'
|
202
|
+
|
203
|
+
# *-false-true
|
204
|
+
when 6&h==4 || 48&h==32
|
205
|
+
d[0,0] = '('
|
206
|
+
d << ')'
|
207
|
+
end
|
208
|
+
|
209
|
+
# ?/~ if *-true-false or *-true-true and other m != d
|
210
|
+
m << SYMBOLS[:uncertain] if h!=31 && (6&h==2 || 6&h==6 && (48&h==16 || 48&h==32))
|
211
|
+
m << SYMBOLS[:approximate] if h!=59 && (48&h==16 || 48&h==48 && (6&h==2 || 6&h==4))
|
212
|
+
end
|
213
|
+
|
214
|
+
# ?/~ if *-*-true
|
215
|
+
d << SYMBOLS[:uncertain] if 4&h==4
|
216
|
+
d << SYMBOLS[:approximate] if 32&h==32
|
217
|
+
end
|
218
|
+
|
219
|
+
s = s.join('-')
|
220
|
+
s << SYMBOLS[:calendar] << calendar if calendar?
|
221
|
+
s
|
135
222
|
end
|
136
223
|
|
137
224
|
alias to_edtf edtf
|
138
225
|
|
139
|
-
# Returns
|
226
|
+
# Returns the Date of the next day, month, or year depending on the
|
140
227
|
# current Date/Time's precision.
|
141
|
-
def next
|
142
|
-
|
228
|
+
def next(n = 1)
|
229
|
+
if n > 1
|
230
|
+
1.upto(n).map { |by| advance(PRECISIONS[precision] => by) }
|
231
|
+
else
|
232
|
+
advance(PRECISIONS[precision] => 1)
|
233
|
+
end
|
143
234
|
end
|
144
|
-
|
145
|
-
# def succ
|
146
|
-
# end
|
147
235
|
|
148
|
-
|
149
|
-
# end
|
236
|
+
alias succ next
|
150
237
|
|
151
|
-
|
152
|
-
|
238
|
+
# Returns the Date of the previous day, month, or year depending on the
|
239
|
+
# current Date/Time's precision.
|
240
|
+
def prev(n = 1)
|
241
|
+
if n > 1
|
242
|
+
1.upto(n).map { |by| advance(PRECISIONS[precision] => -by) }
|
243
|
+
else
|
244
|
+
advance(PRECISIONS[precision] => -1)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def <=>(other)
|
249
|
+
return nil unless other.is_a?(::Date)
|
250
|
+
values <=> other.values
|
251
|
+
end
|
153
252
|
|
154
|
-
# def ===(other)
|
155
|
-
# end
|
156
253
|
|
157
254
|
# Returns an array of the current year, month, and day values filtered by
|
158
255
|
# the Date/Time's precision.
|
@@ -160,16 +257,24 @@ class Date
|
|
160
257
|
precision_filter.map { |p| send(p) }
|
161
258
|
end
|
162
259
|
|
163
|
-
# Returns the same date with negated year
|
260
|
+
# Returns the same date but with negated year.
|
164
261
|
def negate
|
165
262
|
change(:year => year * -1)
|
166
263
|
end
|
167
264
|
|
168
265
|
alias -@ negate
|
169
266
|
|
170
|
-
|
267
|
+
# Returns true if this Date/Time has year precision and the year exceeds four digits.
|
268
|
+
def long_year?
|
269
|
+
precision == :year && year.abs > 9999
|
270
|
+
end
|
271
|
+
|
171
272
|
private
|
172
|
-
|
273
|
+
|
274
|
+
def ua_hash
|
275
|
+
uncertain.hash + approximate.hash
|
276
|
+
end
|
277
|
+
|
173
278
|
def precision_filter
|
174
279
|
@precision_filter ||= update_precision_filter
|
175
280
|
end
|
@@ -188,5 +293,16 @@ class Date
|
|
188
293
|
protected
|
189
294
|
|
190
295
|
attr_writer :uncertain, :unspecified, :approximate
|
191
|
-
|
296
|
+
|
297
|
+
def copy_extended_attributes(other)
|
298
|
+
@uncertain = other.uncertain.dup
|
299
|
+
@approximate = other.approximate.dup
|
300
|
+
@unspecified = other.unspecified.dup
|
301
|
+
|
302
|
+
@calendar = other.calendar.dup if other.calendar?
|
303
|
+
@precision = other.precision
|
304
|
+
|
305
|
+
self
|
306
|
+
end
|
307
|
+
|
192
308
|
end
|
data/lib/edtf/date_time.rb
CHANGED
data/lib/edtf/interval.rb
CHANGED
@@ -1,86 +1,285 @@
|
|
1
1
|
module EDTF
|
2
2
|
|
3
|
+
# An interval behaves like a regular Range but is dedicated to EDTF dates.
|
4
|
+
# Most importantly, intervals use the date's precision when generating
|
5
|
+
# the set of contained values and for membership tests. All tests are
|
6
|
+
# implemented without iteration and should therefore be considerably faster
|
7
|
+
# than if you were to use a regular Range.
|
8
|
+
#
|
9
|
+
# For example, the interval "2003/2006" covers the years 2003, 2004, 2005
|
10
|
+
# and 2006. Converting the interval to an array would result in a an array
|
11
|
+
# containing exactly four dates with year precision. This is also reflected
|
12
|
+
# in membership tests.
|
13
|
+
#
|
14
|
+
# Date.edtf('2003/2006').length -> 4
|
15
|
+
#
|
16
|
+
# Date.edtf('2003/2006').include? Date.edtf('2004') -> true
|
17
|
+
# Date.edtf('2003/2006').include? Date.edtf('2004-03') -> false
|
18
|
+
#
|
19
|
+
# Date.edtf('2003/2006').cover? Date.edtf('2004-03') -> true
|
20
|
+
#
|
3
21
|
class Interval
|
4
22
|
|
5
23
|
extend Forwardable
|
6
|
-
|
7
|
-
include Enumerable
|
8
24
|
|
9
|
-
|
25
|
+
include Comparable
|
26
|
+
include Enumerable
|
10
27
|
|
11
|
-
|
28
|
+
# Intervals delegate hash calculation to Ruby Range
|
29
|
+
def_delegators :to_range, :eql?, :hash
|
30
|
+
def_delegators :to_a, :length, :empty?
|
12
31
|
|
13
|
-
|
32
|
+
attr_accessor :from, :to
|
33
|
+
|
34
|
+
def initialize(from = Date.today, to = :open)
|
14
35
|
@from, @to = from, to
|
15
36
|
end
|
16
37
|
|
17
|
-
def from=(from)
|
18
|
-
@from = from || :open
|
19
|
-
end
|
20
|
-
|
21
|
-
def to=(to)
|
22
|
-
@to = to || :open
|
23
|
-
end
|
24
|
-
|
25
38
|
[:open, :unknown].each do |method_name|
|
26
|
-
|
27
|
-
define_method("#{method_name}?") do
|
28
|
-
@to == method_name || @from == method_name
|
29
|
-
end
|
30
|
-
|
31
|
-
define_method("#{method_name}!") do
|
39
|
+
define_method("#{method_name}_end!") do
|
32
40
|
@to = method_name
|
41
|
+
self
|
33
42
|
end
|
34
43
|
|
35
|
-
alias_method("#{method_name}_end!", "#{method_name}!")
|
36
|
-
|
37
44
|
define_method("#{method_name}_end?") do
|
38
45
|
@to == method_name
|
39
46
|
end
|
40
|
-
|
41
47
|
end
|
42
|
-
|
48
|
+
|
49
|
+
alias open! open_end!
|
50
|
+
alias open? open_end?
|
51
|
+
|
43
52
|
def unknown_start?
|
44
|
-
|
53
|
+
from == :unknown
|
45
54
|
end
|
46
55
|
|
47
56
|
def unknown_start!
|
48
57
|
@from = :unknown
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def unknown?
|
62
|
+
unknown_start? || unknown_end?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns the intervals precision. Mixed precisions are currently not
|
66
|
+
# supported; in that case, the start date's precision takes precedence.
|
67
|
+
def precision
|
68
|
+
min.precision || max.precision
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns true if the precisions of start and end date are not the same.
|
72
|
+
def mixed_precision?
|
73
|
+
min.precsion != max.precision
|
49
74
|
end
|
50
75
|
|
76
|
+
def each(&block)
|
77
|
+
step(1, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
# call-seq:
|
82
|
+
# interval.step(by=1) { |date| block } -> self
|
83
|
+
# interval.step(by=1) -> Enumerator
|
84
|
+
#
|
85
|
+
# Iterates over the interval by passing by elements at each step and
|
86
|
+
# yielding each date to the passed-in block. Note that the semantics
|
87
|
+
# of by are precision dependent: e.g., a value of 2 can mean 2 days,
|
88
|
+
# 2 months, or 2 years.
|
89
|
+
#
|
90
|
+
# If not block is given, returns an enumerator instead.
|
91
|
+
#
|
92
|
+
def step(by = 1)
|
93
|
+
raise ArgumentError unless by.respond_to?(:to_i)
|
94
|
+
|
95
|
+
if block_given?
|
96
|
+
f, t, by = min, max, by.to_i
|
97
|
+
|
98
|
+
unless f.nil? || t.nil? || by < 1
|
99
|
+
by = { Date::PRECISIONS[precision] => by }
|
100
|
+
|
101
|
+
until f > t do
|
102
|
+
yield f
|
103
|
+
f = f.advance(by)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
self
|
108
|
+
else
|
109
|
+
enum_for(:step, by)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# This method always returns false for Range compatibility. EDTF intervals
|
115
|
+
# always include the last date.
|
116
|
+
def exclude_end?
|
117
|
+
false
|
118
|
+
end
|
119
|
+
|
120
|
+
|
51
121
|
# TODO how to handle +/- Infinity for Dates?
|
122
|
+
# TODO we can't delegate to Ruby range for mixed precision intervals
|
52
123
|
|
124
|
+
# Returns the Interval as a Range.
|
53
125
|
def to_range
|
54
126
|
case
|
55
|
-
when open?
|
56
|
-
nil
|
57
|
-
when unknown_end?
|
127
|
+
when open?, unknown?
|
58
128
|
nil
|
59
129
|
else
|
60
|
-
Range.new(unknown_start? ? Date.new : @from,
|
130
|
+
Range.new(unknown_start? ? Date.new : @from, max)
|
61
131
|
end
|
62
132
|
end
|
63
133
|
|
64
|
-
|
134
|
+
# Returns true if other is an element of the Interval, false otherwise.
|
135
|
+
# Comparision is done according to the Interval's min/max date and
|
136
|
+
# precision.
|
137
|
+
def include?(other)
|
138
|
+
cover?(other) && precision == other.precision
|
139
|
+
end
|
140
|
+
alias member? include?
|
141
|
+
|
142
|
+
# Returns true if other is an element of the Interval, false otherwise.
|
143
|
+
# In contrast to #include? and #member? this method does not take into
|
144
|
+
# account the date's precision.
|
145
|
+
def cover?(other)
|
146
|
+
return false unless other.is_a?(Date)
|
147
|
+
|
148
|
+
other = other.day_precision
|
149
|
+
|
65
150
|
case
|
66
|
-
when
|
67
|
-
|
68
|
-
when
|
69
|
-
|
151
|
+
when unknown_start?
|
152
|
+
max.day_precision! == other
|
153
|
+
when unknown_end?
|
154
|
+
min.day_precision! == other
|
155
|
+
when open_end?
|
156
|
+
min.day_precision! <= other
|
157
|
+
else
|
158
|
+
min.day_precision! <= other && other <= max.day_precision!
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# call-seq:
|
163
|
+
# interval.first -> Date or nil
|
164
|
+
# interval.first(n) -> Array
|
165
|
+
#
|
166
|
+
# Returns the first date in the interval, or the first n dates.
|
167
|
+
def first(n = 1)
|
168
|
+
if n > 1
|
169
|
+
(ds = Array(min)).empty? ? ds : ds.concat(ds[0].next(n - 1))
|
170
|
+
else
|
171
|
+
min
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# call-seq:
|
176
|
+
# interval.last -> Date or nil
|
177
|
+
# interval.last(n) -> Array
|
178
|
+
#
|
179
|
+
# Returns the last date in the interval, or the last n dates.
|
180
|
+
def last(n = 1)
|
181
|
+
if n > 1
|
182
|
+
(ds = Array(max)).empty? ? ds : ds.concat(ds[0].prev(n - 1))
|
183
|
+
else
|
184
|
+
max
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# call-seq:
|
189
|
+
# interval.min -> Date or nil
|
190
|
+
# interval.min { |a,b| block } -> Date or nil
|
191
|
+
#
|
192
|
+
# Returns the minimum value in the interval. If a block is given, it is
|
193
|
+
# used to compare values (slower). Returns nil if the first date of the
|
194
|
+
# interval is larger than the last or if the interval has an unknown or
|
195
|
+
# open start.
|
196
|
+
def min
|
197
|
+
if block_given?
|
198
|
+
to_a.min(&Proc.new)
|
199
|
+
else
|
200
|
+
case
|
201
|
+
when unknown_start?, !open? && to < from
|
202
|
+
nil
|
203
|
+
when from.day_precision?
|
204
|
+
from
|
205
|
+
when from.month_precision?
|
206
|
+
from.beginning_of_month
|
207
|
+
else
|
208
|
+
from.beginning_of_year
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def begin
|
214
|
+
min
|
215
|
+
end
|
216
|
+
|
217
|
+
# call-seq:
|
218
|
+
# interval.max -> Date or nil
|
219
|
+
# interval.max { |a,b| block } -> Date or nil
|
220
|
+
#
|
221
|
+
# Returns the maximum value in the interval. If a block is given, it is
|
222
|
+
# used to compare values (slower). Returns nil if the first date of the
|
223
|
+
# interval is larger than the last or if the interval has an unknown or
|
224
|
+
# open end.
|
225
|
+
#
|
226
|
+
# To calculate the dates, precision is taken into account. Thus, the max
|
227
|
+
# Date of "2007/2008" would be 2008-12-31, whilst the max Date of
|
228
|
+
# "2007-12/2008-10" would be 2009-10-31.
|
229
|
+
def max
|
230
|
+
if block_given?
|
231
|
+
to_a.max(&Proc.new)
|
232
|
+
else
|
233
|
+
case
|
234
|
+
when open_end?, unknown_end?, !unknown_start? && to < from
|
235
|
+
nil
|
236
|
+
when to.day_precision?
|
237
|
+
to
|
238
|
+
when to.month_precision?
|
239
|
+
to.end_of_month
|
240
|
+
else
|
241
|
+
to.end_of_year
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def end
|
247
|
+
max
|
248
|
+
end
|
249
|
+
|
250
|
+
def <=>(other)
|
251
|
+
case other
|
252
|
+
when Interval
|
253
|
+
[min, max] <=> [other.min, other.max]
|
254
|
+
when Date
|
255
|
+
cover?(other) ? min <=> other : 0
|
70
256
|
else
|
71
|
-
|
257
|
+
nil
|
72
258
|
end
|
73
259
|
end
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
260
|
+
|
261
|
+
def ===(other)
|
262
|
+
case other
|
263
|
+
when Interval
|
264
|
+
cover?(other.min) && cover?(other.max)
|
265
|
+
when Date
|
266
|
+
cover?(other)
|
267
|
+
else
|
268
|
+
false
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
# Returns the Interval as an EDTF string.
|
274
|
+
def edtf
|
275
|
+
[
|
276
|
+
from.send(from.respond_to?(:edtf) ? :edtf : :to_s),
|
277
|
+
to.send(to.respond_to?(:edtf) ? :edtf : :to_s)
|
278
|
+
] * '/'
|
279
|
+
end
|
280
|
+
|
281
|
+
alias to_s edtf
|
282
|
+
|
84
283
|
end
|
85
284
|
|
86
285
|
end
|