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
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
|