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
data/lib/edtf/parser.y
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class EDTF::Parser
|
4
4
|
|
5
|
-
token T Z E X U UNKNOWN OPEN LONGYEAR UNMATCHED DOTS
|
5
|
+
token T Z E X U UNKNOWN OPEN LONGYEAR UNMATCHED DOTS PUA
|
6
6
|
|
7
7
|
expect 0
|
8
8
|
|
@@ -133,7 +133,7 @@ rule
|
|
133
133
|
result = Interval.new(val[0], val[2])
|
134
134
|
}
|
135
135
|
|
136
|
-
level_1_start : date |
|
136
|
+
level_1_start : date | partial_uncertain_or_approximate | unspecified | partial_unspecified | UNKNOWN
|
137
137
|
|
138
138
|
level_1_end : level_1_start | OPEN
|
139
139
|
|
@@ -159,7 +159,7 @@ rule
|
|
159
159
|
;
|
160
160
|
|
161
161
|
|
162
|
-
season : year '-' season_number { result = Season.new(val[0], val[2]) }
|
162
|
+
season : year '-' season_number opt_ua { result = Season.new(val[0], val[2]) }
|
163
163
|
|
164
164
|
season_number : '2' '1' { result = 21 }
|
165
165
|
| '2' '2' { result = 22 }
|
@@ -172,8 +172,8 @@ rule
|
|
172
172
|
|
173
173
|
# NB: Level 2 Intervals are covered by the Level 1 Interval rules.
|
174
174
|
level_2_expression : season_qualified
|
175
|
-
|
|
176
|
-
|
|
175
|
+
| partial_uncertain_or_approximate
|
176
|
+
| partial_unspecified
|
177
177
|
| choice_list
|
178
178
|
| inclusive_list
|
179
179
|
| masked_precision
|
@@ -239,7 +239,7 @@ rule
|
|
239
239
|
;
|
240
240
|
|
241
241
|
list_element : date
|
242
|
-
|
|
242
|
+
| partial_uncertain_or_approximate
|
243
243
|
| unspecified
|
244
244
|
| consecutives { result = val[0].map { |d| Date.new(*d) } }
|
245
245
|
;
|
@@ -256,7 +256,7 @@ rule
|
|
256
256
|
| year DOTS year { result = (val[0]..val[2]).to_a.map }
|
257
257
|
;
|
258
258
|
|
259
|
-
|
259
|
+
partial_unspecified :
|
260
260
|
unspecified_year '-' month '-' d01_31
|
261
261
|
{
|
262
262
|
result = Date.new(val[0][0], val[2], val[4])
|
@@ -287,22 +287,23 @@ rule
|
|
287
287
|
}
|
288
288
|
;
|
289
289
|
|
290
|
-
internal_uncertain_or_approximate_date : internal_uncertain_or_approximate
|
291
|
-
| '(' internal_uncertain_or_approximate ')' ua { result = uoa(val[1], val[3]) }
|
292
290
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
291
|
+
partial_uncertain_or_approximate : pua_base
|
292
|
+
| '(' pua_base ')' ua { result = uoa(val[1], val[3]) }
|
293
|
+
|
294
|
+
pua_base :
|
295
|
+
pua_year { result = val[0]; result.precision = :year }
|
296
|
+
| pua_year_month { result = val[0]; result.precision = :month }
|
297
|
+
| pua_year_month_day
|
298
|
+
|
299
|
+
pua_year : year ua { result = uoa(Date.new(val[0]), val[1], :year) }
|
300
|
+
|
301
|
+
pua_year_month :
|
302
|
+
pua_year '-' month opt_ua
|
302
303
|
{
|
303
304
|
result = uoa(val[0].change(:month => val[2]), val[3], [:month, :year])
|
304
305
|
}
|
305
|
-
| '('
|
306
|
+
| '(' pua_year PUA month opt_ua
|
306
307
|
{
|
307
308
|
result = uoa(uoa(val[1], val[2], :year).change(:month => val[3]), val[4], :month)
|
308
309
|
}
|
@@ -315,21 +316,21 @@ rule
|
|
315
316
|
result = uoa(Date.new(val[0], val[3]), val[5], [:month])
|
316
317
|
}
|
317
318
|
;
|
318
|
-
|
319
|
-
|
320
|
-
|
319
|
+
|
320
|
+
pua_year_month_day :
|
321
|
+
pua_year_month '-' d01_31 opt_ua
|
321
322
|
{
|
322
323
|
result = uoa(val[0].change(:day => val[2]), val[3])
|
323
324
|
}
|
324
|
-
|
|
325
|
+
| pua_year_month '-' '(' d01_31 ')' ua
|
325
326
|
{
|
326
327
|
result = uoa(val[0].change(:day => val[3]), val[5], [:day])
|
327
328
|
}
|
328
|
-
| '('
|
329
|
+
| '(' pua_year_month PUA d01_31 opt_ua
|
329
330
|
{
|
330
331
|
result = uoa(uoa(val[1], val[2], [:year, :month]).change(:day => val[3]), val[4], :day)
|
331
332
|
}
|
332
|
-
| year '-' '(' month
|
333
|
+
| year '-' '(' month PUA d01_31 opt_ua
|
333
334
|
{
|
334
335
|
result = uoa(uoa(Date.new(val[0], val[3], val[5]), val[4], :month), val[6], :day)
|
335
336
|
}
|
@@ -447,14 +448,22 @@ require 'strscan'
|
|
447
448
|
@options = Parser.defaults.merge(options)
|
448
449
|
end
|
449
450
|
|
450
|
-
|
451
|
+
def parse(input)
|
452
|
+
parse!(input)
|
453
|
+
rescue => e
|
454
|
+
warn e.message if options[:debug]
|
455
|
+
nil
|
456
|
+
end
|
457
|
+
|
458
|
+
def parse!(input)
|
451
459
|
@yydebug = @options[:debug] || ENV['DEBUG']
|
452
460
|
@src = StringScanner.new(input)
|
453
461
|
do_parse
|
454
462
|
end
|
455
463
|
|
456
464
|
def on_error(tid, val, vstack)
|
457
|
-
|
465
|
+
raise ArgumentError,
|
466
|
+
"failed to parse extended date time %s [%s]: %s" % [val.inspect, token_to_str(tid) || '?', vstack.inspect]
|
458
467
|
end
|
459
468
|
|
460
469
|
def apply_uncertainty(date, uncertainty, scope = nil)
|
@@ -475,11 +484,11 @@ require 'strscan'
|
|
475
484
|
when @src.scan(/\(/)
|
476
485
|
['(', @src.matched]
|
477
486
|
when @src.scan(/\)\?~-/)
|
478
|
-
[:
|
487
|
+
[:PUA, [:uncertain!, :approximate!]]
|
479
488
|
when @src.scan(/\)\?-/)
|
480
|
-
[:
|
489
|
+
[:PUA, [:uncertain!]]
|
481
490
|
when @src.scan(/\)~-/)
|
482
|
-
[:
|
491
|
+
[:PUA, [:approximate!]]
|
483
492
|
when @src.scan(/\)/)
|
484
493
|
[')', @src.matched]
|
485
494
|
when @src.scan(/\[/)
|
data/lib/edtf/season.rb
CHANGED
@@ -28,7 +28,8 @@ module EDTF
|
|
28
28
|
|
29
29
|
attr_accessor :qualifier
|
30
30
|
|
31
|
-
def_delegators :to_range,
|
31
|
+
def_delegators :to_range,
|
32
|
+
*Range.instance_methods(false).reject { |m| m.to_s =~ /^(each|eql?|hash)$/ }
|
32
33
|
|
33
34
|
SEASONS.each_value do |s|
|
34
35
|
define_method("#{s}?") { @season == s }
|
@@ -65,6 +66,15 @@ module EDTF
|
|
65
66
|
end
|
66
67
|
end
|
67
68
|
|
69
|
+
def each
|
70
|
+
if block_given?
|
71
|
+
to_range(&Proc.new)
|
72
|
+
self
|
73
|
+
else
|
74
|
+
to_enum
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
68
78
|
def year=(new_year)
|
69
79
|
@year = new_year.to_i
|
70
80
|
end
|
@@ -82,12 +92,12 @@ module EDTF
|
|
82
92
|
'%04d-%2d%s' % [year, CODES[season], qualified? ? "^#{qualifier}" : '']
|
83
93
|
end
|
84
94
|
|
85
|
-
alias
|
95
|
+
alias edtf to_s
|
86
96
|
|
87
97
|
def <=>(other)
|
88
98
|
case other
|
89
99
|
when Date
|
90
|
-
|
100
|
+
cover?(other) ? 0 : to_date <=> other
|
91
101
|
when Season
|
92
102
|
[year, month, qualifier] <=> [other.year, other.month, other.qualifier]
|
93
103
|
else
|
@@ -104,22 +114,13 @@ module EDTF
|
|
104
114
|
end
|
105
115
|
|
106
116
|
def to_date
|
107
|
-
Date.new(year, month)
|
117
|
+
Date.new(year, month, 1)
|
108
118
|
end
|
109
119
|
|
110
|
-
#
|
111
|
-
# case other
|
112
|
-
# when Date
|
113
|
-
# d = to_date
|
114
|
-
# other >= d && other <= d.months_since(3).end_of_month
|
115
|
-
# else
|
116
|
-
# false
|
117
|
-
# end
|
118
|
-
# end
|
119
|
-
|
120
|
+
# Returns a Range that covers the season (a three month period).
|
120
121
|
def to_range
|
121
122
|
d = to_date
|
122
|
-
d .. d.months_since(
|
123
|
+
d .. d.months_since(2).end_of_month
|
123
124
|
end
|
124
125
|
|
125
126
|
protected
|
data/lib/edtf/uncertainty.rb
CHANGED
@@ -1,59 +1,95 @@
|
|
1
1
|
module EDTF
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
# TODO use bitmasks instead of arrays
|
4
|
+
|
5
|
+
class Uncertainty < Struct.new(:year, :month, :day)
|
6
|
+
|
7
|
+
attr_reader :hash_base
|
8
|
+
|
9
|
+
def initialize(year = nil, month = nil, day = nil, hash_base = 1)
|
10
|
+
@hash_base = hash_base
|
11
|
+
super(year, month, day)
|
12
|
+
end
|
13
|
+
|
14
|
+
def hash_base=(base)
|
15
|
+
@hash_map = false
|
16
|
+
@hash_base = base
|
17
|
+
end
|
18
|
+
|
5
19
|
def uncertain?(parts = members)
|
6
|
-
[parts].
|
20
|
+
[*parts].any? { |p| !!send(p) }
|
7
21
|
end
|
8
22
|
|
9
23
|
def uncertain!(parts = members)
|
10
|
-
[parts].
|
24
|
+
[*parts].each { |p| send("#{p}=", true) }
|
11
25
|
self
|
12
26
|
end
|
13
27
|
|
14
28
|
def certain?(parts = members); !uncertain?(parts); end
|
15
|
-
|
29
|
+
|
16
30
|
def certain!(parts = members)
|
17
|
-
[parts].
|
31
|
+
[*parts].each { |p| send("#{p}=", false) }
|
18
32
|
self
|
19
33
|
end
|
34
|
+
|
35
|
+
def eql?(other)
|
36
|
+
hash == other.hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
values.zip(hash_map).reduce(0) { |s, (v, h)| s + (v ? h : 0) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def hash_map
|
46
|
+
@hash_map ||= (0...length).map { |i| hash_base << i }
|
47
|
+
end
|
48
|
+
|
20
49
|
end
|
21
50
|
|
22
|
-
|
51
|
+
|
23
52
|
class Unspecified < Struct.new(:year, :month, :day)
|
24
|
-
|
53
|
+
|
54
|
+
U = 'u'.freeze
|
55
|
+
|
25
56
|
def initialize
|
26
|
-
super
|
57
|
+
super Array.new(4),Array.new(2), Array.new(2)
|
27
58
|
end
|
28
|
-
|
59
|
+
|
29
60
|
def unspecified?(parts = members)
|
30
|
-
[parts].
|
61
|
+
[*parts].any? { |p| send(p).any? { |u| !!u } }
|
31
62
|
end
|
32
|
-
|
63
|
+
|
33
64
|
def unspecified!(parts = members)
|
34
|
-
[parts].
|
65
|
+
[*parts].each { |p| send(p).map! { true } }
|
35
66
|
self
|
36
67
|
end
|
37
68
|
|
38
69
|
def specified?(parts = members); !unspecified?(parts); end
|
39
70
|
|
40
71
|
def specified!(parts = members)
|
41
|
-
[parts].
|
72
|
+
[*parts].each { |p| send(p).map! { false } }
|
42
73
|
self
|
43
74
|
end
|
44
|
-
|
75
|
+
|
45
76
|
alias specific? specified?
|
46
77
|
alias unspecific? unspecified?
|
47
78
|
|
48
79
|
alias specific! specified!
|
49
80
|
alias unspecific! unspecified!
|
50
|
-
|
81
|
+
|
51
82
|
private :year=, :month=, :day=
|
52
|
-
|
83
|
+
|
53
84
|
def to_s
|
54
|
-
|
85
|
+
mask(%w{ ssss ss ss }).join('-')
|
86
|
+
end
|
87
|
+
|
88
|
+
def mask(values)
|
89
|
+
values.zip(members.take(values.length)).map do |value, mask|
|
90
|
+
value.split(//).zip(send(mask)).map { |v,m| m ? U : v }.join
|
91
|
+
end
|
55
92
|
end
|
56
|
-
|
57
93
|
end
|
58
|
-
|
94
|
+
|
59
95
|
end
|
data/lib/edtf/version.rb
CHANGED
data/spec/edtf/date_spec.rb
CHANGED
@@ -38,29 +38,83 @@ describe 'Date/DateTime' do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|
41
|
+
|
42
|
+
describe 'precisions' do
|
43
|
+
let(:date) { Date.today }
|
44
|
+
|
45
|
+
it 'has day precision by default' do
|
46
|
+
date.should be_day_precision
|
47
|
+
end
|
48
|
+
|
49
|
+
it '#day_precison returns a new date object' do
|
50
|
+
date.day_precision.should_not equal(date)
|
51
|
+
date.day_precision.should == date
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
41
56
|
|
42
57
|
describe '#negate' do
|
43
58
|
let(:date) { Date.edtf('2004?') }
|
44
59
|
|
45
|
-
it '
|
60
|
+
it 'returns a new date with the negated year' do
|
46
61
|
date.negate.year.should == (date.year * -1)
|
47
62
|
end
|
48
63
|
|
49
|
-
it '
|
64
|
+
it 'returns a new date with the same month' do
|
50
65
|
date.negate.month.should == date.month
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns a new date with the same day' do
|
51
69
|
date.negate.day.should == date.day
|
52
70
|
end
|
53
71
|
|
54
|
-
it '
|
72
|
+
it 'returns a new date with the same precision' do
|
55
73
|
date.negate.precision.should == date.precision
|
56
74
|
end
|
57
75
|
end
|
58
76
|
|
77
|
+
describe '#succ' do
|
78
|
+
|
79
|
+
it 'the successor of 2004 is 2005' do
|
80
|
+
Date.edtf('2004').succ.year.should == 2005
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'the successor of 2004-03 is 2004-04' do
|
84
|
+
Date.edtf('2004-03').succ.strftime('%Y-%m').should == '2004-04'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'the successor of 2004-03-01 is 2004-03-02' do
|
88
|
+
Date.edtf('2004-03-01').succ.strftime('%Y-%m-%d').should == '2004-03-02'
|
89
|
+
end
|
90
|
+
|
91
|
+
it "the successor of 1999 has year precision" do
|
92
|
+
Date.edtf('1999').succ.should be_year_precision
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#next' do
|
98
|
+
|
99
|
+
it 'returns the successor when given no argument' do
|
100
|
+
Date.edtf('1999').next.year.should == 2000
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'returns an array of the next 3 elements when passed 3 as an argument' do
|
104
|
+
Date.edtf('1999').next(3).map(&:year) == [2000, 2001, 2002]
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
|
59
110
|
describe '#change' do
|
60
111
|
let(:date) { Date.edtf('2004-09?~') }
|
61
112
|
|
62
113
|
it 'returns a copy of the date if given empty option hash' do
|
63
114
|
date.change({}).should == date
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'the returned copy is not identical at object level' do
|
64
118
|
date.change({}).should_not equal(date)
|
65
119
|
end
|
66
120
|
|
@@ -114,5 +168,78 @@ describe 'Date/DateTime' do
|
|
114
168
|
end
|
115
169
|
|
116
170
|
end
|
171
|
+
|
172
|
+
describe 'uncertain/approximate hash' do
|
173
|
+
|
174
|
+
it 'is 0 by default' do
|
175
|
+
Date.new().send(:ua_hash).should == 0
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'is 8 for approximate year' do
|
179
|
+
Date.new.approximate!(:year).send(:ua_hash).should == 8
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
describe 'sorting and comparisons' do
|
185
|
+
let(:xmas) { Date.new(2011, 12, 24) }
|
186
|
+
let(:new_years_eve) { Date.new(2011, 12, 31) }
|
187
|
+
|
188
|
+
describe '#values' do
|
189
|
+
|
190
|
+
it 'returns [2011,12,24] for christmas' do
|
191
|
+
xmas.values.should == [2011,12,24]
|
192
|
+
end
|
193
|
+
|
194
|
+
it 'returns [2011,12] for christmas with month precision' do
|
195
|
+
xmas.month_precision!.values.should == [2011,12]
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns [2011] for christmas with year precision' do
|
199
|
+
xmas.year_precision!.values.should == [2011]
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
describe '#<=>' do
|
205
|
+
|
206
|
+
it '2009-12-24 should be less than 2011-12-31' do
|
207
|
+
Date.new(2009,12,24).should < Date.new(2011,12,31)
|
208
|
+
end
|
209
|
+
|
210
|
+
it '2009-12-24 should be less than 2011-12' do
|
211
|
+
Date.new(2009,12,24).should < Date.edtf('2011-12')
|
212
|
+
end
|
213
|
+
|
214
|
+
it '2009-12-24 should be less than 2011' do
|
215
|
+
Date.new(2009,12,24).should < Date.edtf('2011')
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#==' do
|
222
|
+
|
223
|
+
it "xmas and new year's eve are not equal" do
|
224
|
+
xmas.should_not == new_years_eve
|
225
|
+
end
|
226
|
+
|
227
|
+
it "xmas and new year's eve are equal using month percision" do
|
228
|
+
xmas.month_precision!.should == new_years_eve.month_precision!
|
229
|
+
end
|
230
|
+
|
231
|
+
it "xmas and january 24 are not equal using month percision" do
|
232
|
+
xmas.month_precision!.should_not == xmas.month_precision!.next
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
it "xmas and new year's eve are equal using year percision" do
|
237
|
+
xmas.year_precision!.should == new_years_eve.year_precision!
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
|
117
244
|
|
118
245
|
end
|