@@ -257,12 +257,31 @@ end
257257
258258# ISO, YMD
259259_DEFAULT_TYPE_MAP[:timestamptz ] = ZonedDateTime
260- const TIMESTAMPTZ_FORMATS = (
261- dateformat " y-m-d HH:MM:SSz" ,
262- dateformat " y-m-d HH:MM:SS.sz" ,
263- dateformat " y-m-d HH:MM:SS.ssz" ,
264- dateformat " y-m-d HH:MM:SS.sssz" ,
260+ const TIMESTAMPTZ_FORMATS = Dict (
261+ ZonedDateTime => (
262+ dateformat " y-m-d HH:MM:SSz" ,
263+ dateformat " y-m-d HH:MM:SS.sz" ,
264+ dateformat " y-m-d HH:MM:SS.ssz" ,
265+ dateformat " y-m-d HH:MM:SS.sssz" ,
266+ ),
267+ UTCDateTime => (
268+ dateformat " y-m-d HH:MM:SS" ,
269+ dateformat " y-m-d HH:MM:SS.s" ,
270+ dateformat " y-m-d HH:MM:SS.ss" ,
271+ dateformat " y-m-d HH:MM:SS.sss" ,
272+ ),
265273)
274+
275+ function _pqparse (:: Type{T} , str:: AbstractString ) where T<: Union{UTCDateTime, ZonedDateTime}
276+ formats = TIMESTAMPTZ_FORMATS[T]
277+ for fmt in formats[1 : (end - 1 )]
278+ parsed = tryparse (T, str, fmt)
279+ parsed != = nothing && return parsed
280+ end
281+
282+ return parse (T, _trunc_seconds (str), formats[end ])
283+ end
284+
266285function pqparse (:: Type{ZonedDateTime} , str:: AbstractString )
267286 if str == " infinity"
268287 depwarn_timetype_inf ()
@@ -272,12 +291,23 @@ function pqparse(::Type{ZonedDateTime}, str::AbstractString)
272291 return ZonedDateTime (typemin (DateTime), tz " UTC" )
273292 end
274293
275- for fmt in TIMESTAMPTZ_FORMATS[1 : (end - 1 )]
276- parsed = tryparse (ZonedDateTime, str, fmt)
277- parsed != = nothing && return parsed
294+ return _pqparse (ZonedDateTime, str)
295+ end
296+
297+ function pqparse (:: Type{UTCDateTime} , str:: AbstractString )
298+ if str == " infinity"
299+ depwarn_timetype_inf ()
300+ return UTCDateTime (typemax (DateTime))
301+ elseif str == " -infinity"
302+ depwarn_timetype_inf ()
303+ return UTCDateTime (typemin (DateTime))
278304 end
279305
280- return parse (ZonedDateTime, _trunc_seconds (str), TIMESTAMPTZ_FORMATS[end ])
306+ # Postgres should give us strings ending with +00, +00:00, -00:00
307+ # We use the regex below to strip these character off before parsing, iff,
308+ # the values after the `-`/`+` are `0` or `:`. This means parsing will fail if
309+ # we're asked to parse a non-UTC string like +04:00.
310+ return _pqparse (UTCDateTime, replace (str, r" [-|\+ ][0|:]*$" => " " ))
281311end
282312
283313_DEFAULT_TYPE_MAP[:date ] = Date
@@ -331,6 +361,10 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
331361 return TimeZones. unix2zdt (parse (Int64, pqv))
332362end
333363
364+ function Base. parse (:: Type{UTCDateTime} , pqv:: PQValue{PQ_SYSTEM_TYPES[:int8]} )
365+ return UTCDateTime (unix2datetime (parse (Int64, pqv)))
366+ end
367+
334368# All postgresql timestamptz are stored in UTC time with the epoch of 2000-01-01.
335369const POSTGRES_EPOCH_DATE = Date (" 2000-01-01" )
336370const POSTGRES_EPOCH_DATETIME = DateTime (" 2000-01-01" )
@@ -351,6 +385,19 @@ function pqparse(::Type{ZonedDateTime}, ptr::Ptr{UInt8})
351385 return ZonedDateTime (dt, tz " UTC" ; from_utc= true )
352386end
353387
388+ function pqparse (:: Type{UTCDateTime} , ptr:: Ptr{UInt8} )
389+ value = ntoh (unsafe_load (Ptr {Int64} (ptr)))
390+ if value == typemax (Int64)
391+ depwarn_timetype_inf ()
392+ return UTCDateTime (typemax (DateTime))
393+ elseif value == typemin (Int64)
394+ depwarn_timetype_inf ()
395+ return UTCDateTime (typemin (DateTime))
396+ end
397+ dt = POSTGRES_EPOCH_DATETIME + Microsecond (value)
398+ return UTCDateTime (dt)
399+ end
400+
354401function pqparse (:: Type{DateTime} , ptr:: Ptr{UInt8} )
355402 value = ntoh (unsafe_load (Ptr {Int64} (ptr)))
356403 if value == typemax (Int64)
0 commit comments