edtf 0.0.3 → 0.0.4
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/Gemfile +3 -0
- data/edtf.gemspec +2 -0
- data/features/parser/dates.feature +1 -1
- data/features/parser/intervals.feature +9 -10
- data/features/print/level_1_edtf.feature +68 -0
- data/features/print/level_2_edtf.feature +8 -0
- data/features/step_definitions/edtf_steps.rb +38 -20
- data/lib/edtf.rb +9 -1
- data/lib/edtf/compatibility.rb +9 -10
- data/lib/edtf/date.rb +168 -121
- data/lib/edtf/date_time.rb +7 -13
- data/lib/edtf/epoch.rb +57 -0
- data/lib/edtf/extensions.rb +0 -8
- data/lib/edtf/interval.rb +21 -1
- data/lib/edtf/parser.y +59 -73
- data/lib/edtf/season.rb +133 -0
- data/lib/edtf/uncertainty.rb +1 -0
- data/lib/edtf/version.rb +1 -1
- data/spec/edtf/date_spec.rb +118 -0
- data/spec/edtf/epoch_spec.rb +0 -0
- data/spec/edtf/interval_spec.rb +12 -0
- data/spec/edtf/parser_spec.rb +8 -3
- data/spec/edtf/{seasons_spec.rb → season_spec.rb} +23 -21
- data/spec/spec_helper.rb +0 -5
- metadata +37 -15
- data/lib/edtf/seasons.rb +0 -36
data/lib/edtf/date_time.rb
CHANGED
@@ -1,15 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def values
|
10
|
-
super + [hour,minute,second,offset]
|
11
|
-
end
|
12
|
-
|
1
|
+
class DateTime
|
2
|
+
|
3
|
+
alias edtf iso8601
|
4
|
+
alias to_edtf edtf
|
5
|
+
|
6
|
+
def values
|
7
|
+
super + [hour,minute,second,offset]
|
13
8
|
end
|
14
|
-
|
15
9
|
end
|
data/lib/edtf/epoch.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module EDTF
|
2
|
+
class Epoch
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
include Enumerable
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
attr_accessor :year
|
10
|
+
|
11
|
+
alias get year
|
12
|
+
alias set year=
|
13
|
+
|
14
|
+
private_class_method :new
|
15
|
+
|
16
|
+
def initialize(year = 0)
|
17
|
+
end
|
18
|
+
|
19
|
+
def <=>(other)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def to_date
|
24
|
+
Date.new(year)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
class Century < Epoch
|
30
|
+
|
31
|
+
def_delegator :to_range, :each
|
32
|
+
|
33
|
+
def to_range
|
34
|
+
d = to_date
|
35
|
+
d .. d.years_since(100).end_of_year
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_edtf
|
39
|
+
'%02dxx' % (year / 100)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
class Decade < Epoch
|
45
|
+
|
46
|
+
def_delegator :to_range, :each
|
47
|
+
|
48
|
+
def to_range
|
49
|
+
d = to_date
|
50
|
+
d .. d.years_since(10).end_of_year
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_edtf
|
54
|
+
'%03dx' % (year / 10)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/edtf/extensions.rb
CHANGED
data/lib/edtf/interval.rb
CHANGED
@@ -57,10 +57,30 @@ module EDTF
|
|
57
57
|
when unknown_end?
|
58
58
|
nil
|
59
59
|
else
|
60
|
-
Range.new(unknown_start? ? Date.new : from,
|
60
|
+
Range.new(unknown_start? ? Date.new : @from, bounds)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
def bounds
|
65
|
+
case
|
66
|
+
when open_end?, to.day_precision?
|
67
|
+
to
|
68
|
+
when to.month_precision?
|
69
|
+
to.end_of_month
|
70
|
+
else
|
71
|
+
to.end_of_year
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def edtf
|
76
|
+
[
|
77
|
+
@from.send(@from.respond_to?(:edtf) ? :edtf : :to_s),
|
78
|
+
@to.send(@to.respond_to?(:edtf) ? :edtf : :to_s)
|
79
|
+
].join('/')
|
80
|
+
end
|
81
|
+
|
82
|
+
alias to_s edtf
|
83
|
+
|
64
84
|
end
|
65
85
|
|
66
86
|
end
|
data/lib/edtf/parser.y
CHANGED
@@ -123,7 +123,7 @@ rule
|
|
123
123
|
| long_year digit { result = 10 * val[0] + val[1] }
|
124
124
|
|
125
125
|
|
126
|
-
season : year MINUS season_number { result =
|
126
|
+
season : year MINUS season_number { result = Season.new(val[0], val[2]) }
|
127
127
|
|
128
128
|
season_number : D2 D1 { result = 21 }
|
129
129
|
| D2 D2 { result = 22 }
|
@@ -283,85 +283,71 @@ require 'strscan'
|
|
283
283
|
|
284
284
|
def parse(input)
|
285
285
|
@yydebug = @options[:debug] || ENV['DEBUG']
|
286
|
-
|
286
|
+
@src = StringScanner.new(input)
|
287
287
|
do_parse
|
288
288
|
end
|
289
289
|
|
290
|
-
def next_token
|
291
|
-
@stack.shift
|
292
|
-
end
|
293
|
-
|
294
290
|
def on_error(tid, val, vstack)
|
295
291
|
warn "failed to parse extended date time %s (%s) %s" % [val.inspect, token_to_str(tid) || '?', vstack.inspect]
|
296
292
|
end
|
297
293
|
|
298
|
-
def
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
when @src.scan(/\^\w+/)
|
356
|
-
@stack << [:CARET, @src.matched[1..-1]]
|
357
|
-
when @src.scan(/\d/)
|
358
|
-
@stack << [['D', @src.matched].join.intern, @src.matched]
|
359
|
-
else @src.scan(/./)
|
360
|
-
@stack << [:UNMATCHED, @src.rest]
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
@stack
|
294
|
+
def next_token
|
295
|
+
case
|
296
|
+
when @src.eos?
|
297
|
+
nil
|
298
|
+
# when @src.scan(/\s+/)
|
299
|
+
# ignore whitespace
|
300
|
+
when @src.scan(/\(/)
|
301
|
+
[:LP, @src.matched]
|
302
|
+
when @src.scan(/\)/)
|
303
|
+
[:RP, @src.matched]
|
304
|
+
when @src.scan(/\[/)
|
305
|
+
[:LSQUARE, @src.matched]
|
306
|
+
when @src.scan(/\]/)
|
307
|
+
[:RSQUARE, @src.matched]
|
308
|
+
when @src.scan(/\{/)
|
309
|
+
[:LBRACE, @src.matched]
|
310
|
+
when @src.scan(/\}/)
|
311
|
+
[:RBRACE, @src.matched]
|
312
|
+
when @src.scan(/T/)
|
313
|
+
[:T, @src.matched]
|
314
|
+
when @src.scan(/Z/)
|
315
|
+
[:Z, @src.matched]
|
316
|
+
when @src.scan(/\?/)
|
317
|
+
[:UNCERTAIN, @src.matched]
|
318
|
+
when @src.scan(/~/)
|
319
|
+
[:APPROXIMATE, @src.matched]
|
320
|
+
when @src.scan(/open/i)
|
321
|
+
[:OPEN, @src.matched]
|
322
|
+
when @src.scan(/unkn?own/i) # matches 'unkown' typo too
|
323
|
+
[:UNKNOWN, @src.matched]
|
324
|
+
when @src.scan(/u/)
|
325
|
+
[:UNSPECIFIED, @src.matched]
|
326
|
+
when @src.scan(/x/i)
|
327
|
+
[:X, @src.matched]
|
328
|
+
when @src.scan(/y/)
|
329
|
+
[:LONGYEAR, @src.matched]
|
330
|
+
when @src.scan(/e/)
|
331
|
+
[:E, @src.matched]
|
332
|
+
when @src.scan(/\+/)
|
333
|
+
[:PLUS, @src.matched]
|
334
|
+
when @src.scan(/-/)
|
335
|
+
[:MINUS, @src.matched]
|
336
|
+
when @src.scan(/:/)
|
337
|
+
[:COLON, @src.matched]
|
338
|
+
when @src.scan(/\//)
|
339
|
+
[:SLASH, @src.matched]
|
340
|
+
when @src.scan(/\s*\.\.\s*/)
|
341
|
+
[:DOTS, '..']
|
342
|
+
when @src.scan(/\s*,\s*/)
|
343
|
+
[:COMMA, ',']
|
344
|
+
when @src.scan(/\^\w+/)
|
345
|
+
[:CARET, @src.matched[1..-1]]
|
346
|
+
when @src.scan(/\d/)
|
347
|
+
[['D', @src.matched].join.intern, @src.matched]
|
348
|
+
else @src.scan(/./)
|
349
|
+
[:UNMATCHED, @src.rest]
|
350
|
+
end
|
365
351
|
end
|
366
352
|
|
367
353
|
|
data/lib/edtf/season.rb
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
module EDTF
|
2
|
+
|
3
|
+
class Season
|
4
|
+
|
5
|
+
SEASONS = Hash[21, :spring, 22, :summer, 23, :autumn, 24, :winter].freeze
|
6
|
+
|
7
|
+
CODES = Hash.new { |h,k| h.fetch(k.to_sym, nil) }.merge(
|
8
|
+
SEASONS.invert).merge({ :fall => 23 }).freeze
|
9
|
+
|
10
|
+
NORTHERN = Hash[:spring, [3,4,5], :summer, [6,7,8], :autumn, [9,10,11], :winter, [12,1,2]].freeze
|
11
|
+
SOUTHERN = Hash[:autumn, [3,4,5], :winter, [6,7,8], :spring, [9,10,11], :summer, [12,1,2]].freeze
|
12
|
+
|
13
|
+
NORTHERN_MONTHS = Hash[*NORTHERN.map { |s,ms| ms.map { |m| [m,s] } }.flatten].freeze
|
14
|
+
SOUTHERN_MONTHS = Hash[*SOUTHERN.map { |s,ms| ms.map { |m| [m,s] } }.flatten].freeze
|
15
|
+
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
include Comparable
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def current
|
23
|
+
Date.today.season
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :season, :year
|
28
|
+
|
29
|
+
attr_accessor :qualifier
|
30
|
+
|
31
|
+
def_delegators :to_range, :each
|
32
|
+
|
33
|
+
SEASONS.each_value do |s|
|
34
|
+
define_method("#{s}?") { @season == s }
|
35
|
+
define_method("#{s}!") { @season = s }
|
36
|
+
end
|
37
|
+
|
38
|
+
alias fall? autumn?
|
39
|
+
alias fall! autumn!
|
40
|
+
|
41
|
+
[:first, :second, :third, :fourth].zip(SEASONS.values).each do |quarter, season|
|
42
|
+
alias_method("#{quarter}?", "#{season}?")
|
43
|
+
alias_method("#{quarter}!", "#{season}!")
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def initialize(*arguments)
|
48
|
+
arguments.flatten!
|
49
|
+
raise ArgumentError, "wrong number of arguments (#{arguments.length} for 0..3)" if arguments.length > 3
|
50
|
+
|
51
|
+
if arguments.length == 1
|
52
|
+
case arguments[0]
|
53
|
+
when Date
|
54
|
+
@year, @season = arguments[0].year, NORTHERN_MONTHS[arguments[0]]
|
55
|
+
when Symbol, String
|
56
|
+
@year, @season = Date.today.year, SEASONS[CODES[arguments[0].intern]]
|
57
|
+
else
|
58
|
+
self.year = arguments[0]
|
59
|
+
@season = NORTHERN_MONTHS[Date.today.month]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
self.year = arguments[0] || Date.today.year
|
63
|
+
self.season = arguments[1] || NORTHERN_MONTHS[Date.today.month]
|
64
|
+
self.qualifier = qualifier
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def year=(new_year)
|
69
|
+
@year = new_year.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
def season=(new_season)
|
73
|
+
@season = SEASONS[new_season] || SEASONS[CODES[new_season]] ||
|
74
|
+
raise(ArgumentError, "unknown season/format: #{new_season.inspect})")
|
75
|
+
end
|
76
|
+
|
77
|
+
def season?; true; end
|
78
|
+
|
79
|
+
def qualified?; !!@qualifier; end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
'%04d-%2d%s' % [year, CODES[season], qualified? ? "^#{qualifier}" : '']
|
83
|
+
end
|
84
|
+
|
85
|
+
alias to_edtf to_s
|
86
|
+
|
87
|
+
def <=>(other)
|
88
|
+
case other
|
89
|
+
when Date
|
90
|
+
include?(other) ? 0 : to_date <=> other
|
91
|
+
when Season
|
92
|
+
[year, month, qualifier] <=> [other.year, other.month, other.qualifier]
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
rescue
|
97
|
+
nil
|
98
|
+
end
|
99
|
+
|
100
|
+
def ===(other)
|
101
|
+
(self <=> other) == 0
|
102
|
+
rescue
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def to_date
|
107
|
+
Date.new(year, month)
|
108
|
+
end
|
109
|
+
|
110
|
+
# def include?(other)
|
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
|
+
def to_range
|
121
|
+
d = to_date
|
122
|
+
d .. d.months_since(3).end_of_month
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
def month
|
128
|
+
NORTHERN[@season][0]
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
data/lib/edtf/uncertainty.rb
CHANGED
data/lib/edtf/version.rb
CHANGED
@@ -0,0 +1,118 @@
|
|
1
|
+
describe 'Date/DateTime' do
|
2
|
+
|
3
|
+
describe 'class methods' do
|
4
|
+
it 'responds to edtf' do
|
5
|
+
Date.should respond_to(:edtf)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'instance methods' do
|
10
|
+
[:uncertain?, :approximate?, :unspecified?, :uncertain, :approximate, :unspecified].each do |method|
|
11
|
+
it "responds to #{method}" do
|
12
|
+
Date.new.respond_to?(method).should == true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#dup' do
|
18
|
+
let(:date) { Date.edtf('2004-09?~') }
|
19
|
+
|
20
|
+
it 'copies all date values' do
|
21
|
+
date.dup.to_s == '2004-09-01'
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'copies uncertainty' do
|
25
|
+
date.dup.should be_uncertain
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'copies approximate' do
|
29
|
+
date.dup.should be_approximate
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'copies precision' do
|
33
|
+
date.dup.precision.should == :month
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'copies uncertainty by value' do
|
37
|
+
lambda { date.dup.certain! }.should_not change { date.uncertain? }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#negate' do
|
43
|
+
let(:date) { Date.edtf('2004?') }
|
44
|
+
|
45
|
+
it 'should return a new date with the negated year' do
|
46
|
+
date.negate.year.should == (date.year * -1)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should return a new date with the same month and day' do
|
50
|
+
date.negate.month.should == date.month
|
51
|
+
date.negate.day.should == date.day
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should return a new date with the same precision' do
|
55
|
+
date.negate.precision.should == date.precision
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#change' do
|
60
|
+
let(:date) { Date.edtf('2004-09?~') }
|
61
|
+
|
62
|
+
it 'returns a copy of the date if given empty option hash' do
|
63
|
+
date.change({}).should == date
|
64
|
+
date.change({}).should_not equal(date)
|
65
|
+
end
|
66
|
+
|
67
|
+
describe 'when given a new year' do
|
68
|
+
let(:changeset) { { :year => 1999 } }
|
69
|
+
|
70
|
+
it 'returns a new date instance with the changed year' do
|
71
|
+
date.change(changeset).year.should == 1999
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'when given a new precision' do
|
77
|
+
let(:changeset) { { :precision => :year } }
|
78
|
+
|
79
|
+
it 'returns a new date instance with the changed precision' do
|
80
|
+
date.change(changeset).precision.should == :year
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'copies extended values by value' do
|
86
|
+
lambda { date.change({}).approximate! }.should_not change { date.approximate? }
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#uncertain?' do
|
92
|
+
|
93
|
+
let(:date) { Date.new }
|
94
|
+
|
95
|
+
it { Date.new.should_not be_uncertain }
|
96
|
+
|
97
|
+
[:year, :month, :day].each do |part|
|
98
|
+
it "should not be uncertain by default (#{part})" do
|
99
|
+
Date.new.uncertain?(part).should == false
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should be uncertain if set to uncertain (#{part})" do
|
103
|
+
date.uncertain.send("#{part}=", true)
|
104
|
+
date.uncertain?(part).should == true
|
105
|
+
end
|
106
|
+
|
107
|
+
([:year, :month, :day] - [part]).each do |other|
|
108
|
+
it "#{other} should not be uncertain if #{part} is uncertain" do
|
109
|
+
date.uncertain.send("#{part}=", true)
|
110
|
+
date.uncertain?(other).should == false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|