rdf 3.2.4 → 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -67,10 +67,9 @@ module RDF; class Literal
67
67
  ##
68
68
  # Returns the sum of `self` plus `other`.
69
69
  #
70
- # For xs:float or xs:double values, if one of the operands is a zero or a finite number
71
- # and the other is INF or -INF, INF or -INF is returned. If both operands are INF, INF is returned.
72
- # If both operands are -INF, -INF is returned. If one of the operands is INF
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 < Literal
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%:z'.freeze
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.is_a?(::DateTime) then value
25
- when value.respond_to?(:to_datetime) then value.to_datetime rescue ::DateTime.parse(value.to_s)
26
- else ::DateTime.parse(value.to_s)
27
- end rescue ::DateTime.new
28
- end
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
- @object.strftime(FORMAT[0..-4]).sub('.000', '')
49
- end
50
- end
51
- self
52
- end
53
-
54
- ##
55
- # Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
56
- #
57
- # @return [RDF::Literal]
58
- # @see http://www.w3.org/TR/sparql11-query/#func-tz
59
- def tz
60
- zone = timezone? ? object.zone : ""
61
- zone = "Z" if zone == "+00:00"
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
- t += if self.tz == 'Z'
108
- " UTC"
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