diff --git a/README.md b/README.md index c727cab..5d36a5b 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,16 @@ To configure if enums should be converted to atoms, set `enum_config` in `pg_typ ### Datetimes +#### Timestamps Timestamps can be returned as Erlang system time in seconds (as an integer or float): `{pg_types, [{timestamp_config, float_system_time_seconds | integer_system_time_seconds}]}` +Timestamps with time zone can be returned as Erlang system time in rfc3339 +binary: + +`{pg_types, [{timestamptz_config, rfc3339_bin}]}` + Future plans include allowing this to be set per-query instead of globally. ### Incomplete Types diff --git a/src/pg_timestamp.erl b/src/pg_timestamp.erl index 695846d..c302028 100644 --- a/src/pg_timestamp.erl +++ b/src/pg_timestamp.erl @@ -81,6 +81,10 @@ time_to_seconds(H, M, S) when is_integer(H), is_integer(M), is_integer(S) -> -spec decode_timestamp(binary(), config() | []) -> datetime(). decode_timestamp(<<16#7FFFFFFFFFFFFFFF:?int64>>, _) -> infinity; decode_timestamp(<<-16#8000000000000000:?int64>>, _) -> '-infinity'; +decode_timestamp(<>, rfc3339_bin) -> + MS = Timestamp + (?POSTGRESQL_GS_EPOCH * 1000000) - (62167219200 * 1000000), + Dtz = calendar:system_time_to_rfc3339(MS, [{unit, microsecond}, {time_designator, $\s}, {offset, ""}]), + list_to_binary(Dtz); decode_timestamp(<>, float_system_time_seconds) -> %% Note: We do this instead of just dividing by 1000000 to not end up with float inaccuracies USecs = Timestamp rem ?MILLION, diff --git a/src/pg_timestampz.erl b/src/pg_timestamptz.erl similarity index 57% rename from src/pg_timestampz.erl rename to src/pg_timestamptz.erl index 959b27a..f391a54 100644 --- a/src/pg_timestampz.erl +++ b/src/pg_timestamptz.erl @@ -1,4 +1,4 @@ --module(pg_timestampz). +-module(pg_timestamptz). -behaviour(pg_types). @@ -7,16 +7,20 @@ decode/2, type_spec/0]). +-include("pg_types.hrl"). -include("pg_protocol.hrl"). +init(#{timestamptz_config := Config}) -> + {[<<"timestamptz_send">>], Config}; init(_Opts) -> - {[<<"timestamptz_send">>], []}. + Config = application:get_env(pg_types, timestamptz_config, []), + {[<<"timestamptz_send">>], Config}. encode(Timestamp, _TypeInfo) -> <<8:?int32, (pg_timestamp:encode_timestamp(Timestamp)):?int64>>. -decode(Bin, _TypeInfo) -> - pg_timestamp:decode_timestamp(Bin, []). +decode(Bin, #type_info{config=Config}) -> + pg_timestamp:decode_timestamp(Bin, Config). type_spec() -> "{{Year::integer(), Month::1..12, Day::1..31}, {Hours::integer(), Minutes::integer(), Seconds::integer() | float()}, {HourOffset::integer(), MinuteOffset::pos_integer()}}". diff --git a/test/prop_timestamp.erl b/test/prop_timestamp.erl index a014780..f0b6378 100644 --- a/test/prop_timestamp.erl +++ b/test/prop_timestamp.erl @@ -7,11 +7,11 @@ prop_int_sec_codec() -> prop_tz_int_sec_codec() -> ?FORALL(Val, int_timestamp(), - proper_lib:codec(pg_timestampz, [], Val)). + proper_lib:codec(pg_timestamptz, [], Val)). prop_tz_offset_codec() -> ?FORALL(Val, utc_offset_timestamp(), - proper_lib:codec(pg_timestampz, [], Val, apply_offset(Val))). + proper_lib:codec(pg_timestamptz, [], Val, apply_offset(Val))). apply_offset({{Year, Month, Day}, {Hour, Minute, Second}, {HourOffset, MinuteOffset}}) -> Sign = case HourOffset >= 0 of diff --git a/test/timestamptz_tests.erl b/test/timestamptz_tests.erl index d96ffd6..e247cc8 100644 --- a/test/timestamptz_tests.erl +++ b/test/timestamptz_tests.erl @@ -5,7 +5,7 @@ negative_offset_timestamptz_test() -> ?assertEqual( proper_lib:encode_decode( - pg_timestampz, + pg_timestamptz, [], {{2024, 3, 1}, {1, 0, 0}, {-5, 15}} ), @@ -15,7 +15,7 @@ negative_offset_timestamptz_test() -> positive_offset_timestamptz_test() -> ?assertEqual( proper_lib:encode_decode( - pg_timestampz, + pg_timestamptz, [], {{2024, 3, 1}, {1, 0, 0}, {5, 15}} ), @@ -25,7 +25,7 @@ positive_offset_timestamptz_test() -> no_offset_timestamptz_test() -> ?assertEqual( proper_lib:encode_decode( - pg_timestampz, + pg_timestamptz, [], {{2024, 3, 1}, {1, 0, 0}, {0, 0}} ),