Last active
January 8, 2017 22:03
-
-
Save talentdeficit/927e7e1511a167bdaaa16fb9b8cb65bf to your computer and use it in GitHub Desktop.
date with infinity
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
defmodule :posterize_xt_date do | |
@moduledoc false | |
@behaviour Postgrex.Extension | |
import Postgrex.BinaryUtils, warn: false | |
use Postgrex.BinaryExtension, [send: "date_send"] | |
@pg_epoch :calendar.date_to_gregorian_days({ 2000, 1, 1 }) | |
@max_year 5874897 | |
@min_year -4713 | |
@common_era_start -1 * (:calendar.date_to_gregorian_days({ 1999, 1, 1 }) - 1) | |
# shift all negative dates so that -1 BC is 4800 AD, -4713 BC is 88 AD and | |
# -4401 is 400 AD so leap years line up | |
@proleptic_year_adj 4801 | |
# after shifting years, unshift days to get postgres encoding | |
@proleptic_day_adj :calendar.date_to_gregorian_days({ 4800, 1, 1 }) + @pg_epoch | |
@infinity 2147483647 | |
@neg_infinity -2147483648 | |
def init(_), do: :undefined | |
def encode(_) do | |
quote location: :keep do | |
{ year, month, day } -> | |
:posterize_xt_date.do_encode({ year, month, day }) | |
date when date == :infinity or date == :'-infinity' -> | |
:posterize_xt_date.do_encode(date) | |
other -> :posterize_xt_date.bad_date(other) | |
end | |
end | |
def do_encode(:infinity), do: << 4 :: int32, @infinity :: int32 >> | |
def do_encode(:'-infinity'), do: << 4 :: int32, @neg_infinity :: int32 >> | |
def do_encode({ year, month, day } = date) when year >= @min_year and year < 0 and is_integer(month) and is_integer(day) do | |
try do | |
days = :calendar.date_to_gregorian_days({ year + @proleptic_year_adj, month, day }) | |
<< 4 :: int32, (days - @proleptic_day_adj) :: int32 >> | |
rescue | |
_ in ErlangError -> bad_date(date) | |
end | |
end | |
def do_encode({ year, month, day } = date) when year > 0 and year <= @max_year and is_integer(month) and is_integer(day) do | |
try do | |
date = :calendar.date_to_gregorian_days(date) - @pg_epoch | |
<< 4 :: int32, date :: int32 >> | |
rescue | |
_ in ErlangError -> bad_date(date) | |
end | |
end | |
def do_encode(date), do: bad_date(date) | |
def decode(_) do | |
quote location: :keep do | |
<< 4 :: int32, days :: int32 >> -> | |
:posterize_xt_date.do_decode(days) | |
end | |
end | |
def do_decode(@infinity), do: :infinity | |
def do_decode(@neg_infinity), do: :'-infinity' | |
# dates get weird when dealing with BCE | |
def do_decode(days) when days < @common_era_start do | |
d = days + @proleptic_day_adj | |
{ y, m, d } = :calendar.gregorian_days_to_date(d) | |
{ y - @proleptic_year_adj, m, d } | |
end | |
def do_decode(days) do | |
:calendar.gregorian_days_to_date(days + @pg_epoch) | |
end | |
def bad_date(date) do | |
raise ArgumentError, Postgrex.Utils.encode_msg( | |
date, | |
"a date tuple (`{ Year, Month, Day }`) representing a valid date, the atom 'infinity' or the atom '-infinity'" | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment