edtf 0.0.9 → 1.0.0

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