Allow initialization of dates and times with timezones from tuples
This commit is contained in:
parent
bbcbe6af31
commit
ed403d9175
5 changed files with 76 additions and 36 deletions
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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{
|
||||
|
|
Loading…
Reference in a new issue