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.
@@ -1,15 +1,9 @@
1
- module EDTF
2
-
3
- module ExtendedDateTime
4
-
5
- def to_edtf
6
- super
7
- end
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
@@ -1,8 +0,0 @@
1
-
2
- class Date
3
- include EDTF::ExtendedDate
4
- end
5
-
6
- class DateTime
7
- include EDTF::ExtendedDateTime
8
- end
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, to)
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 = Date.new(val[0]); result.season = val[2]; result.precision = :year }
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
- scan(input)
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 scan(input)
299
- @src = StringScanner.new(input)
300
- @stack = []
301
- tokenize
302
- end
303
-
304
- private
305
-
306
- def tokenize
307
- until @src.eos?
308
- case
309
- # when @src.scan(/\s+/)
310
- # ignore whitespace
311
- when @src.scan(/\(/)
312
- @stack << [:LP, @src.matched]
313
- when @src.scan(/\)/)
314
- @stack << [:RP, @src.matched]
315
- when @src.scan(/\[/)
316
- @stack << [:LSQUARE, @src.matched]
317
- when @src.scan(/\]/)
318
- @stack << [:RSQUARE, @src.matched]
319
- when @src.scan(/\{/)
320
- @stack << [:LBRACE, @src.matched]
321
- when @src.scan(/\}/)
322
- @stack << [:RBRACE, @src.matched]
323
- when @src.scan(/T/)
324
- @stack << [:T, @src.matched]
325
- when @src.scan(/Z/)
326
- @stack << [:Z, @src.matched]
327
- when @src.scan(/\?/)
328
- @stack << [:UNCERTAIN, @src.matched]
329
- when @src.scan(/~/)
330
- @stack << [:APPROXIMATE, @src.matched]
331
- when @src.scan(/open/i)
332
- @stack << [:OPEN, @src.matched]
333
- when @src.scan(/unkn?own/i) # matches 'unkown' typo too
334
- @stack << [:UNKNOWN, @src.matched]
335
- when @src.scan(/u/)
336
- @stack << [:UNSPECIFIED, @src.matched]
337
- when @src.scan(/x/i)
338
- @stack << [:X, @src.matched]
339
- when @src.scan(/y/)
340
- @stack << [:LONGYEAR, @src.matched]
341
- when @src.scan(/e/)
342
- @stack << [:E, @src.matched]
343
- when @src.scan(/\+/)
344
- @stack << [:PLUS, @src.matched]
345
- when @src.scan(/-/)
346
- @stack << [:MINUS, @src.matched]
347
- when @src.scan(/:/)
348
- @stack << [:COLON, @src.matched]
349
- when @src.scan(/\//)
350
- @stack << [:SLASH, @src.matched]
351
- when @src.scan(/\s*\.\.\s*/)
352
- @stack << [:DOTS, '..']
353
- when @src.scan(/\s*,\s*/)
354
- @stack << [:COMMA, ',']
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
 
@@ -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
@@ -1,6 +1,7 @@
1
1
  module EDTF
2
2
 
3
3
  class Uncertainty < Struct.new(:year, :month, :day, :hour, :minute, :second)
4
+
4
5
  def uncertain?(parts = members)
5
6
  [parts].flatten.any? { |p| !!send(p) }
6
7
  end
data/lib/edtf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EDTF
2
- VERSION = '0.0.3'.freeze
2
+ VERSION = '0.0.4'.freeze
3
3
  end
@@ -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