Allow initialization of dates and times with timezones from tuples

This commit is contained in:
Marcel Otto 2020-12-20 02:55:24 +01:00
parent bbcbe6af31
commit ed403d9175
5 changed files with 76 additions and 36 deletions

View file

@ -5,6 +5,26 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
[Keep a CHANGELOG](http://keepachangelog.com).
## Unreleased
### Added
- `RDF.XSD.Base64Binary` datatype ([@pukkamustard](https://github.com/pukkamustard))
### Changed
- a new option `:as_value` to enforce interpretation of an input string as a value
instead of a lexical, which is needed on datatypes where the lexical space and
the value space both consist of strings
- `RDF.XSD.Date` and `RDF.XSD.Time` both can now be initialized with tuples of an
Elixir `Date` resp. `Time` value and a timezone string (previously XSD date and
time values with time zones could only be created from strings)
[Compare v0.9.1...HEAD](https://github.com/rdf-elixir/rdf-ex/compare/v0.9.1...HEAD)
## 0.9.1 - 2020-11-16
Elixir versions < 1.9 are no longer supported

View file

@ -56,25 +56,30 @@ defmodule RDF.XSD.Date do
end
@impl XSD.Datatype
@spec elixir_mapping(Date.t() | any, Keyword.t()) ::
@spec elixir_mapping(Date.t() | valid_value | any, Keyword.t()) ::
value | {value, XSD.Datatype.uncanonical_lexical()}
def elixir_mapping(value, opts)
# Special case for date and dateTime, for which 0 is not a valid year
def elixir_mapping(%Date{year: 0}, _), do: @invalid_value
def elixir_mapping({%Date{year: 0}, _}, _), do: @invalid_value
def elixir_mapping(%Date{} = value, opts) do
if tz = Keyword.get(opts, :tz) do
if valid_timezone?(tz) do
{{value, timezone_mapping(tz)}, nil}
else
@invalid_value
end
elixir_mapping({value, tz}, opts)
else
value
end
end
def elixir_mapping({%Date{} = value, tz}, _opts) when is_binary(tz) do
if valid_timezone?(tz) do
{{value, timezone_mapping(tz)}, nil}
else
@invalid_value
end
end
def elixir_mapping(_, _), do: @invalid_value
defp valid_timezone?(string), do: Regex.match?(@tz_grammar, string)

View file

@ -38,29 +38,21 @@ defmodule RDF.XSD.Time do
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case Regex.run(@grammar, lexical) do
[_, time] ->
do_lexical_mapping(time, opts)
[_, time, tz] ->
do_lexical_mapping(
time,
opts |> Keyword.put_new(:tz, tz) |> Keyword.put_new(:lexical_present, true)
)
_ ->
@invalid_value
[_, time] -> do_lexical_mapping(time, nil, opts)
[_, time, tz] -> do_lexical_mapping(time, tz, opts)
_ -> @invalid_value
end
end
defp do_lexical_mapping(value, opts) do
defp do_lexical_mapping(value, tz, opts) do
do_lexical_mapping(value, Keyword.get(opts, :tz, tz))
end
defp do_lexical_mapping(value, tz) do
case Time.from_iso8601(value) do
{:ok, time} -> elixir_mapping(time, opts)
{:ok, time} -> time_value(time, tz)
_ -> @invalid_value
end
|> case do
{{_, true} = value, _} -> value
value -> value
end
end
@impl XSD.Datatype
@ -70,20 +62,34 @@ defmodule RDF.XSD.Time do
def elixir_mapping(%Time{} = value, opts) do
if tz = Keyword.get(opts, :tz) do
case with_offset(value, tz) do
@invalid_value ->
@invalid_value
time ->
{{time, true}, unless(Keyword.get(opts, :lexical_present), do: Time.to_iso8601(value))}
end
elixir_mapping({value, tz}, opts)
else
value
end
end
def elixir_mapping({%Time{} = time, tz}, _opts) do
case time_value(time, tz) do
@invalid_value -> @invalid_value
time_with_tz -> {time_with_tz, Time.to_iso8601(time) <> if(tz == true, do: "Z", else: tz)}
end
end
def elixir_mapping(_, _), do: @invalid_value
defp time_value(time, nil), do: time
defp time_value(time, false), do: time
defp time_value(time, true), do: {time, true}
defp time_value(time, zone) when is_binary(zone) do
case with_offset(time, zone) do
@invalid_value -> @invalid_value
time -> {time, true}
end
end
defp time_value(_, _), do: @invalid_value
defp with_offset(time, zone) when zone in ~W[Z UTC GMT], do: time
defp with_offset(time, offset) do

View file

@ -14,6 +14,9 @@ defmodule RDF.XSD.DateTest do
valid: %{
# input => { value, lexical, canonicalized }
~D[2010-01-01] => {~D[2010-01-01], nil, "2010-01-01"},
{~D[2010-01-01], "Z"} => {{~D[2010-01-01], "Z"}, nil, "2010-01-01Z"},
{~D[2010-01-01], "+01:00"} => {{~D[2010-01-01], "+01:00"}, nil, "2010-01-01+01:00"},
{~D[2010-01-01], "+00:00"} => {{~D[2010-01-01], "Z"}, nil, "2010-01-01Z"},
"2010-01-01" => {~D[2010-01-01], nil, "2010-01-01"},
"2010-01-01Z" => {{~D[2010-01-01], "Z"}, nil, "2010-01-01Z"},
"2010-01-01+00:00" => {{~D[2010-01-01], "Z"}, "2010-01-01+00:00", "2010-01-01Z"},
@ -40,8 +43,10 @@ defmodule RDF.XSD.DateTest do
false,
2010,
3.14,
# this value representation is just internal and not accepted as
{~D[2010-01-01], "Z"}
{~D[2010-01-01], "01:00"},
{~D[2010-01-01], true},
{"2010-01-01", "Z"},
{~D[0000-01-01], "Z"}
]
describe "new/2" do

View file

@ -15,6 +15,10 @@ defmodule RDF.XSD.TimeTest do
# input => { value, lexical, canonicalized }
~T[00:00:00] => {~T[00:00:00], nil, "00:00:00"},
~T[00:00:00.123] => {~T[00:00:00.123], nil, "00:00:00.123"},
{~T[00:00:00], true} => {{~T[00:00:00], true}, nil, "00:00:00Z"},
{~T[00:00:00], "Z"} => {{~T[00:00:00], true}, nil, "00:00:00Z"},
{~T[01:00:00], "+01:00"} => {{~T[00:00:00], true}, "01:00:00+01:00", "00:00:00Z"},
{~T[01:00:00], "+00:00"} => {{~T[01:00:00], true}, "01:00:00+00:00", "01:00:00Z"},
"00:00:00" => {~T[00:00:00], nil, "00:00:00"},
"00:00:00.123" => {~T[00:00:00.123], nil, "00:00:00.123"},
"00:00:00Z" => {{~T[00:00:00], true}, nil, "00:00:00Z"},
@ -41,13 +45,13 @@ defmodule RDF.XSD.TimeTest do
3.14,
"00:00:00Z foo",
"foo 00:00:00Z",
# this value representation is just internal and not accepted as
{~T[00:00:00], true},
{~T[00:00:00], "Z"}
{~T[00:00:00], "00:00"},
{~T[00:00:00], 42},
{"01:00:00", "+01:00"}
]
describe "new/2" do
test "with date and tz opt" do
test "with time and tz opt" do
assert XSD.Time.new("12:00:00", tz: "+01:00") ==
%RDF.Literal{
literal: %XSD.Time{