rdf 3.2.4 → 3.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/rdf/model/literal/date.rb +27 -82
- data/lib/rdf/model/literal/datetime.rb +22 -122
- data/lib/rdf/model/literal/decimal.rb +12 -0
- data/lib/rdf/model/literal/double.rb +20 -0
- data/lib/rdf/model/literal/integer.rb +6 -0
- data/lib/rdf/model/literal/numeric.rb +154 -4
- data/lib/rdf/model/literal/temporal.rb +310 -0
- data/lib/rdf/model/literal/time.rb +26 -98
- data/lib/rdf/model/literal.rb +2 -1
- data/lib/rdf/vocab/xsd.rb +98 -98
- metadata +10 -3
@@ -67,10 +67,9 @@ module RDF; class Literal
|
|
67
67
|
##
|
68
68
|
# Returns the sum of `self` plus `other`.
|
69
69
|
#
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# If both operands are
|
73
|
-
# and the other is -INF, NaN is returned.
|
70
|
+
# From the XQuery function [op:numeric-add](https://www.w3.org/TR/xpath-functions/#func-numeric-add).
|
71
|
+
#
|
72
|
+
# @note For `xs:float` or `xs:double` values, if one of the operands is a zero or a finite number and the other is `INF` or `-INF`, `INF` or `-INF` is returned. If both operands are `INF`, `INF` is returned. If both operands are `-INF`, `-INF` is returned. If one of the operands is `INF` and the other is `-INF`, `NaN` is returned.
|
74
73
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
75
74
|
# @return [RDF::Literal::Numeric]
|
76
75
|
# @since 0.2.3
|
@@ -90,6 +89,8 @@ module RDF; class Literal
|
|
90
89
|
##
|
91
90
|
# Returns the difference of `self` minus `other`.
|
92
91
|
#
|
92
|
+
# From the XQuery function [op:numeric-subtract](https://www.w3.org/TR/xpath-functions/#func-numeric-subtract).
|
93
|
+
#
|
93
94
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
94
95
|
# @return [RDF::Literal::Numeric]
|
95
96
|
# @since 0.2.3
|
@@ -109,6 +110,8 @@ module RDF; class Literal
|
|
109
110
|
##
|
110
111
|
# Returns the product of `self` times `other`.
|
111
112
|
#
|
113
|
+
# From the XQuery function [op:numeric-multiply](https://www.w3.org/TR/xpath-functions/#func-numeric-multiply).
|
114
|
+
#
|
112
115
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
113
116
|
# @return [RDF::Literal::Numeric]
|
114
117
|
# @since 0.2.3
|
@@ -130,6 +133,8 @@ module RDF; class Literal
|
|
130
133
|
#
|
131
134
|
# Promotes values, as necessary, with the result type depending on the input values.
|
132
135
|
#
|
136
|
+
# From the XQuery function [math:pow](https://www.w3.org/TR/xpath-functions/#func-numeric-pow).
|
137
|
+
#
|
133
138
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
134
139
|
# @return [RDF::Literal::Numeric]
|
135
140
|
# @since 0.2.3
|
@@ -143,6 +148,8 @@ module RDF; class Literal
|
|
143
148
|
##
|
144
149
|
# Exponent − Performs remainder of `self` divided by `other`.
|
145
150
|
#
|
151
|
+
# From the XQuery function [math:mod](https://www.w3.org/TR/xpath-functions/#func-numeric-mod).
|
152
|
+
#
|
146
153
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
147
154
|
# @return [RDF::Literal]
|
148
155
|
# @since 0.2.3
|
@@ -165,6 +172,8 @@ module RDF; class Literal
|
|
165
172
|
# As a special case, if the types of both $arg1 and $arg2 are xsd:integer,
|
166
173
|
# then the return type is xsd:decimal.
|
167
174
|
#
|
175
|
+
# From the XQuery function [op:numeric-divide](https://www.w3.org/TR/xpath-functions/#func-numeric-divide).
|
176
|
+
#
|
168
177
|
# @param [Literal::Numeric, #to_i, #to_f, #to_d] other
|
169
178
|
# @return [RDF::Literal::Numeric]
|
170
179
|
# @raise [ZeroDivisionError] if divided by zero
|
@@ -183,8 +192,11 @@ module RDF; class Literal
|
|
183
192
|
##
|
184
193
|
# Returns the absolute value of `self`.
|
185
194
|
#
|
195
|
+
# From the XQuery function [fn:abs](https://www.w3.org/TR/xpath-functions/#func-abs).
|
196
|
+
#
|
186
197
|
# @return [RDF::Literal]
|
187
198
|
# @raise [NotImplementedError] unless implemented in subclass
|
199
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-abs
|
188
200
|
def abs
|
189
201
|
raise NotImplementedError
|
190
202
|
end
|
@@ -192,8 +204,11 @@ module RDF; class Literal
|
|
192
204
|
##
|
193
205
|
# Returns the number with no fractional part that is closest to the argument. If there are two such numbers, then the one that is closest to positive infinity is returned. An error is raised if arg is not a numeric value.
|
194
206
|
#
|
207
|
+
# From the XQuery function [fn:round](https://www.w3.org/TR/xpath-functions/#func-round).
|
208
|
+
#
|
195
209
|
# @return [RDF::Literal]
|
196
210
|
# @raise [NotImplementedError] unless implemented in subclass
|
211
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-round
|
197
212
|
def round
|
198
213
|
raise NotImplementedError
|
199
214
|
end
|
@@ -201,10 +216,13 @@ module RDF; class Literal
|
|
201
216
|
##
|
202
217
|
# Returns the smallest integer greater than or equal to `self`.
|
203
218
|
#
|
219
|
+
# From the XQuery function [fn:ceil](https://www.w3.org/TR/xpath-functions/#func-ceil).
|
220
|
+
#
|
204
221
|
# @example
|
205
222
|
# RDF::Literal(1).ceil #=> RDF::Literal(1)
|
206
223
|
#
|
207
224
|
# @return [RDF::Literal]
|
225
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-ceil
|
208
226
|
def ceil
|
209
227
|
self
|
210
228
|
end
|
@@ -212,14 +230,146 @@ module RDF; class Literal
|
|
212
230
|
##
|
213
231
|
# Returns the largest integer less than or equal to `self`.
|
214
232
|
#
|
233
|
+
# From the XQuery function [fn:floor](https://www.w3.org/TR/xpath-functions/#func-floor).
|
234
|
+
#
|
215
235
|
# @example
|
216
236
|
# RDF::Literal(1).floor #=> RDF::Literal(1)
|
217
237
|
#
|
218
238
|
# @return [RDF::Literal]
|
239
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-floor
|
219
240
|
def floor
|
220
241
|
self
|
221
242
|
end
|
222
243
|
|
244
|
+
##
|
245
|
+
# Returns the value of `e`<sup>`x`</sup>.
|
246
|
+
#
|
247
|
+
# @return [Double]
|
248
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-exp
|
249
|
+
def exp
|
250
|
+
Double.new(Math.exp(self.to_f))
|
251
|
+
end
|
252
|
+
|
253
|
+
##
|
254
|
+
# Returns the value of `10`<sup>`x`</sup>.
|
255
|
+
#
|
256
|
+
# @return [Double]
|
257
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-exp10
|
258
|
+
def exp10
|
259
|
+
Double.new(10**self.to_f)
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Returns the natural logarithm of the argument.
|
264
|
+
#
|
265
|
+
# @return [Double]
|
266
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-log
|
267
|
+
def log
|
268
|
+
Double.new(Math.log(self.to_f))
|
269
|
+
rescue Math::DomainError
|
270
|
+
Double.new(::Float::NAN)
|
271
|
+
end
|
272
|
+
|
273
|
+
##
|
274
|
+
# Returns the base-ten logarithm of the argument.
|
275
|
+
#
|
276
|
+
# @return [Double]
|
277
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-log10
|
278
|
+
def log10
|
279
|
+
Double.new(Math.log10(self.to_f))
|
280
|
+
rescue Math::DomainError
|
281
|
+
Double.new(::Float::NAN)
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Returns the non-negative square root of the argument.
|
286
|
+
#
|
287
|
+
# @return [Double]
|
288
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-sqrt
|
289
|
+
def sqrt
|
290
|
+
Double.new(Math.sqrt(self.to_f))
|
291
|
+
rescue Math::DomainError
|
292
|
+
Double.new(::Float::NAN)
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Returns the sine of the argument. The argument is an angle in radians.
|
297
|
+
#
|
298
|
+
# @return [Double]
|
299
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-sin
|
300
|
+
def sin
|
301
|
+
Double.new(Math.sin(self.to_f))
|
302
|
+
rescue Math::DomainError
|
303
|
+
Double.new(::Float::NAN)
|
304
|
+
end
|
305
|
+
|
306
|
+
##
|
307
|
+
# Returns the cosine of the argument. The argument is an angle in radians.
|
308
|
+
#
|
309
|
+
# @return [Double]
|
310
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-cos
|
311
|
+
def cos
|
312
|
+
Double.new(Math.cos(self.to_f))
|
313
|
+
rescue Math::DomainError
|
314
|
+
Double.new(::Float::NAN)
|
315
|
+
end
|
316
|
+
|
317
|
+
##
|
318
|
+
# Returns the tangent of the argument. The argument is an angle in radians.
|
319
|
+
#
|
320
|
+
# @return [Double]
|
321
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-tan
|
322
|
+
def tan
|
323
|
+
Double.new(Math.tan(self.to_f))
|
324
|
+
rescue Math::DomainError
|
325
|
+
Double.new(::Float::NAN)
|
326
|
+
end
|
327
|
+
|
328
|
+
##
|
329
|
+
# Returns the arc sine of the argument.
|
330
|
+
#
|
331
|
+
# @return [Double]
|
332
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-asin
|
333
|
+
def asin
|
334
|
+
Double.new(Math.asin(self.to_f))
|
335
|
+
rescue Math::DomainError
|
336
|
+
Double.new(::Float::NAN)
|
337
|
+
end
|
338
|
+
|
339
|
+
##
|
340
|
+
# Returns the arc cosine of the argument.
|
341
|
+
#
|
342
|
+
# @return [Double]
|
343
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-acos
|
344
|
+
def acos
|
345
|
+
Double.new(Math.acos(self.to_f))
|
346
|
+
rescue Math::DomainError
|
347
|
+
Double.new(::Float::NAN)
|
348
|
+
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# Returns the arc tangent of the argument.
|
352
|
+
#
|
353
|
+
# @return [Double]
|
354
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-atan
|
355
|
+
def atan
|
356
|
+
Double.new(Math.atan(self.to_f))
|
357
|
+
rescue Math::DomainError
|
358
|
+
Double.new(::Float::NAN)
|
359
|
+
end
|
360
|
+
|
361
|
+
##
|
362
|
+
# Returns the angle in radians subtended at the origin by the point on a plane with coordinates (x, y) and the positive x-axis.
|
363
|
+
#
|
364
|
+
# @param [#to_f] arg
|
365
|
+
# @return [Double]
|
366
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-math-atan2
|
367
|
+
def atan2(arg)
|
368
|
+
Double.new(Math.atan2(self.to_f, arg.to_f))
|
369
|
+
rescue Math::DomainError
|
370
|
+
Double.new(::Float::NAN)
|
371
|
+
end
|
372
|
+
|
223
373
|
##
|
224
374
|
# Returns the value as an integer.
|
225
375
|
#
|
@@ -0,0 +1,310 @@
|
|
1
|
+
module RDF; class Literal
|
2
|
+
##
|
3
|
+
# Shared methods and class ancestry for date, time, and dateTime literal classes.
|
4
|
+
#
|
5
|
+
# @since 3.1
|
6
|
+
class Temporal < Literal
|
7
|
+
# Matches either -10:00 or -P1H0M forms
|
8
|
+
ZONE_GRAMMAR = %r(\A
|
9
|
+
(?:(?<si>[+-])(?<hr>\d{2}):(?:(?<mi>\d{2}))?)
|
10
|
+
|(?:(?<si>-)?PT(?<hr>\d{1,2})H(?:(?<mi>\d{1,2})M)?)
|
11
|
+
\z)x.freeze
|
12
|
+
|
13
|
+
##
|
14
|
+
# Compares this literal to `other` for sorting purposes.
|
15
|
+
#
|
16
|
+
# @param [Object] other
|
17
|
+
# @return [Integer] `-1`, `0`, or `1`
|
18
|
+
def <=>(other)
|
19
|
+
# If lexically invalid, use regular literal testing
|
20
|
+
return super unless self.valid? && (!other.respond_to?(:valid?) || other.valid?)
|
21
|
+
return super unless other.is_a?(self.class)
|
22
|
+
@object <=> other.object
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns `true` if this literal is equal to `other`.
|
27
|
+
#
|
28
|
+
# @param [Object] other
|
29
|
+
# @return [Boolean] `true` or `false`
|
30
|
+
# @since 0.3.0
|
31
|
+
def ==(other)
|
32
|
+
# If lexically invalid, use regular literal testing
|
33
|
+
return super unless self.valid? && (!other.respond_to?(:valid?) || other.valid?)
|
34
|
+
|
35
|
+
case other
|
36
|
+
when self.class
|
37
|
+
self.object == other.object
|
38
|
+
when Literal::Temporal
|
39
|
+
false
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Converts this literal into its canonical lexical representation.
|
47
|
+
# with date and time normalized to UTC.
|
48
|
+
#
|
49
|
+
# @return [RDF::Literal] `self`
|
50
|
+
# @see http://www.w3.org/TR/xmlschema11-2/#dateTime
|
51
|
+
def canonicalize!
|
52
|
+
if self.valid? && @zone && @zone != '+00:00'
|
53
|
+
adjust_to_timezone!
|
54
|
+
else
|
55
|
+
@string = nil
|
56
|
+
end
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
|
62
|
+
#
|
63
|
+
# @return [RDF::Literal]
|
64
|
+
def tz
|
65
|
+
RDF::Literal(@zone == "+00:00" ? 'Z' : @zone)
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
# @since 1.1.6
|
73
|
+
def timezone?
|
74
|
+
# Can only know there's a timezone from the string represntation
|
75
|
+
md = to_s.match(self.class.const_get(:GRAMMAR))
|
76
|
+
md && !!md[2]
|
77
|
+
end
|
78
|
+
alias_method :tz?, :timezone?
|
79
|
+
alias_method :has_tz?, :timezone?
|
80
|
+
alias_method :has_timezone?, :timezone?
|
81
|
+
|
82
|
+
##
|
83
|
+
# Returns the timezone part of arg as an xsd:dayTimeDuration, or `nil`
|
84
|
+
# if lexical form of literal does not include a timezone.
|
85
|
+
#
|
86
|
+
# From [fn:timezone-from-date](https://www.w3.org/TR/xpath-functions/#func-timezone-from-date).
|
87
|
+
#
|
88
|
+
# @return [RDF::Literal]
|
89
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-timezone-from-date
|
90
|
+
def timezone
|
91
|
+
if @zone
|
92
|
+
md = @zone.match(ZONE_GRAMMAR)
|
93
|
+
si, hr, mi = md[:si], md[:hr].to_i, md[:mi].to_i
|
94
|
+
si = nil unless si == "-"
|
95
|
+
res = "#{si}PT#{hr}H#{"#{mi}M" if mi > 0}"
|
96
|
+
RDF::Literal(res, datatype: RDF::URI("http://www.w3.org/2001/XMLSchema#dayTimeDuration"))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Returns `true` if the value adheres to the defined grammar of the
|
102
|
+
# datatype.
|
103
|
+
#
|
104
|
+
# Special case for date and dateTime, for which '0000' is not a valid year
|
105
|
+
#
|
106
|
+
# @return [Boolean]
|
107
|
+
# @since 0.2.1
|
108
|
+
def valid?
|
109
|
+
super && object && value !~ %r(\A0000)
|
110
|
+
end
|
111
|
+
|
112
|
+
##
|
113
|
+
# Does the literal representation include millisectonds?
|
114
|
+
#
|
115
|
+
# @return [Boolean]
|
116
|
+
# @since 1.1.6
|
117
|
+
def milliseconds?
|
118
|
+
object.strftime("%L").to_i > 0
|
119
|
+
end
|
120
|
+
alias_method :has_milliseconds?, :milliseconds?
|
121
|
+
alias_method :has_ms?, :milliseconds?
|
122
|
+
alias_method :ms?, :milliseconds?
|
123
|
+
|
124
|
+
##
|
125
|
+
# Returns the `timezone` of the literal. If the
|
126
|
+
##
|
127
|
+
# Returns the value as a string.
|
128
|
+
#
|
129
|
+
# @return [String]
|
130
|
+
def to_s
|
131
|
+
@string || (@object.strftime(self.class.const_get(:FORMAT)).sub('.000', '') + self.tz)
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Adjust the timezone.
|
136
|
+
#
|
137
|
+
# From [fn:adjust-dateTime-to-timezone](https://www.w3.org/TR/xpath-functions/#func-adjust-dateTime-to-timezone)
|
138
|
+
#
|
139
|
+
# @overload adjust_to_timezone!
|
140
|
+
# Adjusts the timezone to UTC.
|
141
|
+
#
|
142
|
+
# @return [Temporal] `self`
|
143
|
+
# @raise [RangeError] if `zone < -14*60` or `zone > 14*60`
|
144
|
+
# @overload adjust_to_timezone!(zone)
|
145
|
+
# If `zone` is nil, then the timzeone component is removed.
|
146
|
+
#
|
147
|
+
# Otherwise, the timezone is set based on the difference between the current timezone offset (if any) and `zone`.
|
148
|
+
#
|
149
|
+
# @param [String] zone (nil) In the form of {ZONE_GRAMMAR}.
|
150
|
+
# @return [Temporal] `self`
|
151
|
+
# @raise [RangeError] if `zone < -14*60` or `zone > 14*60`
|
152
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-adjust-dateTime-to-timezone
|
153
|
+
def adjust_to_timezone!(*args)
|
154
|
+
zone = args.empty? ? '+00:00' : args.first
|
155
|
+
if zone.nil?
|
156
|
+
# Remove timezone component
|
157
|
+
@object = self.class.new(@object.strftime(self.class.const_get(:FORMAT))).object
|
158
|
+
@zone = nil
|
159
|
+
else
|
160
|
+
md = zone.match(ZONE_GRAMMAR)
|
161
|
+
raise ArgumentError,
|
162
|
+
"expected #{zone.inspect} to be a xsd:dayTimeDuration or +/-HH:MM" unless md
|
163
|
+
|
164
|
+
# Adjust to zone
|
165
|
+
si, hr, mi = md[:si], md[:hr], md[:mi]
|
166
|
+
si ||= '+'
|
167
|
+
offset = hr.to_i * 60 + mi.to_i
|
168
|
+
raise ArgumentError,
|
169
|
+
"Zone adjustment of #{zone} out of range" if
|
170
|
+
md.nil? || offset > 14*60
|
171
|
+
|
172
|
+
new_zone = "%s%.2d:%.2d" % [si, hr.to_i, mi.to_i]
|
173
|
+
dt = @zone.nil? ? @object : @object.new_offset(new_zone)
|
174
|
+
@object = self.class.new(dt.strftime(self.class.const_get(:FORMAT) + new_zone)).object
|
175
|
+
@zone = new_zone
|
176
|
+
end
|
177
|
+
@string = nil
|
178
|
+
self
|
179
|
+
end
|
180
|
+
|
181
|
+
##
|
182
|
+
# Functional version of `#adjust_to_timezone!`.
|
183
|
+
#
|
184
|
+
# @overload adjust_to_timezone
|
185
|
+
# @param (see #adjust_to_timezone!)
|
186
|
+
# @return [DateTime]
|
187
|
+
# @raise (see #adjust_to_timezone!)
|
188
|
+
# @overload adjust_to_timezone(zone) (see #adjust_to_timezone!)
|
189
|
+
# @return [DateTime]
|
190
|
+
# @raise (see #adjust_to_timezone!)
|
191
|
+
def adjust_to_timezone(*args)
|
192
|
+
self.dup.adjust_to_timezone!(*args)
|
193
|
+
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# Add a Duration to a Temporal.
|
197
|
+
#
|
198
|
+
# For YearMonthDuration, turns duration into months and adds to internal DateTime object.
|
199
|
+
#
|
200
|
+
# For DayTimeDuration, turns duration into rational days, and adds to internal DateTime object.
|
201
|
+
#
|
202
|
+
# @note This depends on the parameter responding to `#to_i` or `#to_r`, which for Duration types, is implemented in the rdf-xsd gem.
|
203
|
+
#
|
204
|
+
# @param [YearMonthDuration, DayTimeDuration] other
|
205
|
+
# @return [Temporal]
|
206
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-add-yearMonthDuration-to-dateTime
|
207
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-add-dayTimeDuration-to-dateTime
|
208
|
+
def +(other)
|
209
|
+
new_dt = case other
|
210
|
+
when YearMonthDuration
|
211
|
+
@object >> other.to_i
|
212
|
+
when DayTimeDuration
|
213
|
+
@object + other.to_r
|
214
|
+
else
|
215
|
+
return super
|
216
|
+
end
|
217
|
+
|
218
|
+
dt = new_dt.strftime(self.class.const_get(:FORMAT)) + tz
|
219
|
+
self.class.new(dt)
|
220
|
+
rescue NoMethodError => e
|
221
|
+
raise "Consider including the rdf-xsd class for method implementaions: #{e.message}"
|
222
|
+
end
|
223
|
+
|
224
|
+
##
|
225
|
+
# Subtract times or durations from a temporal.
|
226
|
+
#
|
227
|
+
# @overload +(other)
|
228
|
+
# For YearMonthDuration, turns duration into months and subtracts from internal DateTime object resulting in a new {Temporal} object.
|
229
|
+
#
|
230
|
+
# For DayTimeDuration, turns duration into rational days, and subtracts from internal DateTime object resulting in a new {Temporal} object.
|
231
|
+
#
|
232
|
+
# For Temporal, subtracts the two moments resulting in a `xsd:dayTimeDuration`.
|
233
|
+
#
|
234
|
+
# @param [YearMonthDuration, DayTimeDurationm, Temporal] other
|
235
|
+
# @return [Temporal, DayTimeDuration]
|
236
|
+
# @note This depends on the parameter responding to `#to_i` or `#to_r`, which for Duration types, is implemented in the rdf-xsd gem.
|
237
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-subtract-yearMonthDuration-from-dateTime
|
238
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-subtract-dayTimeDuration-from-dateTime
|
239
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-subtract-dateTimes
|
240
|
+
def -(other)
|
241
|
+
new_dt = case other
|
242
|
+
when YearMonthDuration
|
243
|
+
@object << other.to_i
|
244
|
+
when DayTimeDuration
|
245
|
+
@object - other.to_r
|
246
|
+
when Temporal
|
247
|
+
@object - other.object
|
248
|
+
else
|
249
|
+
return super
|
250
|
+
end
|
251
|
+
|
252
|
+
if new_dt.is_a?(Rational)
|
253
|
+
RDF::Literal(new_dt, datatype: RDF::XSD.dayTimeDuration)
|
254
|
+
else
|
255
|
+
dt = new_dt.strftime(self.class.const_get(:FORMAT)) + tz
|
256
|
+
self.class.new(dt)
|
257
|
+
end
|
258
|
+
rescue NoMethodError => e
|
259
|
+
raise "Consider including the rdf-xsd class for method implementaions: #{e.message}"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Years
|
263
|
+
#
|
264
|
+
# From the XQuery function [fn:year-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-year-from-dateTime).
|
265
|
+
#
|
266
|
+
# @return [Integer]
|
267
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-year-from-dateTime
|
268
|
+
def year; Integer.new(object.year); end
|
269
|
+
|
270
|
+
# Months
|
271
|
+
#
|
272
|
+
# From the XQuery function [fn:month-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-month-from-dateTime).
|
273
|
+
#
|
274
|
+
# @return [Integer]
|
275
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-month-from-dateTime
|
276
|
+
def month; Integer.new(object.month); end
|
277
|
+
|
278
|
+
# Days
|
279
|
+
#
|
280
|
+
# From the XQuery function [fn:day-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-day-from-dateTime).
|
281
|
+
#
|
282
|
+
# @return [Integer]
|
283
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-day-from-dateTime
|
284
|
+
def day; Integer.new(object.day); end
|
285
|
+
|
286
|
+
# Hours
|
287
|
+
#
|
288
|
+
# From the XQuery function [fn:hours-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-hours-from-dateTime).
|
289
|
+
#
|
290
|
+
# @return [Integer]
|
291
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-hours-from-dateTime
|
292
|
+
def hours; Integer.new(object.hour); end
|
293
|
+
|
294
|
+
# Minutes
|
295
|
+
#
|
296
|
+
# From the XQuery function [fn:minutes-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-minutes-from-dateTime).
|
297
|
+
#
|
298
|
+
# @return [Integer]
|
299
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-minutes-from-dateTime
|
300
|
+
def minutes; Integer.new(object.min); end
|
301
|
+
|
302
|
+
# Seconds
|
303
|
+
#
|
304
|
+
# From the XQuery function [fn:seconds-from-dateTime](https://www.w3.org/TR/xpath-functions/#func-seconds-from-dateTime).
|
305
|
+
#
|
306
|
+
# @return [Decimal]
|
307
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-seconds-from-dateTime
|
308
|
+
def seconds; Decimal.new(object.strftime("%S.%L")); end
|
309
|
+
end # Temporal
|
310
|
+
end; end # RDF::Literal
|
@@ -9,91 +9,41 @@ module RDF; class Literal
|
|
9
9
|
#
|
10
10
|
# @see http://www.w3.org/TR/xmlschema11-2/#time
|
11
11
|
# @since 0.2.1
|
12
|
-
class Time <
|
12
|
+
class Time < Temporal
|
13
13
|
DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#time")
|
14
14
|
GRAMMAR = %r(\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
|
15
|
-
FORMAT = '%H:%M:%S.%L
|
15
|
+
FORMAT = '%H:%M:%S.%L'.freeze
|
16
16
|
|
17
17
|
##
|
18
|
+
# Internally, a `DateTime` is represented using a native `::DateTime`. If initialized from a `::DateTime`, the timezone is taken from that native object, otherwise, a timezone (or no timezone) is taken from the string representation having a matching `zzzzzz` component.
|
19
|
+
#
|
18
20
|
# @param [String, DateTime, #to_datetime] value
|
19
21
|
# @param (see Literal#initialize)
|
20
22
|
def initialize(value, datatype: nil, lexical: nil, **options)
|
21
23
|
@datatype = RDF::URI(datatype || self.class.const_get(:DATATYPE))
|
22
24
|
@string = lexical || (value if value.is_a?(String))
|
23
25
|
@object = case
|
24
|
-
when value.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
##
|
31
|
-
# Converts this literal into its canonical lexical representation.
|
32
|
-
#
|
33
|
-
# §3.2.8.2 Canonical representation
|
34
|
-
#
|
35
|
-
# The canonical representation for time is defined by prohibiting
|
36
|
-
# certain options from the Lexical representation (§3.2.8.1).
|
37
|
-
# Specifically, either the time zone must be omitted or, if present, the
|
38
|
-
# time zone must be Coordinated Universal Time (UTC) indicated by a "Z".
|
39
|
-
# Additionally, the canonical representation for midnight is 00:00:00.
|
40
|
-
#
|
41
|
-
# @return [RDF::Literal] `self`
|
42
|
-
# @see http://www.w3.org/TR/xmlschema11-2/#time
|
43
|
-
def canonicalize!
|
44
|
-
if self.valid?
|
45
|
-
@string = if timezone?
|
46
|
-
@object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
|
26
|
+
when value.respond_to?(:to_datetime)
|
27
|
+
dt = value.to_datetime
|
28
|
+
@zone = dt.zone
|
29
|
+
# Normalize to 1972-12-31 dateTime base
|
30
|
+
hms = dt.strftime(FORMAT)
|
31
|
+
::DateTime.parse("1972-12-31T#{hms}#{@zone}")
|
47
32
|
else
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
RDF::Literal(zone)
|
63
|
-
end
|
64
|
-
|
65
|
-
##
|
66
|
-
# Returns `true` if the value adheres to the defined grammar of the
|
67
|
-
# datatype.
|
68
|
-
#
|
69
|
-
# Special case for date and dateTime, for which '0000' is not a valid year
|
70
|
-
#
|
71
|
-
# @return [Boolean]
|
72
|
-
# @since 0.2.1
|
73
|
-
def valid?
|
74
|
-
super && !object.nil?
|
75
|
-
end
|
76
|
-
|
77
|
-
##
|
78
|
-
# Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
|
79
|
-
#
|
80
|
-
# @return [Boolean]
|
81
|
-
# @since 1.1.6
|
82
|
-
def timezone?
|
83
|
-
md = self.to_s.match(GRAMMAR)
|
84
|
-
md && !!md[2]
|
85
|
-
end
|
86
|
-
alias_method :tz?, :timezone?
|
87
|
-
alias_method :has_tz?, :timezone?
|
88
|
-
alias_method :has_timezone?, :timezone?
|
89
|
-
|
90
|
-
##
|
91
|
-
# Returns the value as a string.
|
92
|
-
# Does not normalize timezone
|
93
|
-
#
|
94
|
-
# @return [String]
|
95
|
-
def to_s
|
96
|
-
@string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
|
33
|
+
md = value.to_s.match(GRAMMAR)
|
34
|
+
_, tm, tz = Array(md)
|
35
|
+
if tz
|
36
|
+
@zone = tz == 'Z' ? '+00:00' : tz
|
37
|
+
else
|
38
|
+
@zone = nil # No timezone
|
39
|
+
end
|
40
|
+
# Normalize 24:00:00 to 00:00:00
|
41
|
+
hr, mi, se = tm.split(':')
|
42
|
+
hr = "%.2i" % (hr.to_i % 24) if hr.to_i > 23
|
43
|
+
value = "#{hr}:#{mi}:#{se}"
|
44
|
+
# Normalize to 1972-12-31 dateTime base
|
45
|
+
::DateTime.parse("1972-12-31T#{hr}:#{mi}:#{se}#{@zone}")
|
46
|
+
end rescue ::DateTime.new
|
97
47
|
end
|
98
48
|
|
99
49
|
##
|
@@ -104,32 +54,10 @@ module RDF; class Literal
|
|
104
54
|
def humanize(lang = :en)
|
105
55
|
t = object.strftime("%r")
|
106
56
|
if timezone?
|
107
|
-
|
108
|
-
|
109
|
-
else
|
110
|
-
" #{self.tz}"
|
111
|
-
end
|
57
|
+
z = @zone == '+00:00' ? "UTC" : @zone
|
58
|
+
t += " #{z}"
|
112
59
|
end
|
113
60
|
t
|
114
61
|
end
|
115
|
-
|
116
|
-
##
|
117
|
-
# Equal compares as Time objects
|
118
|
-
def ==(other)
|
119
|
-
# If lexically invalid, use regular literal testing
|
120
|
-
return super unless self.valid?
|
121
|
-
|
122
|
-
case other
|
123
|
-
when Literal::Time
|
124
|
-
return super unless other.valid?
|
125
|
-
# Compare as strings, as time includes a date portion, and adjusting for UTC
|
126
|
-
# can create a mismatch in the date portion.
|
127
|
-
self.object.new_offset.strftime('%H%M%S.%L') == other.object.new_offset.strftime('%H%M%S.%L')
|
128
|
-
when Literal::DateTime, Literal::Date
|
129
|
-
false
|
130
|
-
else
|
131
|
-
super
|
132
|
-
end
|
133
|
-
end
|
134
62
|
end # Time
|
135
63
|
end; end # RDF::Literal
|