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).
|
[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
|
## 0.9.1 - 2020-11-16
|
||||||
|
|
||||||
Elixir versions < 1.9 are no longer supported
|
Elixir versions < 1.9 are no longer supported
|
||||||
|
|
|
@ -56,25 +56,30 @@ defmodule RDF.XSD.Date do
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl XSD.Datatype
|
@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()}
|
value | {value, XSD.Datatype.uncanonical_lexical()}
|
||||||
def elixir_mapping(value, opts)
|
def elixir_mapping(value, opts)
|
||||||
|
|
||||||
# Special case for date and dateTime, for which 0 is not a valid year
|
# 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{year: 0}, _}, _), do: @invalid_value
|
||||||
|
|
||||||
def elixir_mapping(%Date{} = value, opts) do
|
def elixir_mapping(%Date{} = value, opts) do
|
||||||
if tz = Keyword.get(opts, :tz) do
|
if tz = Keyword.get(opts, :tz) do
|
||||||
if valid_timezone?(tz) do
|
elixir_mapping({value, tz}, opts)
|
||||||
{{value, timezone_mapping(tz)}, nil}
|
|
||||||
else
|
|
||||||
@invalid_value
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
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
|
def elixir_mapping(_, _), do: @invalid_value
|
||||||
|
|
||||||
defp valid_timezone?(string), do: Regex.match?(@tz_grammar, string)
|
defp valid_timezone?(string), do: Regex.match?(@tz_grammar, string)
|
||||||
|
|
|
@ -38,29 +38,21 @@ defmodule RDF.XSD.Time do
|
||||||
@impl XSD.Datatype
|
@impl XSD.Datatype
|
||||||
def lexical_mapping(lexical, opts) do
|
def lexical_mapping(lexical, opts) do
|
||||||
case Regex.run(@grammar, lexical) do
|
case Regex.run(@grammar, lexical) do
|
||||||
[_, time] ->
|
[_, time] -> do_lexical_mapping(time, nil, opts)
|
||||||
do_lexical_mapping(time, opts)
|
[_, time, tz] -> do_lexical_mapping(time, tz, opts)
|
||||||
|
_ -> @invalid_value
|
||||||
[_, time, tz] ->
|
|
||||||
do_lexical_mapping(
|
|
||||||
time,
|
|
||||||
opts |> Keyword.put_new(:tz, tz) |> Keyword.put_new(:lexical_present, true)
|
|
||||||
)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
@invalid_value
|
|
||||||
end
|
end
|
||||||
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
|
case Time.from_iso8601(value) do
|
||||||
{:ok, time} -> elixir_mapping(time, opts)
|
{:ok, time} -> time_value(time, tz)
|
||||||
_ -> @invalid_value
|
_ -> @invalid_value
|
||||||
end
|
end
|
||||||
|> case do
|
|
||||||
{{_, true} = value, _} -> value
|
|
||||||
value -> value
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl XSD.Datatype
|
@impl XSD.Datatype
|
||||||
|
@ -70,20 +62,34 @@ defmodule RDF.XSD.Time do
|
||||||
|
|
||||||
def elixir_mapping(%Time{} = value, opts) do
|
def elixir_mapping(%Time{} = value, opts) do
|
||||||
if tz = Keyword.get(opts, :tz) do
|
if tz = Keyword.get(opts, :tz) do
|
||||||
case with_offset(value, tz) do
|
elixir_mapping({value, tz}, opts)
|
||||||
@invalid_value ->
|
|
||||||
@invalid_value
|
|
||||||
|
|
||||||
time ->
|
|
||||||
{{time, true}, unless(Keyword.get(opts, :lexical_present), do: Time.to_iso8601(value))}
|
|
||||||
end
|
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
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
|
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, zone) when zone in ~W[Z UTC GMT], do: time
|
||||||
|
|
||||||
defp with_offset(time, offset) do
|
defp with_offset(time, offset) do
|
||||||
|
|
|
@ -14,6 +14,9 @@ defmodule RDF.XSD.DateTest do
|
||||||
valid: %{
|
valid: %{
|
||||||
# input => { value, lexical, canonicalized }
|
# input => { value, lexical, canonicalized }
|
||||||
~D[2010-01-01] => {~D[2010-01-01], nil, "2010-01-01"},
|
~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-01" => {~D[2010-01-01], nil, "2010-01-01"},
|
||||||
"2010-01-01Z" => {{~D[2010-01-01], "Z"}, nil, "2010-01-01Z"},
|
"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"},
|
"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,
|
false,
|
||||||
2010,
|
2010,
|
||||||
3.14,
|
3.14,
|
||||||
# this value representation is just internal and not accepted as
|
{~D[2010-01-01], "01:00"},
|
||||||
{~D[2010-01-01], "Z"}
|
{~D[2010-01-01], true},
|
||||||
|
{"2010-01-01", "Z"},
|
||||||
|
{~D[0000-01-01], "Z"}
|
||||||
]
|
]
|
||||||
|
|
||||||
describe "new/2" do
|
describe "new/2" do
|
||||||
|
|
|
@ -15,6 +15,10 @@ defmodule RDF.XSD.TimeTest do
|
||||||
# input => { value, lexical, canonicalized }
|
# input => { value, lexical, canonicalized }
|
||||||
~T[00:00:00] => {~T[00:00:00], nil, "00:00:00"},
|
~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.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" => {~T[00:00:00], nil, "00:00:00"},
|
||||||
"00:00:00.123" => {~T[00:00:00.123], nil, "00:00:00.123"},
|
"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"},
|
"00:00:00Z" => {{~T[00:00:00], true}, nil, "00:00:00Z"},
|
||||||
|
@ -41,13 +45,13 @@ defmodule RDF.XSD.TimeTest do
|
||||||
3.14,
|
3.14,
|
||||||
"00:00:00Z foo",
|
"00:00:00Z foo",
|
||||||
"foo 00:00:00Z",
|
"foo 00:00:00Z",
|
||||||
# this value representation is just internal and not accepted as
|
{~T[00:00:00], "00:00"},
|
||||||
{~T[00:00:00], true},
|
{~T[00:00:00], 42},
|
||||||
{~T[00:00:00], "Z"}
|
{"01:00:00", "+01:00"}
|
||||||
]
|
]
|
||||||
|
|
||||||
describe "new/2" do
|
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") ==
|
assert XSD.Time.new("12:00:00", tz: "+01:00") ==
|
||||||
%RDF.Literal{
|
%RDF.Literal{
|
||||||
literal: %XSD.Time{
|
literal: %XSD.Time{
|
||||||
|
|
Loading…
Reference in a new issue