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 +234 -32
- data/edtf.gemspec +1 -1
- data/features/parser/precision.feature +2 -2
- data/features/step_definitions/edtf_steps.rb +1 -1
- data/lib/edtf/date.rb +6 -8
- data/lib/edtf/epoch.rb +100 -51
- data/lib/edtf/interval.rb +1 -1
- data/lib/edtf/parser.y +6 -4
- data/lib/edtf/season.rb +67 -11
- data/lib/edtf/version.rb +1 -1
- data/spec/edtf/date_spec.rb +1 -1
- data/spec/edtf/epoch_spec.rb +88 -0
- data/spec/edtf/parser_spec.rb +18 -4
- data/spec/edtf/season_spec.rb +50 -1
- metadata +17 -17
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
EDTF-Ruby
|
2
2
|
=========
|
3
3
|
|
4
|
-
Ruby implementation of the [Extended
|
5
|
-
|
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
|
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
|
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 `
|
39
|
-
|
40
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
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
|
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
|
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]
|
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
|
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
|
84
|
-
|
85
|
-
|
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
|
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
|
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
|
97
|
-
> s.earlier!
|
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
|
-
=> "
|
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
|
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
|
data/edtf.gemspec
CHANGED
@@ -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://
|
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'
|
data/lib/edtf/date.rb
CHANGED
@@ -223,17 +223,15 @@ class Date
|
|
223
223
|
|
224
224
|
alias to_edtf edtf
|
225
225
|
|
226
|
-
# Returns
|
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
|
-
|
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
|
-
|
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
|
282
|
+
@precision_filter = case precision
|
285
283
|
when :year
|
286
284
|
[:year]
|
287
285
|
when :month
|
data/lib/edtf/epoch.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
-
|
21
|
+
def_delegators :to_range,
|
22
|
+
*Range.instance_methods(false).reject { |m| m.to_s =~ /^(each|min|max|cover|inspect)$|^\W/ }
|
47
23
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
data/lib/edtf/interval.rb
CHANGED
data/lib/edtf/parser.y
CHANGED
@@ -159,8 +159,10 @@ rule
|
|
159
159
|
;
|
160
160
|
|
161
161
|
|
162
|
-
|
163
|
-
|
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 =
|
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 =
|
219
|
+
result = EDTF::Century.new(d)
|
218
220
|
}
|
219
221
|
;
|
220
222
|
|
data/lib/edtf/season.rb
CHANGED
@@ -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|
|
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].
|
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
|
131
|
+
def edtf
|
92
132
|
'%04d-%2d%s' % [year, CODES[season], qualified? ? "^#{qualifier}" : '']
|
93
133
|
end
|
94
134
|
|
95
|
-
alias edtf
|
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
|
-
|
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
|
data/lib/edtf/version.rb
CHANGED
data/spec/edtf/date_spec.rb
CHANGED
@@ -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
|
data/spec/edtf/epoch_spec.rb
CHANGED
@@ -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
|
data/spec/edtf/parser_spec.rb
CHANGED
@@ -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
|
102
|
-
d.should_not
|
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
|
108
|
-
d.should_not
|
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
|
data/spec/edtf/season_spec.rb
CHANGED
@@ -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
|
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-
|
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: &
|
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: *
|
24
|
+
version_requirements: *70108307717900
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rake
|
27
|
-
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: *
|
35
|
+
version_requirements: *70108307716720
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: racc
|
38
|
-
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: *
|
46
|
+
version_requirements: *70108307715280
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: cucumber
|
49
|
-
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: *
|
57
|
+
version_requirements: *70108307714220
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rspec
|
60
|
-
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: *
|
68
|
+
version_requirements: *70108307713500
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: ZenTest
|
71
|
-
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: *
|
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://
|
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: -
|
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: -
|
161
|
+
hash: -2732104332429493129
|
162
162
|
requirements: []
|
163
163
|
rubyforge_project:
|
164
164
|
rubygems_version: 1.8.10
|