citeproc 0.0.8 → 0.0.9
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/.document +5 -0
- data/.gitignore +5 -0
- data/.travis.yml +13 -0
- data/.yardopts +2 -0
- data/AGPL +662 -0
- data/{LICENSE → BSDL} +2 -1
- data/Gemfile +13 -2
- data/README.md +32 -1
- data/Rakefile +39 -0
- data/citeproc.gemspec +18 -11
- data/lib/citeproc.rb +13 -11
- data/lib/citeproc/abbreviate.rb +1 -4
- data/lib/citeproc/assets.rb +11 -4
- data/lib/citeproc/attributes.rb +102 -95
- data/lib/citeproc/bibliography.rb +66 -42
- data/lib/citeproc/citation_data.rb +57 -46
- data/lib/citeproc/compatibility.rb +161 -4
- data/lib/citeproc/date.rb +517 -225
- data/lib/citeproc/engine.rb +0 -2
- data/lib/citeproc/errors.rb +4 -4
- data/lib/citeproc/extensions.rb +6 -5
- data/lib/citeproc/item.rb +85 -22
- data/lib/citeproc/names.rb +642 -543
- data/lib/citeproc/variable.rb +149 -70
- data/lib/citeproc/version.rb +1 -1
- data/spec/citeproc/assets_spec.rb +10 -4
- data/spec/citeproc/date_spec.rb +274 -8
- data/spec/citeproc/item_spec.rb +23 -4
- data/spec/citeproc/names_spec.rb +601 -486
- data/spec/citeproc/variable_spec.rb +4 -12
- data/spec/spec_helper.rb +13 -0
- metadata +64 -31
data/lib/citeproc/date.rb
CHANGED
@@ -1,229 +1,521 @@
|
|
1
|
-
|
2
1
|
module CiteProc
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
3
|
+
# Represents a {Variable} wrapping a date value. A date value is a hybrid
|
4
|
+
# object in that it can represent either an atomic date or a date range,
|
5
|
+
# depending on whether or not the 'date-parts' attribute contains one
|
6
|
+
# or two lists of date parts.
|
7
|
+
#
|
8
|
+
# {Date Dates} can be constructed from a wide range of input values,
|
9
|
+
# including Ruby date objects, integers, date ranges, ISO 8601 and
|
10
|
+
# CiteProc JSON strings or hashes, and - provided you have the respective
|
11
|
+
# gems installed - EDTF strings all strings supported by Chronic.
|
12
|
+
#
|
13
|
+
# @example Initialization
|
14
|
+
# CiteProc::Date.new
|
15
|
+
# #-> #<CiteProc::Date "[]">
|
16
|
+
#
|
17
|
+
# CiteProc::Date.today
|
18
|
+
# #-> #<CiteProc::Date "[2012, 6, 10]">
|
19
|
+
#
|
20
|
+
# CiteProc::Date.new('Yesterday')
|
21
|
+
# #-> #<CiteProc::Date "[[2012, 6, 9]]">
|
22
|
+
#
|
23
|
+
# CiteProc::Date.new(1966)
|
24
|
+
# #-> #<CiteProc::Date "[1966]">
|
25
|
+
#
|
26
|
+
# CiteProc::Date.new(1999..2003)
|
27
|
+
# #-> #<CiteProc::Date "[[1999], [2003]]">
|
28
|
+
#
|
29
|
+
# CiteProc::Date.new(Date.new(1900)...Date.new(2000))
|
30
|
+
# #-> #<CiteProc::Date "[[1900, 1, 1], [1999, 12, 31]]">
|
31
|
+
#
|
32
|
+
# CiteProc::Date.new('2009-03?')
|
33
|
+
# #-> #<CiteProc::Date "[[2009, 3]]">
|
34
|
+
#
|
35
|
+
# CiteProc::Date.new('2001-02/2007')
|
36
|
+
# #-> #<CiteProc::Date "[[2001, 2, 1], [2007, 12, 31]]">
|
37
|
+
#
|
38
|
+
# {Date} instances are typically manipulated by a cite processor. Therefore,
|
39
|
+
# the API is optimized for easy information extraction and formatting.
|
40
|
+
# Additionally, {Date Dates} can be serialized as CiteProc JSON data.
|
41
|
+
#
|
42
|
+
# @example Serialization
|
43
|
+
# CiteProc::Date.new('2009-03?').to_citeproc
|
44
|
+
# #-> {"date-parts"=>[[2009, 3]], "circa"=>true}
|
45
|
+
#
|
46
|
+
# CiteProc::Date.new(1999..2003).to_json
|
47
|
+
# #-> '{"date-parts":[[1999],[2003]]}'
|
48
|
+
#
|
49
|
+
class Date < Variable
|
50
|
+
|
51
|
+
|
52
|
+
# Represents the individual parts of a date (i.e., year, month, day).
|
53
|
+
# There is a sublte difference between CiteProc dates (and date parts)
|
54
|
+
# and regular Ruby dates, because a Ruby date will always contain valid
|
55
|
+
# year, month and date values, whereas CiteProc dates may leave the month
|
56
|
+
# and day parts empty. That is to say, CiteProc distinguishes between
|
57
|
+
# the first of May 1955 and the month of May 1955 - a distinction that
|
58
|
+
# is not supported by regular Ruby dates.
|
59
|
+
#
|
60
|
+
# may_1955 = CiteProc::Date::DateParts.new(1955, 5)
|
61
|
+
# first_of_may_1955 = CiteProc::Date::DateParts.new(1955, 5, 1)
|
62
|
+
#
|
63
|
+
# may_1955 < first_of_may_1955
|
64
|
+
# #-> true
|
65
|
+
#
|
66
|
+
# Date.new(1955, 5) < Date.new(1955, 5, 1)
|
67
|
+
# #-> false
|
68
|
+
#
|
69
|
+
# The above example shows that a month's sort order is less than a day
|
70
|
+
# in that month, whereas, with Ruby date's there is no such distinction.
|
71
|
+
#
|
72
|
+
# The {DateParts} class encapsulates the year, month and day parts of a
|
73
|
+
# date; it is used internally by {Date} variables and not supposed to
|
74
|
+
# be used in an external context.
|
75
|
+
class DateParts < Struct.new(:year, :month, :day)
|
76
|
+
include Comparable
|
77
|
+
|
78
|
+
def initialize(*arguments)
|
79
|
+
if arguments.length == 1 && arguments[0].is_a?(::Date)
|
80
|
+
d = arguments[0]
|
81
|
+
super(d.year, d.month, d.day)
|
82
|
+
else
|
83
|
+
super(*arguments.map(&:to_i))
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def initialize_copy(other)
|
88
|
+
update(other)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Update the date parts with the passed-in values.
|
92
|
+
# @param parts [Array, #each_pair] an ordered list of date parts (year,
|
93
|
+
# month, day) or a Hash containing the mapping
|
94
|
+
# @return [self]
|
95
|
+
def update(parts)
|
96
|
+
unless parts.respond_to?(:each_pair)
|
97
|
+
parts = Hash[DateParts.members.zip(parts)]
|
98
|
+
end
|
99
|
+
|
100
|
+
parts.each_pair do |part, value|
|
101
|
+
self[part] = value.nil? ? nil : value.to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
self
|
105
|
+
end
|
106
|
+
|
107
|
+
# @return [Boolean] whether or not the date parts are unset
|
108
|
+
def empty?
|
109
|
+
to_citeproc.empty?
|
110
|
+
end
|
111
|
+
|
112
|
+
# In the current CiteProc specification, date parts consisting of
|
113
|
+
# zeroes are used to designate open ranges.
|
114
|
+
# @return [Boolean] whether or not the this is an open-end date
|
115
|
+
def open?
|
116
|
+
to_citeproc.include?(0)
|
117
|
+
end
|
118
|
+
|
119
|
+
# A date is said to be BC when the year is defined and less than zero.
|
120
|
+
# @return [Boolean] whether or not the date is BC
|
121
|
+
def bc?
|
122
|
+
!!(year && year < 0)
|
123
|
+
end
|
124
|
+
|
125
|
+
# A date is said to be AD when it is in the first millennium, i.e.,
|
126
|
+
# between 1 and 1000 AD
|
127
|
+
# @return [Boolean] whether or not the date is AD
|
128
|
+
def ad?
|
129
|
+
!bc? && year < 1000
|
130
|
+
end
|
131
|
+
|
132
|
+
# Formats the date parts according to the passed-in format string.
|
133
|
+
# @param format [String] a format string
|
134
|
+
# @return [String,nil] the formatted date string; nil if the date
|
135
|
+
# parts are not a valid date.
|
136
|
+
def strftime(format = '%F')
|
137
|
+
d = to_date
|
138
|
+
|
139
|
+
if d.nil?
|
140
|
+
nil
|
141
|
+
else
|
142
|
+
d.strftime(format)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Compares the date parts with the passed-in date.
|
147
|
+
# @param other [DateParts, #to_date] the other date
|
148
|
+
# @return [Fixnum,nil] the result of the comparison (-1, 0, 1 or nil)
|
149
|
+
def <=>(other)
|
150
|
+
case
|
151
|
+
when other.is_a?(DateParts)
|
152
|
+
to_citeproc <=> other.to_citeproc
|
153
|
+
when other.respond_to?(:to_date)
|
154
|
+
to_date <=> other.to_date
|
155
|
+
else
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Convert the date parts into a proper Ruby date object; if the date
|
161
|
+
# parts are empty, contain zero or are otherwise invalid, nil will
|
162
|
+
# be returned instead.
|
163
|
+
# @return [::Date,nil] the date parts as a Ruby date object
|
164
|
+
def to_date
|
165
|
+
parts = to_citeproc
|
166
|
+
|
167
|
+
if parts.empty? || parts.include?(0)
|
168
|
+
nil
|
169
|
+
else
|
170
|
+
begin
|
171
|
+
::Date.new(*parts)
|
172
|
+
rescue
|
173
|
+
# Catch invalid dates (e.g., if the month is 13).
|
174
|
+
nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
alias to_ruby to_date
|
180
|
+
|
181
|
+
# @return [Array<Fixnum>] the list of date parts
|
182
|
+
def to_citeproc
|
183
|
+
take_while { |p| !p.nil? }
|
184
|
+
end
|
185
|
+
|
186
|
+
# @return [String] the date parts as a string
|
187
|
+
def to_s
|
188
|
+
to_citeproc.inspect
|
189
|
+
end
|
190
|
+
|
191
|
+
# @return [String] a human-readable representation of the object
|
192
|
+
def inspect
|
193
|
+
"#<DateParts #{to_s}>"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
include Attributes
|
199
|
+
|
200
|
+
alias attributes value
|
201
|
+
protected :value, :attributes
|
202
|
+
|
203
|
+
undef_method :value=
|
204
|
+
|
205
|
+
|
206
|
+
# List of date parsers (must respond to #parse)
|
207
|
+
@parsers = []
|
208
|
+
|
209
|
+
[%w{ edtf EDTF }, %w{ chronic Chronic }].each do |date_parser, module_id|
|
210
|
+
begin
|
211
|
+
require date_parser
|
212
|
+
@parsers << ::Object.const_get(module_id)
|
213
|
+
rescue LoadError
|
214
|
+
warn "failed to load `#{date_parser}' gem"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
@parsers << ::Date
|
219
|
+
|
220
|
+
|
221
|
+
class << self
|
222
|
+
|
223
|
+
# @!attribute [r] parsers
|
224
|
+
#
|
225
|
+
# A list of available date parsers. Each parser must respond to a
|
226
|
+
# #parse method that converts a date string into a Ruby date object.
|
227
|
+
# By default, the list will include Ruby's date parser from the
|
228
|
+
# standard library, as well as the parsers of the Chronic and EDTF
|
229
|
+
# gems if they are available; to install the latter on your system
|
230
|
+
# make sure to `gem install chronic edtf`.
|
231
|
+
#
|
232
|
+
# @return [Array] the available date parsers
|
233
|
+
attr_reader :parsers
|
234
|
+
|
235
|
+
# Parses the passed-in string with all available date parsers. Creates
|
236
|
+
# a new CiteProc Date from the first valid date returned by a parser;
|
237
|
+
# returns nil if no parser was able to parse the string successfully.
|
238
|
+
#
|
239
|
+
# For an equivalent method that raises an error on invalid input
|
240
|
+
# @see #parse!
|
241
|
+
#
|
242
|
+
# @param date_string [String] the date to be parsed
|
243
|
+
# @return [CiteProc::Date,nil] the parsed date or nil
|
244
|
+
def parse(date_string)
|
245
|
+
parse!(date_string)
|
246
|
+
rescue ParseError
|
247
|
+
nil
|
248
|
+
end
|
249
|
+
|
250
|
+
# Like #parse but raises a ParseError if the input failed to be parsed.
|
251
|
+
#
|
252
|
+
# @param date_string [String] the date to be parsed
|
253
|
+
# @return [CiteProc::Date] the parsed date
|
254
|
+
#
|
255
|
+
# @raise [ParseError] when the string cannot be parsed
|
256
|
+
def parse!(date_string)
|
257
|
+
@parsers.each do |p|
|
258
|
+
date = p.parse(date_string) rescue nil
|
259
|
+
return new(date) unless date.nil?
|
260
|
+
end
|
261
|
+
|
262
|
+
# Subtle: if we get here it means all parsers failed to create a date
|
263
|
+
raise ParseError, "failed to parse #{date_string.inspect}"
|
264
|
+
end
|
265
|
+
|
266
|
+
# @return [CiteProc::Date] a date object for the current day
|
267
|
+
def today
|
268
|
+
new(::Date.today)
|
269
|
+
end
|
270
|
+
|
271
|
+
alias now today
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
attr_predicates :circa, :season, :literal, :'date-parts'
|
277
|
+
|
278
|
+
# Make Date behave like a regular Ruby Date
|
279
|
+
def_delegators :to_ruby,
|
280
|
+
*::Date.instance_methods(false).reject { |m| m.to_s =~ /^to_s$|^inspect$|start$|^\W|uncertain|season/ }
|
281
|
+
|
282
|
+
|
283
|
+
def initialize(value = {})
|
284
|
+
super
|
285
|
+
yield self if block_given?
|
286
|
+
end
|
287
|
+
|
288
|
+
def initialize_copy(other)
|
289
|
+
@value = other.value.deep_copy
|
290
|
+
end
|
291
|
+
|
292
|
+
def merge(other)
|
293
|
+
super
|
294
|
+
convert_parts!
|
295
|
+
end
|
296
|
+
|
297
|
+
# Replaces the date's value. Typically called by the constructor, this
|
298
|
+
# method intelligently converts various input values.
|
299
|
+
def replace(value)
|
300
|
+
case
|
301
|
+
when value.is_a?(CiteProc::Date)
|
302
|
+
initialize_copy(value)
|
303
|
+
when value.is_a?(::Date) && Object.const_defined?(:EDTF)
|
304
|
+
@value = { :'date-parts' => [DateParts.new(*value.values)] }
|
305
|
+
uncertain! if value.uncertain?
|
306
|
+
when value.respond_to?(:strftime)
|
307
|
+
@value = { :'date-parts' => [DateParts.new(*value.strftime('%Y-%m-%d').split(/-/))] }
|
308
|
+
when value.is_a?(Numeric)
|
309
|
+
@value = { :'date-parts' => [DateParts.new(value)] }
|
310
|
+
when value.is_a?(Hash)
|
311
|
+
attributes = value.symbolize_keys
|
312
|
+
|
313
|
+
if attributes.has_key?(:raw)
|
314
|
+
@value = Date.parse(attributes.delete(:raw)).value
|
315
|
+
@value.merge!(attributes)
|
316
|
+
else
|
317
|
+
@value = attributes.deep_copy
|
318
|
+
end
|
319
|
+
convert_parts!
|
320
|
+
|
321
|
+
when value.is_a?(Array)
|
322
|
+
@value = { :'date-parts' => value[0].is_a?(Array) ? value : [value] }
|
323
|
+
convert_parts!
|
324
|
+
when !value.is_a?(String) && value.respond_to?(:min) && value.respond_to?(:max)
|
325
|
+
@value = { :'date-parts' => [
|
326
|
+
DateParts.new(value.min),
|
327
|
+
DateParts.new(value.max)
|
328
|
+
]}
|
329
|
+
when value.is_a?(String) && /^\s*\{/ =~ value
|
330
|
+
return replace(MultiJson.decode(value, :symbolize_keys => true))
|
331
|
+
when value.respond_to?(:to_s)
|
332
|
+
@value = Date.parse!(value.to_s).value
|
333
|
+
else
|
334
|
+
raise TypeError, "failed to create date from #{value.inspect}"
|
335
|
+
end
|
336
|
+
|
337
|
+
self
|
338
|
+
end
|
339
|
+
|
340
|
+
# @return [Array<DateParts>]
|
341
|
+
def date_parts
|
342
|
+
@value[:'date-parts'] ||= []
|
343
|
+
end
|
344
|
+
|
345
|
+
alias parts date_parts
|
346
|
+
alias parts= date_parts=
|
347
|
+
|
348
|
+
# @return [Boolean] whether or not the date parts' are empty and the
|
349
|
+
# date is neither literal nor a season
|
350
|
+
def empty?
|
351
|
+
parts.all?(&:empty?) && !literal? && !season?
|
352
|
+
end
|
353
|
+
|
354
|
+
# @!attribute year
|
355
|
+
# @return [Fixnum] the year (of the start date for ranges)
|
356
|
+
|
357
|
+
# @!attribute month
|
358
|
+
# @return [Fixnum] the month (of the start date for ranges)
|
359
|
+
|
360
|
+
# @!attribute day
|
361
|
+
# @return [Fixnum] the day (of the start date for ranges)
|
362
|
+
[:year, :month, :day].each do |reader|
|
363
|
+
writer = "#{reader}="
|
364
|
+
|
365
|
+
define_method(reader) do
|
366
|
+
d = parts[0] and d.send(reader)
|
367
|
+
end
|
368
|
+
|
369
|
+
define_method(writer) do |v|
|
370
|
+
parts[0] ||= DateParts.new
|
371
|
+
parts[0].send(writer, v.to_i)
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
# @return [Date] a copy of the date with an inverted year
|
376
|
+
def -@
|
377
|
+
d = dup
|
378
|
+
d.year = -1 * year
|
379
|
+
d
|
380
|
+
end
|
381
|
+
|
382
|
+
# @return [::Date,nil] the date (start date if this instance is a range); or nil
|
383
|
+
def start_date
|
384
|
+
d = parts[0] and d.to_date
|
385
|
+
end
|
386
|
+
|
387
|
+
def start_date=(date)
|
388
|
+
parts[0] = DateParts.new(date.strftime('%Y-%m-%d').split(/-/))
|
389
|
+
end
|
390
|
+
|
391
|
+
def end_date=(date)
|
392
|
+
parts[1] = DateParts.new(date.nil? ? 0 : date.strftime('%Y-%m-%d').split(/-/))
|
393
|
+
end
|
394
|
+
|
395
|
+
# @return [Date,Range] the date as a Ruby date object or as a Range if
|
396
|
+
# this instance is closed range
|
397
|
+
def to_ruby
|
398
|
+
if closed_range?
|
399
|
+
start_date..end_date
|
400
|
+
else
|
401
|
+
start_date
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
# @return [::Date,nil] the range's end date; or nil
|
406
|
+
def end_date
|
407
|
+
d = parts[1] and d.to_date
|
408
|
+
end
|
409
|
+
|
410
|
+
# @return [Boolean] whether or not the date-parts contain an end date
|
411
|
+
def has_end_date?
|
412
|
+
parts[1] && !parts[1].empty?
|
413
|
+
end
|
414
|
+
|
415
|
+
# Returns true if this date is a range
|
416
|
+
alias range? has_end_date?
|
417
|
+
|
418
|
+
# @return [Boolean] whether or not this date is an open range
|
419
|
+
def open_range?
|
420
|
+
range? && parts[1].open?
|
421
|
+
end
|
422
|
+
|
423
|
+
alias open? open_range?
|
424
|
+
|
425
|
+
# @return [Boolean] whether or not this date is a closed range
|
426
|
+
def closed_range?
|
427
|
+
range? && !open_range?
|
428
|
+
end
|
429
|
+
|
430
|
+
alias closed? closed_range?
|
431
|
+
|
432
|
+
alias uncertain? circa?
|
433
|
+
|
434
|
+
# Marks the date as uncertain
|
435
|
+
# @return [self]
|
436
|
+
def uncertain!
|
437
|
+
@value[:circa] = true
|
438
|
+
self
|
439
|
+
end
|
440
|
+
|
441
|
+
# Marks the date as a certain date
|
442
|
+
# @return [self]
|
443
|
+
def certain!
|
444
|
+
@value[:circa] = false
|
445
|
+
self
|
446
|
+
end
|
447
|
+
|
448
|
+
def certain?
|
449
|
+
!uncertain?
|
450
|
+
end
|
451
|
+
|
452
|
+
# @return false
|
453
|
+
def numeric?
|
454
|
+
false
|
455
|
+
end
|
456
|
+
|
457
|
+
# A date is said to be BC when the year is defined and less than zero.
|
458
|
+
# @return [Boolean, nil] whether or not the date is BC; nil if there is
|
459
|
+
# no start date set
|
460
|
+
def bc?
|
461
|
+
date = parts[0] and date.bc?
|
462
|
+
end
|
463
|
+
|
464
|
+
# A date is said to be AD when it is in the first millennium, i.e.,
|
465
|
+
# between 1 and 1000 AD
|
466
|
+
# @return [Boolean, nil] whether or not the date is AD; nil if there is
|
467
|
+
# no start date set
|
468
|
+
def ad?
|
469
|
+
date = parts[0] and date.ad?
|
470
|
+
end
|
471
|
+
|
472
|
+
# @return [Hash] a hash representation of the date.
|
473
|
+
def to_citeproc
|
474
|
+
cp = @value.stringify_keys
|
475
|
+
|
476
|
+
# Convert (or suppress empty) date-parts
|
477
|
+
if parts.all?(&:empty?)
|
478
|
+
cp.delete('date-parts')
|
479
|
+
else
|
480
|
+
cp['date-parts'] = cp['date-parts'].map(&:to_citeproc)
|
481
|
+
end
|
482
|
+
|
483
|
+
cp
|
484
|
+
end
|
485
|
+
|
486
|
+
# @return [String] the date as a string
|
487
|
+
def to_s
|
488
|
+
case
|
489
|
+
when literal?
|
490
|
+
literal
|
491
|
+
when season?
|
492
|
+
season
|
493
|
+
else
|
494
|
+
parts.map(&:to_citeproc).inspect
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
def <=>(other)
|
499
|
+
case other
|
500
|
+
when Date
|
501
|
+
parts <=> other.parts
|
502
|
+
when ::Date
|
503
|
+
parts <=> [other]
|
504
|
+
else
|
505
|
+
nil
|
506
|
+
end
|
507
|
+
end
|
508
|
+
|
509
|
+
private
|
510
|
+
|
511
|
+
def convert_parts!
|
512
|
+
parts.map! do |part|
|
513
|
+
part.is_a?(DateParts) ? part : DateParts.new(*part)
|
514
|
+
end
|
515
|
+
|
516
|
+
self
|
517
|
+
end
|
518
|
+
|
519
|
+
end
|
228
520
|
|
229
521
|
end
|