edtf 0.0.6 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|