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