timestamp.ex (2783B)
1 defmodule Postgrex.Extensions.Timestamp do 2 @moduledoc false 3 import Postgrex.BinaryUtils, warn: false 4 use Postgrex.BinaryExtension, send: "timestamp_send" 5 6 @gs_epoch NaiveDateTime.to_gregorian_seconds(~N[2000-01-01 00:00:00]) |> elem(0) 7 @max_year 294_276 8 @min_year -4_713 9 @plus_infinity 9_223_372_036_854_775_807 10 @minus_infinity -9_223_372_036_854_775_808 11 12 def init(opts), do: Keyword.get(opts, :allow_infinite_timestamps, false) 13 14 def encode(_) do 15 quote location: :keep do 16 %NaiveDateTime{calendar: Calendar.ISO} = naive -> 17 unquote(__MODULE__).encode_elixir(naive) 18 19 %DateTime{calendar: Calendar.ISO} = dt -> 20 unquote(__MODULE__).encode_elixir(dt) 21 22 other -> 23 raise DBConnection.EncodeError, 24 Postgrex.Utils.encode_msg(other, {DateTime, NaiveDateTime}) 25 end 26 end 27 28 def decode(infinity?) do 29 quote location: :keep do 30 <<8::int32(), microsecs::int64()>> -> 31 unquote(__MODULE__).microsecond_to_elixir(microsecs, unquote(infinity?)) 32 end 33 end 34 35 ## Helpers 36 37 def encode_elixir( 38 %_{ 39 year: year, 40 hour: hour, 41 minute: min, 42 second: sec, 43 microsecond: {usec, _} 44 } = date_time 45 ) 46 when year <= @max_year and year >= @min_year and hour in 0..23 and min in 0..59 and 47 sec in 0..59 and 48 usec in 0..999_999 do 49 {gregorian_seconds, usec} = NaiveDateTime.to_gregorian_seconds(date_time) 50 secs = gregorian_seconds - @gs_epoch 51 <<8::int32(), secs * 1_000_000 + usec::int64()>> 52 end 53 54 def microsecond_to_elixir(@plus_infinity, infinity?) do 55 if infinity?, do: :inf, else: raise_infinity("infinity") 56 end 57 58 def microsecond_to_elixir(@minus_infinity, infinity?) do 59 if infinity?, do: :"-inf", else: raise_infinity("-infinity") 60 end 61 62 def microsecond_to_elixir(microsecs, _infinity) do 63 split(microsecs) 64 end 65 66 defp split(microsecs) when microsecs < 0 and rem(microsecs, 1_000_000) != 0 do 67 secs = div(microsecs, 1_000_000) - 1 68 microsecs = 1_000_000 + rem(microsecs, 1_000_000) 69 split(secs, microsecs) 70 end 71 72 defp split(microsecs) do 73 secs = div(microsecs, 1_000_000) 74 microsecs = rem(microsecs, 1_000_000) 75 split(secs, microsecs) 76 end 77 78 defp split(secs, microsecs) do 79 NaiveDateTime.from_gregorian_seconds(secs + @gs_epoch, {microsecs, 6}) 80 end 81 82 defp raise_infinity(type) do 83 raise ArgumentError, """ 84 got \"#{type}\" from PostgreSQL. If you want to support infinity timestamps \ 85 in your application, you can enable them by defining your own types: 86 87 Postgrex.Types.define(MyApp.PostgrexTypes, [], allow_infinite_timestamps: true) 88 89 And then configuring your database to use it: 90 91 types: MyApp.PostgrexTypes 92 """ 93 end 94 end