diff --git a/CHANGELOG.md b/CHANGELOG.md index fbeada7..9f618ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ This project adheres to [Semantic Versioning](http://semver.org/) and XSD spec - the logical operators and the Effective Boolean Value (EBV) coercion algorithm from the XPath and SPARQL specs on `RDF.Boolean` -- `RDF.DateTime.now/0` +- various functions on the `RDF.DateTime` and `RDF.Time` datatypes - `RDF.Term.equal?/2` and `RDF.Term.equal_value?/2` - `RDF.LangString.match_language?/2` - possibility to configure an application-specific default base IRI; for now it diff --git a/lib/rdf/datatypes/date_time.ex b/lib/rdf/datatypes/date_time.ex index 431a7de..019cd0b 100644 --- a/lib/rdf/datatypes/date_time.ex +++ b/lib/rdf/datatypes/date_time.ex @@ -5,6 +5,8 @@ defmodule RDF.DateTime do use RDF.Datatype, id: RDF.Datatype.NS.XSD.dateTime + import RDF.Literal.Guards + def now() do new(DateTime.utc_now()) @@ -59,20 +61,35 @@ defmodule RDF.DateTime do end def tz(%Literal{value: %NaiveDateTime{}}), do: "" - + def tz(datetime_literal) do if valid?(datetime_literal) do - lexical = lexical(datetime_literal) - case Regex.run(~r/([+-])(\d\d:\d\d)/, lexical) do - [_, sign, zone] -> - sign <> zone - _ -> - if String.ends_with?(lexical, "Z") do - "Z" - else - "" - end - end + datetime_literal + |> lexical() + |> RDF.DateTimeUtils.tz() + end + end + + @doc """ + Converts a datetime literal to a canonical string, preserving the zone information. + """ + def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal) + when is_xsd_datetime(datatype) do + case tz(literal) do + nil -> + nil + + zone when zone in ["Z", "", "+00:00"] -> + canonical_lexical(literal.value) + + zone -> + literal + |> lexical() + |> String.replace_trailing(zone, "Z") + |> DateTime.from_iso8601() + |> elem(1) + |> canonical_lexical() + |> String.replace_trailing("Z", zone) end end diff --git a/lib/rdf/datatypes/date_time_utils.ex b/lib/rdf/datatypes/date_time_utils.ex new file mode 100644 index 0000000..67925d2 --- /dev/null +++ b/lib/rdf/datatypes/date_time_utils.ex @@ -0,0 +1,17 @@ +defmodule RDF.DateTimeUtils do + @moduledoc false + + def tz(string) do + case Regex.run(~r/([+-])(\d\d:\d\d)/, string) do + [_, sign, zone] -> + sign <> zone + _ -> + if String.ends_with?(string, "Z") do + "Z" + else + "" + end + end + end + +end diff --git a/lib/rdf/datatypes/time.ex b/lib/rdf/datatypes/time.ex index e7f6aae..48502ae 100644 --- a/lib/rdf/datatypes/time.ex +++ b/lib/rdf/datatypes/time.ex @@ -5,6 +5,8 @@ defmodule RDF.Time do use RDF.Datatype, id: RDF.Datatype.NS.XSD.time + import RDF.Literal.Guards + @grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/ @tz_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/ @@ -76,4 +78,35 @@ defmodule RDF.Time do canonical_lexical(value) <> "Z" end + def tz(time_literal) do + if valid?(time_literal) do + time_literal + |> lexical() + |> RDF.DateTimeUtils.tz() + end + end + + + @doc """ + Converts a time literal to a canonical string, preserving the zone information. + """ + def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal) + when is_xsd_time(datatype) do + case tz(literal) do + nil -> + nil + + zone when zone in ["Z", "", "+00:00"] -> + canonical_lexical(literal.value) + + zone -> + literal + |> lexical() + |> String.replace_trailing(zone, "") + |> Time.from_iso8601!() + |> canonical_lexical() + |> Kernel.<>(zone) + end + end + end diff --git a/test/unit/datatypes/date_time_test.exs b/test/unit/datatypes/date_time_test.exs index 425caae..14f92a0 100644 --- a/test/unit/datatypes/date_time_test.exs +++ b/test/unit/datatypes/date_time_test.exs @@ -123,4 +123,12 @@ defmodule RDF.DateTimeTest do end end + test "canonical_lexical_with_zone/1" do + assert RDF.date_time(~N[2010-01-01T12:34:56]) |> DateTime.canonical_lexical_with_zone() == "2010-01-01T12:34:56" + assert RDF.date_time("2010-01-01T12:34:56") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T12:34:56" + assert RDF.date_time("2010-01-01T00:00:00+00:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T00:00:00Z" + assert RDF.date_time("2010-01-01T01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T01:00:00+01:00" + assert RDF.date_time("2010-01-01 01:00:00+01:00") |> DateTime.canonical_lexical_with_zone() == "2010-01-01T01:00:00+01:00" + end + end