edtf 0.0.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  EDTF-Ruby
2
2
  =========
3
3
 
4
- Ruby implementation of the [Extended Date/Time Format
5
- Specification](http://www.loc.gov/standards/datetime/spec.html).
4
+ EDTF-Ruby comprises a parser and an API implementation of the [Extended
5
+ Date/Time Format standard](http://www.loc.gov/standards/datetime/spec.html).
6
6
 
7
7
 
8
8
  Compatibility
@@ -11,8 +11,6 @@ Compatibility
11
11
  EDTF-Ruby parser implements all levels and features of the EDTF specification
12
12
  (version September 16, 2011). With the following known caveats:
13
13
 
14
- * Uncertain/approximate seasons will be parsed, but the Season class does
15
- not implement attributes.
16
14
  * In the latest revision of the EDTF specification alternative versions of
17
15
  partial uncertain/approximate strings were introduced (with or without nested
18
16
  parentheses); EDTF-Ruby currently uses the version that tries to reduce
@@ -23,31 +21,115 @@ EDTF-Ruby parser implements all levels and features of the EDTF specification
23
21
 
24
22
  EDTF-Ruby has been confirmed to work on the following Ruby implementations:
25
23
  1.9.3, 1.9.2, 1.8.7, Rubinius, and JRuby. Active Support's date extensions
26
- are currently listed as a dependency, because of a number of many functional
27
- overlaps.
24
+ are currently listed as a dependency, because of many functional overlaps.
28
25
 
29
26
 
30
27
  Quickstart
31
28
  ----------
32
29
 
33
30
  EDTF Ruby is implemented as an extension to the regular Ruby date/time classes.
34
- You can access parse EDTF strings either using `Date.edtf` or `EDTF.parse`
31
+ You can parse EDTF strings either using `Date.edtf` or `EDTF.parse`
35
32
  (both methods come with an alternative bang! version, that will raise an error
36
33
  if the string cannot be parsed instead of silently returning nil); if
37
34
  given a valid EDTF string the return value will either be an (extended) `Date`,
38
- `EDTF::Interval`, `EDTF::Set`, or `Range` (for masked precision strings)
39
- instance. Given a Date, you can print the corresponding EDTF string using the
40
- `#edtf` method.
35
+ `EDTF::Interval`, `EDTF::Set`, `EDTF::Epoch` or `EDTF::Season` instance.
36
+
37
+ Given any of these instances, you can print the corresponding EDTF string
38
+ using the `#edtf` method.
39
+
40
+ === Dates
41
+
42
+ Most of the EDTF features deal with dates; EDTF-Ruby implements these by
43
+ extending Active Support's version of the regular Ruby Date class. The library
44
+ intends to be transparent to Ruby's regular API, i.e., every Date instance
45
+ should act as you would normally expect but provides additional functionality.
46
+
47
+ Most, notably, EDTF dates come in day, month, or year precision. This is a
48
+ subtle difference that determines how many other methods work. For instance:
49
+
50
+ > Date.today.precision
51
+ => :day
52
+ > Date.today
53
+ => Thu, 10 Nov 2011
54
+ > Date.today.succ
55
+ => Fri, 11 Nov 2011
56
+ > Date.today.month_precision!.succ
57
+ => Sat, 10 Dec 2011
58
+
59
+ As you can see, dates have day precision by default; after setting the date's
60
+ precision to month, however, the natural successor is not the next day, but
61
+ a day a month from now. Always keep precision in mind when comparing dates,
62
+ too:
63
+
64
+ > Date.new(1666).year_precision! == Date.new(1966)
65
+ => false
66
+
67
+ The year 1666 is not equal to the January 1st, 1966. You can set a date's
68
+ precision directly, or else use the dedicated bang! methods:
69
+
70
+ > d = Date.new(1993)
71
+ > d.day_precision? # -> true
72
+ > d.edtf
73
+ => "1993-01-01"
74
+ > d.month_precision!
75
+ > d.edtf
76
+ => "1993-01"
77
+ > d.year_precision!
78
+ > d.edtf
79
+ => "1993"
80
+ > d.day_precision? # -> false
81
+ > d.year_precision? # -> true
82
+
83
+ In the examples above, you also see that the `#edtf` method will print a
84
+ different string depending on the date's current precision.
85
+
86
+ The second important extension is that dates can be uncertain and or
87
+ approximate. The distinction between the two may seem somewhat contrived,
88
+ but we have come to understand it as follows:
89
+
90
+ Assume you take a history exam and have to answer one of those dreaded
91
+ questions that require you to say exactly in what year a certain event
92
+ happened; you studied hard, but all of a sudden you are *uncertain*: was
93
+ that the year 1683 or was it 1638? This is what uncertainty is in the
94
+ parlance of EDTF: in fact, you would write it just like that "1638?".
95
+
96
+ Approximate dates are similar but slightly different. Lets say you want
97
+ to tell a story about something that happened to you one winter; you
98
+ don't recall the exact date, but you know it must have been sometime
99
+ between Christmas and New Year's. Come to think of it, you don't
100
+ remember the year either, but you must have been around ten years old.
101
+ Using EDTF, you could write something like "1993~-12-(27)~": this
102
+ indicates that both the year and the day are approximations: perhaps
103
+ the day is not the 27th but it is somewhere close.
104
+
105
+ This is the main difference between uncertain and approximate in
106
+ EDTF (in our opinion at least): approximate always means close to the
107
+ actual number, whilst uncertain could be something completely different
108
+ (just as there is a large temporal distance between 1638 and 1683).
109
+
110
+ Here are a few examples of how you can access the uncertain/approximate
111
+ state of dates in Ruby:
41
112
 
42
- $ [sudo] gem install edtf
43
- $ irb
44
- > require 'edtf'
45
113
  > d = Date.edtf('1984?')
46
114
  > d.uncertain?
47
115
  => true
116
+ > d.uncertain? :year
117
+ => true
118
+ > d.uncertain? :day
119
+ => false
48
120
  > d.approximate!
49
121
  > d.edtf
50
122
  => "1984?~"
123
+ > d.month_precision!
124
+ > d.approximate! :month
125
+ > d.edtf
126
+ => "1984?-01~"
127
+
128
+ As you can see above, you can use the bang! methods to set individual date
129
+ parts.
130
+
131
+ In addition, EDTF supports *unspecified* date parts:
132
+
51
133
  > d = Date.edtf('1999-03-uu')
52
134
  > d.unspecified?
53
135
  => true
@@ -58,49 +140,169 @@ instance. Given a Date, you can print the corresponding EDTF string using the
58
140
  > d.unspecified! :month
59
141
  > d.edtf
60
142
  => "1999-uu-uu"
61
- > Date.edtf!('2003-24').winter?
62
- => true
63
- > Date.edtf!('196x')
64
- => #<Date: 1960-01-01>...#<Date: 1970-01-01>
143
+
144
+ All three, uncertain, approximate, and unspecified attributes do not factor
145
+ into date calculations (like comparisons or successors etc.).
146
+
147
+ EDTF long or scientific years are mapped to normal date instances.
148
+
65
149
  > Date.edtf('y-17e7').year
66
150
  => -170000000
151
+
152
+ When printing date strings, EDTF-Ruby will try to avoid nested parentheses:
153
+
154
+ > Date.edtf("(1999-(02)~-23)?").edtf
155
+ => "1999?-(02)?~-23?"
156
+
157
+
158
+ === Intervals
159
+
160
+ If you parse an EDTF interval, the EDTF-Ruby parser will return an instance
161
+ of `EDTF::Interval`; intervals mimic regular Ruby ranges, but offer additional
162
+ functionality.
163
+
67
164
  > d = Date.edtf('1984-06?/2004-08?')
68
165
  > d.from.uncertain?
69
166
  => true
70
167
  > d.include?(Date.new(1987,04,13))
71
- => false # the day is not included because interval has month precision
168
+ => false
169
+
170
+ The day is not included because interval has month precision. However:
171
+
72
172
  > d.cover?(Date.new(1987,04,13))
73
- => true # but the day is still covered by the interval
173
+ => true
174
+
175
+ The day is still covered by the interval. In general, optimized in the
176
+ same way that Ruby optimizes numeric ranges. Additionally, precision
177
+ plays into the way intervals are enumerated:
178
+
74
179
  > d.length
75
- => 243 # months between 1984-06-01 and 2004-08-31
180
+ => 243
181
+
182
+ There are 243 months between 1984-06-01 and 2004-08-31.
183
+
76
184
  > d.step(36).map(&:year)
77
- => [1984, 1987, 1990, 1993, 1996, 1999, 2002] # 36-month steps
185
+ => [1984, 1987, 1990, 1993, 1996, 1999, 2002]
186
+
187
+ Here we iterate through the interval in 36-month steps and map each date to
188
+ the year.
189
+
78
190
  > Date.edtf('1582-10-01/1582-10-31').length
79
- => 21 # number of days in October 1582 (Gregorian calendar)
191
+ => 21
192
+
193
+ This interval has day precision, so 21 is the number of days in October 1582,
194
+ which was cut short because of the Gregorian calendar reform.
195
+
196
+ Intervals can be open or have unknown start or end dates.
197
+
80
198
  > Date.edtf('2004/open').open?
81
199
  => true
82
200
  > Date.edtf('2004/open').cover?(Date.today)
83
- => true # an open ended interval covers today
84
- > Date.edtf("(1999-(02)~-23)?").edtf
85
- => "1999?-(02)?~-23?" # when printing, EDTF-Ruby reduces nested parentheses
201
+ => true
202
+
203
+ === Sets
204
+
205
+ EDTF supports two kind of sets: choice lists (meaning one date out of a list),
206
+ or inclusive lists. In EDTF-Ruby, these are covered by the class `EDTF::Set`
207
+ and the `choice` attribute.
208
+
86
209
  > s = Date.edtf('{1667,1668, 1670..1672}')
210
+ > s.choice?
211
+ => false
212
+ > s.choice!
213
+ > s.edtf
214
+ => "[1667, 1668, 1670..1672]"
215
+
216
+ As you can see above, EDTF-Ruby remembers which parts of the set were
217
+ specified as a range; ranges are however enumerated for membership tests:
218
+
87
219
  > s.include?(Date.edtf('1669'))
88
220
  => false
89
221
  > s.include?(Date.edtf('1671'))
90
- => true # the range is enumerated for membership tests
222
+ => true
223
+
224
+ Even though we're still aware that the year 1671 was is not directly an
225
+ element of the set:
226
+
91
227
  > s.length
92
- => 3 # but we're still aware that there were only three elements
228
+ => 3
229
+
230
+ When in doubt, you can always map the set to an array. This will also
231
+ enumerate all ranges:
232
+
93
233
  > s.map(&:year)
94
234
  => [1667, 1668, 1670, 1671, 1672] # when enumerated there are 5 elements
235
+
236
+ EDTF sets also feature an `#earlier?` and `#later?` attribute:
237
+
95
238
  > s.earlier?
96
- => false # the original list was not vague
97
- > s.earlier! # but we can make it so
239
+ => false
240
+ > s.earlier!
241
+ > s.edtf
242
+ => "[..1667, 1668, 1670..1672]"
243
+
244
+
245
+ === Decades and Centuries
246
+
247
+ The EDTF specification supports so called masked precision strings to define
248
+ decades or centuries. EDTF-Ruby maps these to dedicated intervals which
249
+ always cover 10 or 100 years, respectively.
250
+
251
+ > d = Date.edtf!('196x')
252
+ => 196x
253
+ > d.class
254
+ => EDTF::Decade
255
+ > d.class
256
+ => EDTF::Decade
257
+ > d.min
258
+ => Fri, 01 Jan 1960
259
+ > d.max
260
+ => Wed, 31 Dec 1969
261
+ > d.map(&:year)
262
+ => [1960, 1961, 1962, 1963, 1964, 1965, 1966, 1967, 1968, 1969]
263
+
264
+ === Seasons
265
+
266
+ Finally, EDTF covers seasons. Again, EDTF-Ruby provides a dedicated class
267
+ for this. Note that EDTF does not make any assumption about the specifics
268
+ (which months etc.) of the season and you don't have to either; however
269
+ EDTF-Ruby defines method aliases which allow you to access the seasons
270
+ by the names spring, summer, autumn (or fall), and winter, respectively.
271
+ You can also use the more neutral taxonomy of first, second, third,
272
+ fourth.
273
+
274
+ > w = Date.edtf!('2003-24')
275
+ > w.winter?
276
+ => true
277
+ > s = w.succ
278
+ > s.spring?
279
+ => true
280
+ > s.year
281
+ => 2004
282
+ > s.min
283
+ => Mon, 01 Mar 2004
284
+ > s.max
285
+ => Mon, 31 May 2004
286
+ > s.to_a.length
287
+ => 92
288
+ 005:0> w.to_a.length
289
+ => 91
290
+
291
+ As you can see, spring 2004 lasted one day longer than winter 2003 (note
292
+ that spring and winter here do not relate to the astronomical seasons
293
+ but strictly to three month periods).
294
+
295
+ Of course you can print seasons to EDTF strings, too. Finally, seasons can
296
+ be uncertain/approximate.
297
+
98
298
  > s.edtf
99
- => "{..1667, 1668, 1670..1672}"
299
+ => "2004-21"
300
+ > s.approximate!.edtf
301
+ => "2004-21"
100
302
 
101
303
 
102
- For additional features take a look at the documentation or the extensive
103
- list of rspec examples.
304
+ For additional features, please take a look at the documentation or the
305
+ extensive list of rspec examples.
104
306
 
105
307
 
106
308
  Contributing
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.platform = Gem::Platform::RUBY
11
11
  s.authors = ['Sylvester Keil']
12
12
  s.email = ['http://sylvester.keil.or.at']
13
- s.homepage = 'http://inukshuk.github.com/edtf-ruby'
13
+ s.homepage = 'http://github.com/inukshuk/edtf-ruby'
14
14
  s.summary = 'Extended Date/Time Format for Ruby.'
15
15
  s.description = 'A Ruby implementation of the Extended Date/Time Format (EDTF).'
16
16
  s.license = 'FreeBSD'
@@ -10,5 +10,5 @@ Feature: Parse masked precision strings
10
10
  @level2 @2041
11
11
  Scenarios: decades and centuries
12
12
  | string | start | end |
13
- | 196x | 1960 | 1970 |
14
- | 19xx | 1900 | 2000 |
13
+ | 196x | 1960 | 1969 |
14
+ | 19xx | 1900 | 1999 |
@@ -73,7 +73,7 @@ Then /^the interval should include the date "([^"]*)"$/ do |date|
73
73
  end
74
74
 
75
75
  Then /^the interval should cover the date "([^"]*)"$/ do |date|
76
- @date.should cover(Date.parse(date))
76
+ @date.should be_cover(Date.parse(date))
77
77
  end
78
78
 
79
79
 
@@ -223,17 +223,15 @@ class Date
223
223
 
224
224
  alias to_edtf edtf
225
225
 
226
- # Returns the Date of the next day, month, or year depending on the
226
+ # Returns an array of the next n days, months, or years depending on the
227
227
  # current Date/Time's precision.
228
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
229
+ 1.upto(n).map { |by| advance(PRECISIONS[precision] => by) }
234
230
  end
235
231
 
236
- alias succ next
232
+ def succ
233
+ advance(PRECISIONS[precision] => 1)
234
+ end
237
235
 
238
236
  # Returns the Date of the previous day, month, or year depending on the
239
237
  # current Date/Time's precision.
@@ -281,7 +279,7 @@ class Date
281
279
  end
282
280
 
283
281
  def update_precision_filter
284
- case @precision
282
+ @precision_filter = case precision
285
283
  when :year
286
284
  [:year]
287
285
  when :month
@@ -1,57 +1,106 @@
1
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
2
 
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
3
+ class Epoch
4
+ extend Forwardable
30
5
 
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
6
+ include Enumerable
7
+ include Comparable
8
+
9
+ class << self
10
+ attr_reader :duration, :format
11
+
12
+ def current
13
+ new(Date.today.year)
14
+ end
15
+
16
+ private :new, :current
17
+ end
18
+
19
+ attr_reader :year
45
20
 
46
- def_delegator :to_range, :each
21
+ def_delegators :to_range,
22
+ *Range.instance_methods(false).reject { |m| m.to_s =~ /^(each|min|max|cover|inspect)$|^\W/ }
47
23
 
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
24
+
25
+ def initialize(year = 0)
26
+ self.year = year
27
+ end
28
+
29
+ def year=(year)
30
+ @year = (year / self.class.duration) * self.class.duration
31
+ end
32
+
33
+ alias get year
34
+ alias set year=
35
+
36
+ def cover?(other)
37
+ return false unless other.respond_to?(:day_precision)
38
+ other = other.day_precision
39
+ min.day_precision! <= other && other <= max.day_precision!
40
+ end
41
+
42
+ def <=>(other)
43
+ case other
44
+ when Date
45
+ cover?(other) ? 0 : to_date <=> other
46
+ when Interval, Season
47
+ [min, max] <=> [other.min, other.max]
48
+ when Epoch
49
+ [year, self.class.duration] <=> [other.year, other.class.duration]
50
+ else
51
+ nil
52
+ end
53
+ rescue
54
+ nil
55
+ end
56
+
57
+ def ===(other)
58
+ (self <=> other) == 0
59
+ rescue
60
+ false
61
+ end
62
+
63
+ def each
64
+ if block_given?
65
+ to_range.each(&Proc.new)
66
+ else
67
+ to_enum
68
+ end
69
+ end
70
+
71
+ def to_date
72
+ Date.new(year).year_precision!
73
+ end
74
+
75
+ alias min to_date
76
+
77
+ def max
78
+ to_date.advance(:years => self.class.duration - 1).end_of_year
79
+ end
80
+
81
+ def to_range
82
+ min..max
83
+ end
84
+
85
+ def edtf
86
+ self.class.format % (year / self.class.duration)
87
+ end
88
+
89
+ alias to_s edtf
90
+
91
+ end
92
+
93
+ class Century < Epoch
94
+ @duration = 100.freeze
95
+ @format = '%02dxx'.freeze
96
+
97
+ public_class_method :current, :new
98
+ end
99
+
100
+ class Decade < Epoch
101
+ @duration = 10.freeze
102
+ @format = '%03dx'.freeze
103
+
104
+ public_class_method :current, :new
105
+ end
57
106
  end
@@ -249,7 +249,7 @@ module EDTF
249
249
 
250
250
  def <=>(other)
251
251
  case other
252
- when Interval
252
+ when Interval, Season, Epoch
253
253
  [min, max] <=> [other.min, other.max]
254
254
  when Date
255
255
  cover?(other) ? min <=> other : 0
@@ -159,8 +159,10 @@ rule
159
159
  ;
160
160
 
161
161
 
162
- # TODO uncertain/approximate seasons
163
- season : year '-' season_number ua { result = Season.new(val[0], val[2]) }
162
+ season : year '-' season_number ua {
163
+ result = Season.new(val[0], val[2])
164
+ val[3].each { |ua| result.send(ua) }
165
+ }
164
166
 
165
167
  season_number : '2' '1' { result = 21 }
166
168
  | '2' '2' { result = 22 }
@@ -209,12 +211,12 @@ rule
209
211
  digit digit digit X
210
212
  {
211
213
  d = val[0,3].zip([1000,100,10]).reduce(0) { |s,(a,b)| s += a * b }
212
- result = Date.new(d) ... Date.new(d+10)
214
+ result = EDTF::Decade.new(d)
213
215
  }
214
216
  | digit digit X X
215
217
  {
216
218
  d = val[0,2].zip([1000,100]).reduce(0) { |s,(a,b)| s += a * b }
217
- result = Date.new(d) ... Date.new(d+100)
219
+ result = EDTF::Century.new(d)
218
220
  }
219
221
  ;
220
222
 
@@ -1,6 +1,7 @@
1
1
  module EDTF
2
2
 
3
3
  class Season
4
+ extend Forwardable
4
5
 
5
6
  SEASONS = Hash[21, :spring, 22, :summer, 23, :autumn, 24, :winter].freeze
6
7
 
@@ -13,7 +14,6 @@ module EDTF
13
14
  NORTHERN_MONTHS = Hash[*NORTHERN.map { |s,ms| ms.map { |m| [m,s] } }.flatten].freeze
14
15
  SOUTHERN_MONTHS = Hash[*SOUTHERN.map { |s,ms| ms.map { |m| [m,s] } }.flatten].freeze
15
16
 
16
- extend Forwardable
17
17
 
18
18
  include Comparable
19
19
  include Enumerable
@@ -26,10 +26,10 @@ module EDTF
26
26
 
27
27
  attr_reader :season, :year
28
28
 
29
- attr_accessor :qualifier
29
+ attr_accessor :qualifier, :uncertain, :approximate
30
30
 
31
31
  def_delegators :to_range,
32
- *Range.instance_methods(false).reject { |m| m.to_s =~ /^(each|eql?|hash)$/ }
32
+ *Range.instance_methods(false).reject { |m| m.to_s =~ /^(each|min|max|cover?|inspect)$|^\W/ }
33
33
 
34
34
  SEASONS.each_value do |s|
35
35
  define_method("#{s}?") { @season == s }
@@ -44,7 +44,7 @@ module EDTF
44
44
  alias_method("#{quarter}!", "#{season}!")
45
45
  end
46
46
 
47
-
47
+
48
48
  def initialize(*arguments)
49
49
  arguments.flatten!
50
50
  raise ArgumentError, "wrong number of arguments (#{arguments.length} for 0..3)" if arguments.length > 3
@@ -54,7 +54,7 @@ module EDTF
54
54
  when Date
55
55
  @year, @season = arguments[0].year, NORTHERN_MONTHS[arguments[0]]
56
56
  when Symbol, String
57
- @year, @season = Date.today.year, SEASONS[CODES[arguments[0].intern]]
57
+ @year, @season = Date.today.year, SEASONS[CODES[arguments[0].to_sym]]
58
58
  else
59
59
  self.year = arguments[0]
60
60
  @season = NORTHERN_MONTHS[Date.today.month]
@@ -66,9 +66,49 @@ module EDTF
66
66
  end
67
67
  end
68
68
 
69
+
70
+ [:uncertain, :approximate].each do |m|
71
+
72
+ define_method("#{m}?") { !!send(m) }
73
+
74
+ define_method("#{m}!") do
75
+ send("#{m}=", true)
76
+ self
77
+ end
78
+ end
79
+
80
+ def certain?; !uncertain; end
81
+ def precise?; !approximate; end
82
+
83
+ def certain!
84
+ @uncertain = false
85
+ self
86
+ end
87
+
88
+ def precise!
89
+ @approximate = false
90
+ end
91
+
92
+ # Returns the next season.
93
+ def succ
94
+ s = dup
95
+ s.season = next_season_code
96
+ s.year = year + 1 if s.first?
97
+ s
98
+ end
99
+
100
+ # def next(n = 1)
101
+ # end
102
+
103
+ def cover?(other)
104
+ return false unless other.respond_to?(:day_precision)
105
+ other = other.day_precision
106
+ min.day_precision! <= other && other <= max.day_precision!
107
+ end
108
+
69
109
  def each
70
110
  if block_given?
71
- to_range(&Proc.new)
111
+ to_range.each(&Proc.new)
72
112
  self
73
113
  else
74
114
  to_enum
@@ -88,16 +128,19 @@ module EDTF
88
128
 
89
129
  def qualified?; !!@qualifier; end
90
130
 
91
- def to_s
131
+ def edtf
92
132
  '%04d-%2d%s' % [year, CODES[season], qualified? ? "^#{qualifier}" : '']
93
133
  end
94
134
 
95
- alias edtf to_s
135
+ alias to_s edtf
136
+
96
137
 
97
138
  def <=>(other)
98
139
  case other
99
140
  when Date
100
141
  cover?(other) ? 0 : to_date <=> other
142
+ when Interval, Epoch
143
+ [min, max] <=> [other.min, other.max]
101
144
  when Season
102
145
  [year, month, qualifier] <=> [other.year, other.month, other.qualifier]
103
146
  else
@@ -117,18 +160,31 @@ module EDTF
117
160
  Date.new(year, month, 1)
118
161
  end
119
162
 
163
+ alias min to_date
164
+
165
+ def max
166
+ to_date.months_since(2).end_of_month
167
+ end
168
+
120
169
  # Returns a Range that covers the season (a three month period).
121
170
  def to_range
122
- d = to_date
123
- d .. d.months_since(2).end_of_month
171
+ min .. max
124
172
  end
125
-
173
+
126
174
  protected
127
175
 
128
176
  def month
129
177
  NORTHERN[@season][0]
130
178
  end
131
179
 
180
+ def season_code
181
+ CODES[season]
182
+ end
183
+
184
+ def next_season_code(by = 1)
185
+ ((season_code + by) % 4) + 20
186
+ end
187
+
132
188
  end
133
189
 
134
190
  end
@@ -1,3 +1,3 @@
1
1
  module EDTF
2
- VERSION = '0.0.9'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
@@ -97,7 +97,7 @@ describe 'Date/DateTime' do
97
97
  describe '#next' do
98
98
 
99
99
  it 'returns the successor when given no argument' do
100
- Date.edtf('1999').next.year.should == 2000
100
+ Date.edtf('1999').next[0].year.should == 2000
101
101
  end
102
102
 
103
103
  it 'returns an array of the next 3 elements when passed 3 as an argument' do
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module EDTF
4
+
5
+ describe Decade do
6
+
7
+ it { should_not be nil }
8
+
9
+ describe '.current' do
10
+ it 'creates the current decade' do
11
+ Decade.current.year.should == (Time.now.year / 10) * 10
12
+ end
13
+ end
14
+
15
+ describe '#min' do
16
+ it 'the year 1990 should be the minimum of the 1990s' do
17
+ Decade.new(1999).min.should == Date.new(1990).year_precision!
18
+ end
19
+ end
20
+
21
+ describe '#max' do
22
+ it 'the year 1999 should be the maximum of the 1990s' do
23
+ Decade.new(1999).max.should == Date.new(1999).year_precision!
24
+ end
25
+
26
+ it 'has year precision' do
27
+ Decade.new(1999).max.should be_year_precision
28
+ end
29
+ end
30
+
31
+ describe '#cover?' do
32
+ it '1989-12-31 should be covered by the 1980s' do
33
+ Decade.new(1980).should be_cover(Date.new(1989,12,31))
34
+ end
35
+ end
36
+
37
+ describe '#to_range' do
38
+
39
+ it 'returns a range' do
40
+ Decade.new.to_range.should be_instance_of(::Range)
41
+ end
42
+
43
+ it 'the range starts with a date object' do
44
+ Decade.new.to_range.begin.should be_instance_of(::Date)
45
+ end
46
+
47
+ it 'the range ends with a date object' do
48
+ Decade.new.to_range.end.should be_instance_of(::Date)
49
+ end
50
+
51
+ it 'the range start has year precision' do
52
+ Decade.new.to_range.begin.should be_year_precision
53
+ end
54
+
55
+ it 'the range end has year precision' do
56
+ Decade.new.to_range.end.should be_year_precision
57
+ end
58
+
59
+ end
60
+
61
+ describe 'enumeration' do
62
+
63
+ it 'always covers ten years' do
64
+ Decade.new.to_a.should have(10).elements
65
+ end
66
+
67
+ it 'the 1970s should map to the years [1970, 1971, ... , 1979]' do
68
+ Decade.new(1970).map(&:year).should == [1970, 1971, 1972, 1973, 1974, 1975, 1976, 1977, 1978, 1979]
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+
75
+ describe Century do
76
+
77
+ it { should_not be nil }
78
+
79
+ describe '.current' do
80
+ it 'creates the current century' do
81
+ Century.current.year.should == (Time.now.year / 100) * 100
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+
88
+ end
@@ -88,6 +88,20 @@ module EDTF
88
88
  d.qualifier.should == 'european'
89
89
  end
90
90
 
91
+ it 'parses uncertain seasons' do
92
+ Parser.new.parse!('2003-23?').should be_uncertain
93
+ end
94
+
95
+ it 'parses approximate seasons' do
96
+ Parser.new.parse!('2003-23~').should be_approximate
97
+ end
98
+
99
+ it 'parses uncertain and approximate seasons' do
100
+ Parser.new.parse!('2003-23?~').should be_uncertain
101
+ Parser.new.parse!('2003-23?~').should be_approximate
102
+ end
103
+
104
+
91
105
  it 'parses positive scientific long years' do
92
106
  Parser.new.parse('y17e7').year.should == 170000000
93
107
  end
@@ -98,14 +112,14 @@ module EDTF
98
112
 
99
113
  it 'parses masked precision date strings (decades)' do
100
114
  d = Parser.new.parse!('198x')
101
- d.should include(Date.new(1983,3,12))
102
- d.should_not include(Date.new(1990,1,1))
115
+ d.should be_cover(Date.new(1983,3,12))
116
+ d.should_not be_cover(Date.new(1990,1,1))
103
117
  end
104
118
 
105
119
  it 'parses masked precision date strings (centuries)' do
106
120
  d = Parser.new.parse!('18xx')
107
- d.should include(Date.new(1848,1,14))
108
- d.should_not include(Date.new(1799,12,31))
121
+ d.should be_cover(Date.new(1848,1,14))
122
+ d.should_not be_cover(Date.new(1799,12,31))
109
123
  end
110
124
 
111
125
  it 'parses multiple dates (years)' do
@@ -1,7 +1,56 @@
1
1
  module EDTF
2
2
  describe 'Seasons' do
3
- let(:subject) { Season.new }
3
+ let(:subject) { Season.new }
4
+ let(:summer) { Season.new(:summer) }
5
+ let(:winter) { Season.new(:winter) }
4
6
 
7
+
8
+ describe 'uncertain/approximate' do
9
+
10
+ it 'is certain by default' do
11
+ subject.should be_certain
12
+ subject.should_not be_uncertain
13
+ end
14
+
15
+ it 'is precise by default' do
16
+ subject.should be_precise
17
+ subject.should_not be_approximate
18
+ end
19
+
20
+ describe '#approximate!' do
21
+ it 'makes the season approximate' do
22
+ subject.approximate!.should be_approximate
23
+ subject.approximate!.should_not be_precise
24
+ end
25
+ end
26
+
27
+ describe '#uncertain!' do
28
+ it 'makes the season uncertain' do
29
+ subject.uncertain!.should be_uncertain
30
+ subject.uncertain!.should_not be_certain
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ describe '#succ' do
37
+
38
+ it 'returns a season' do
39
+ summer.succ.should be_instance_of(Season)
40
+ end
41
+
42
+ it 'it returns a season that is greater than the original one' do
43
+ summer.succ.should > summer
44
+ end
45
+
46
+ it 'the successor of the winter season is spring of the following year' do
47
+ spring = winter.succ
48
+ spring.should be_spring
49
+ spring.year.should == winter.year + 1
50
+ end
51
+
52
+ end
53
+
5
54
  describe '#season?' do
6
55
  it 'returns true by default' do
7
56
  subject.should be_season
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: edtf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.9
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-11-09 00:00:00.000000000 Z
12
+ date: 2011-11-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
16
- requirement: &70355024984460 !ruby/object:Gem::Requirement
16
+ requirement: &70108307717900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '3.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70355024984460
24
+ version_requirements: *70108307717900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &70355024983720 !ruby/object:Gem::Requirement
27
+ requirement: &70108307716720 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0.9'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70355024983720
35
+ version_requirements: *70108307716720
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: racc
38
- requirement: &70355024982860 !ruby/object:Gem::Requirement
38
+ requirement: &70108307715280 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '1.4'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70355024982860
46
+ version_requirements: *70108307715280
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: cucumber
49
- requirement: &70355024981980 !ruby/object:Gem::Requirement
49
+ requirement: &70108307714220 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '1.0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70355024981980
57
+ version_requirements: *70108307714220
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: rspec
60
- requirement: &70355024981220 !ruby/object:Gem::Requirement
60
+ requirement: &70108307713500 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: '2.6'
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70355024981220
68
+ version_requirements: *70108307713500
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: ZenTest
71
- requirement: &70355024980360 !ruby/object:Gem::Requirement
71
+ requirement: &70108307712900 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,7 +76,7 @@ dependencies:
76
76
  version: '4.6'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70355024980360
79
+ version_requirements: *70108307712900
80
80
  description: A Ruby implementation of the Extended Date/Time Format (EDTF).
81
81
  email:
82
82
  - http://sylvester.keil.or.at
@@ -127,7 +127,7 @@ files:
127
127
  - spec/edtf/uncertainty_spec.rb
128
128
  - spec/spec_helper.rb
129
129
  - lib/edtf/parser.rb
130
- homepage: http://inukshuk.github.com/edtf-ruby
130
+ homepage: http://github.com/inukshuk/edtf-ruby
131
131
  licenses:
132
132
  - FreeBSD
133
133
  post_install_message:
@@ -149,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
149
149
  version: '0'
150
150
  segments:
151
151
  - 0
152
- hash: -1918843685167052448
152
+ hash: -2732104332429493129
153
153
  required_rubygems_version: !ruby/object:Gem::Requirement
154
154
  none: false
155
155
  requirements:
@@ -158,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
158
  version: '0'
159
159
  segments:
160
160
  - 0
161
- hash: -1918843685167052448
161
+ hash: -2732104332429493129
162
162
  requirements: []
163
163
  rubyforge_project:
164
164
  rubygems_version: 1.8.10