Skip to content

Commit

Permalink
Finish 3.2.5
Browse files Browse the repository at this point in the history
  • Loading branch information
gkellogg committed Feb 23, 2022
2 parents 28c9831 + 4b9f286 commit 13bc125
Show file tree
Hide file tree
Showing 14 changed files with 1,842 additions and 670 deletions.
2 changes: 1 addition & 1 deletion VERSION
@@ -1 +1 @@
3.2.4
3.2.5
342 changes: 171 additions & 171 deletions etc/xsd.ttl

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion lib/rdf/model/literal.rb
Expand Up @@ -77,6 +77,7 @@ def self.inherited(child)
require 'rdf/model/literal/decimal'
require 'rdf/model/literal/integer'
require 'rdf/model/literal/double'
require 'rdf/model/literal/temporal'
require 'rdf/model/literal/date'
require 'rdf/model/literal/datetime'
require 'rdf/model/literal/time'
Expand Down Expand Up @@ -295,7 +296,7 @@ def ==(other)
when self.simple? && other.simple?
self.value_hash == other.value_hash && self.value == other.value
when other.comperable_datatype?(self) || self.comperable_datatype?(other)
# Comoparing plain with undefined datatypes does not generate an error, but returns false
# Comparing plain with undefined datatypes does not generate an error, but returns false
# From data-r2/expr-equal/eq-2-2.
false
else
Expand Down
109 changes: 27 additions & 82 deletions lib/rdf/model/literal/date.rb
Expand Up @@ -4,67 +4,41 @@ module RDF; class Literal
#
# @see http://www.w3.org/TR/xmlschema11-2/#date
# @since 0.2.1
class Date < Literal
class Date < Temporal
DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#date")
GRAMMAR = %r(\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
FORMAT = '%Y-%m-%d'.freeze

##
# @param [String, Date, #to_date] value
# Internally, a `Date` is represented using a native `::DateTime` object at midnight. If initialized from a `::Date`, there is no timezone component, 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.
#
# @note If initialized using the `#to_datetime` method, time component is unchanged. Otherewise, it is set to 00:00 (midnight).
#
# @param [String, Date, #to_datetime] value
# @param (see Literal#initialize)
def initialize(value, datatype: nil, lexical: nil, **options)
@datatype = RDF::URI(datatype || self.class.const_get(:DATATYPE))
@string = lexical || (value if value.is_a?(String))
@object = case
when value.is_a?(::Date) then value
when value.respond_to?(:to_date) then value.to_date
else ::Date.parse(value.to_s)
end rescue ::Date.new
end

##
# Converts this literal into its canonical lexical representation.
#
# Note that the timezone is recoverable for xsd:date, where it is not for xsd:dateTime and xsd:time, which are both transformed relative to Z, if a timezone is provided.
#
# @return [RDF::Literal] `self`
# @see http://www.w3.org/TR/xmlschema11-2/#date
def canonicalize!
@string = @object.strftime(FORMAT) + self.tz.to_s if self.valid?
self
end

##
# Returns `true` if the value adheres to the defined grammar of the
# datatype.
#
# Special case for date and dateTime, for which '0000' is not a valid year
#
# @return [Boolean]
# @since 0.2.1
def valid?
super && object && value !~ %r(\A0000)
end

##
# Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
#
# @return [Boolean]
# @since 1.1.6
def timezone?
md = self.to_s.match(GRAMMAR)
md && !!md[2]
end
alias_method :tz?, :timezone?
alias_method :has_tz?, :timezone?
alias_method :has_timezone?, :timezone?

##
# Returns the value as a string.
#
# @return [String]
def to_s
@string || @object.strftime(FORMAT)
when value.class == ::Date
@zone = nil
# Use midnight as midpoint of the interval
::DateTime.parse(value.strftime('%FT00:00:00'))
when value.respond_to?(:to_datetime)
dt = value.to_datetime
@zone = dt.zone
dt
else
md = value.to_s.match(GRAMMAR)
_, dt, tz = Array(md)
if tz
@zone = tz == 'Z' ? '+00:00' : tz
else
@zone = nil # No timezone
end
# Use midnight as midpoint of the interval
::DateTime.parse("#{dt}T00:00:00#{@zone}")
end rescue ::DateTime.new
end

##
Expand All @@ -75,42 +49,13 @@ def to_s
def humanize(lang = :en)
d = object.strftime("%A, %d %B %Y")
if timezone?
d += if self.tz == 'Z'
d += if @zone == '+00:00'
" UTC"
else
" #{self.tz}"
" #{@zone}"
end
end
d
end

##
# Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
#
# @return [RDF::Literal]
# @since 1.1.6
def tz
md = self.to_s.match(GRAMMAR)
zone = md[2].to_s
zone = "Z" if zone == "+00:00"
RDF::Literal(zone)
end

##
# Equal compares as Date objects
def ==(other)
# If lexically invalid, use regular literal testing
return super unless self.valid?

case other
when Literal::Date
return super unless other.valid?
self.object == other.object
when Literal::Time, Literal::DateTime
false
else
super
end
end
end # Date
end; end # RDF::Literal
144 changes: 22 additions & 122 deletions lib/rdf/model/literal/datetime.rb
Expand Up @@ -2,117 +2,38 @@ module RDF; class Literal
##
# A date/time literal.
#
# @see http://www.w3.org/TR/xmlschema11-2/#dateTime#boolean
# @see http://www.w3.org/TR/xmlschema11-2/#dateTime
# @since 0.2.1
class DateTime < Literal
class DateTime < Temporal
DATATYPE = RDF::URI("http://www.w3.org/2001/XMLSchema#dateTime")
GRAMMAR = %r(\A(-?(?:\d{4}|[1-9]\d{4,})-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z).freeze
FORMAT = '%Y-%m-%dT%H:%M:%S.%L%:z'.freeze
FORMAT = '%Y-%m-%dT%H:%M:%S.%L'.freeze

##
# Internally, a `DateTime` is represented using a native `::DateTime`. If initialized from a `::Date`, there is no timezone component, 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.
#
# @param [DateTime] value
# @option options [String] :lexical (nil)
def initialize(value, datatype: nil, lexical: nil, **options)
@datatype = RDF::URI(datatype || self.class.const_get(:DATATYPE))
@string = lexical || (value if value.is_a?(String))
@object = case
when value.is_a?(::DateTime) then value
when value.respond_to?(:to_datetime) then value.to_datetime
else ::DateTime.parse(value.to_s)
end rescue ::DateTime.new
end

##
# Converts this literal into its canonical lexical representation.
# with date and time normalized to UTC.
#
# @return [RDF::Literal] `self`
# @see http://www.w3.org/TR/xmlschema11-2/#dateTime
def canonicalize!
if self.valid?
@string = if timezone?
@object.new_offset.new_offset.strftime(FORMAT[0..-4] + 'Z').sub('.000', '')
when value.is_a?(::DateTime)
@zone = value.zone
value
when value.respond_to?(:to_datetime)
@zone = value.to_datetime.zone
value.to_datetime
else
@object.strftime(FORMAT[0..-4]).sub('.000', '')
end
end
self
end

##
# Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
#
# @return [RDF::Literal]
# @see http://www.w3.org/TR/sparql11-query/#func-tz
def tz
zone = timezone? ? object.zone : ""
zone = "Z" if zone == "+00:00"
RDF::Literal(zone)
end

##
# Returns the timezone part of arg as an xsd:dayTimeDuration, or `nil`
# if lexical form of literal does not include a timezone.
#
# @return [RDF::Literal]
def timezone
if tz == 'Z'
RDF::Literal("PT0S", datatype: RDF::URI("http://www.w3.org/2001/XMLSchema#dayTimeDuration"))
elsif md = tz.to_s.match(/^([+-])?(\d+):(\d+)?$/)
plus_minus, hour, min = md[1,3]
plus_minus = nil unless plus_minus == "-"
hour = hour.to_i
min = min.to_i
res = "#{plus_minus}PT#{hour}H#{"#{min}M" if min > 0}"
RDF::Literal(res, datatype: RDF::URI("http://www.w3.org/2001/XMLSchema#dayTimeDuration"))
end
end

##
# Returns `true` if the value adheres to the defined grammar of the
# datatype.
#
# Special case for date and dateTime, for which '0000' is not a valid year
#
# @return [Boolean]
# @since 0.2.1
def valid?
super && object && value !~ %r(\A0000)
end

##
# Does the literal representation include millisectonds?
#
# @return [Boolean]
# @since 1.1.6
def milliseconds?
self.format("%L").to_i > 0
end
alias_method :has_milliseconds?, :milliseconds?
alias_method :has_ms?, :milliseconds?
alias_method :ms?, :milliseconds?

##
# Does the literal representation include a timezone? Note that this is only possible if initialized using a string, or `:lexical` option.
#
# @return [Boolean]
# @since 1.1.6
def timezone?
md = self.to_s.match(GRAMMAR)
md && !!md[2]
end
alias_method :tz?, :timezone?
alias_method :has_tz?, :timezone?
alias_method :has_timezone?, :timezone?

##
# Returns the `timezone` of the literal. If the
##
# Returns the value as a string.
#
# @return [String]
def to_s
@string || @object.strftime(FORMAT).sub("+00:00", 'Z').sub('.000', '')
md = value.to_s.match(GRAMMAR)
_, _, tz = Array(md)
if tz
@zone = tz == 'Z' ? '+00:00' : tz
else
@zone = nil # No timezone
end
::DateTime.parse(value.to_s)
end rescue ::DateTime.new
end

##
Expand All @@ -123,31 +44,10 @@ def to_s
def humanize(lang = :en)
d = object.strftime("%r on %A, %d %B %Y")
if timezone?
zone = if self.tz == 'Z'
"UTC"
else
self.tz
end
d.sub!(" on ", " #{zone} on ")
z = @zone == '+00:00' ? "UTC" : @zone
d.sub!(" on ", " #{z} on ")
end
d
end

##
# Equal compares as DateTime objects
def ==(other)
# If lexically invalid, use regular literal testing
return super unless self.valid?

case other
when Literal::DateTime
return super unless other.valid?
self.object == other.object
when Literal::Time, Literal::Date
false
else
super
end
end
end # DateTime
end; end # RDF::Literal
12 changes: 12 additions & 0 deletions lib/rdf/model/literal/decimal.rb
Expand Up @@ -54,7 +54,10 @@ def canonicalize!
##
# Returns the absolute value of `self`.
#
# From the XQuery function [fn:abs](https://www.w3.org/TR/xpath-functions/#func-abs).
#
# @return [RDF::Literal]
# @see https://www.w3.org/TR/xpath-functions/#func-abs
# @since 0.2.3
def abs
(d = to_d) && d > 0 ? self : RDF::Literal(d.abs)
Expand All @@ -63,7 +66,10 @@ def abs
##
# 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.
#
# From the XQuery function [fn:round](https://www.w3.org/TR/xpath-functions/#func-round).
#
# @return [RDF::Literal::Decimal]
# @see https://www.w3.org/TR/xpath-functions/#func-round
def round
rounded = to_d.round(half: (to_d < 0 ? :down : :up))
if rounded == -0.0
Expand All @@ -77,21 +83,27 @@ def round
##
# Returns the smallest integer greater than or equal to `self`.
#
# From the XQuery function [fn:ceil](https://www.w3.org/TR/xpath-functions/#func-ceil).
#
# @example
# RDF::Literal(1).ceil #=> RDF::Literal(1)
#
# @return [RDF::Literal::Integer]
# @see https://www.w3.org/TR/xpath-functions/#func-ceil
def ceil
RDF::Literal::Integer.new(to_d.ceil)
end

##
# Returns the largest integer less than or equal to `self`.
#
# From the XQuery function [fn:floor](https://www.w3.org/TR/xpath-functions/#func-floor).
#
# @example
# RDF::Literal(1).floor #=> RDF::Literal(1)
#
# @return [RDF::Literal::Integer]
# @see https://www.w3.org/TR/xpath-functions/#func-floor
def floor
RDF::Literal::Integer.new(to_d.floor)
end
Expand Down

0 comments on commit 13bc125

Please sign in to comment.