@@ -257,12 +257,32 @@ end
257257
258258# ISO, YMD
259259_DEFAULT_TYPE_MAP[:timestamptz ] = ZonedDateTime
260- const TIMESTAMPTZ_FORMATS = (
260+ const TIMESTAMPTZ_ZDT_FORMATS = (
261261 dateformat " y-m-d HH:MM:SSz" ,
262262 dateformat " y-m-d HH:MM:SS.sz" ,
263263 dateformat " y-m-d HH:MM:SS.ssz" ,
264264 dateformat " y-m-d HH:MM:SS.sssz" ,
265265)
266+ const TIMESTAMPTZ_UTC_FORMATS = (
267+ dateformat " y-m-d HH:MM:SS" ,
268+ dateformat " y-m-d HH:MM:SS.s" ,
269+ dateformat " y-m-d HH:MM:SS.ss" ,
270+ dateformat " y-m-d HH:MM:SS.sss" ,
271+ )
272+
273+ timestamptz_formats (:: Type{ZonedDateTime} ) = TIMESTAMPTZ_ZDT_FORMATS
274+ timestamptz_formats (:: Type{UTCDateTime} ) = TIMESTAMPTZ_UTC_FORMATS
275+
276+ function _pqparse (:: Type{T} , str:: AbstractString ) where T<: Union{UTCDateTime, ZonedDateTime}
277+ formats = timestamptz_formats (T)
278+ for fmt in formats[1 : (end - 1 )]
279+ parsed = tryparse (T, str, fmt)
280+ parsed != = nothing && return parsed
281+ end
282+
283+ return parse (T, _trunc_seconds (str), formats[end ])
284+ end
285+
266286function pqparse (:: Type{ZonedDateTime} , str:: AbstractString )
267287 if str == " infinity"
268288 depwarn_timetype_inf ()
@@ -272,12 +292,23 @@ function pqparse(::Type{ZonedDateTime}, str::AbstractString)
272292 return ZonedDateTime (typemin (DateTime), tz " UTC" )
273293 end
274294
275- for fmt in TIMESTAMPTZ_FORMATS[1 : (end - 1 )]
276- parsed = tryparse (ZonedDateTime, str, fmt)
277- parsed != = nothing && return parsed
295+ return _pqparse (ZonedDateTime, str)
296+ end
297+
298+ function pqparse (:: Type{UTCDateTime} , str:: AbstractString )
299+ if str == " infinity"
300+ depwarn_timetype_inf ()
301+ return UTCDateTime (typemax (DateTime))
302+ elseif str == " -infinity"
303+ depwarn_timetype_inf ()
304+ return UTCDateTime (typemin (DateTime))
278305 end
279306
280- return parse (ZonedDateTime, _trunc_seconds (str), TIMESTAMPTZ_FORMATS[end ])
307+ # Postgres should give us strings ending with +00, +00:00, -00:00
308+ # We use the regex below to strip these character off before parsing, iff,
309+ # the values after the `-`/`+` are `0` or `:`. This means parsing will fail if
310+ # we're asked to parse a non-UTC string like +04:00.
311+ return _pqparse (UTCDateTime, replace (str, r" [-|\+ ][0|:]*$" => " " ))
281312end
282313
283314_DEFAULT_TYPE_MAP[:date ] = Date
@@ -331,6 +362,10 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
331362 return TimeZones. unix2zdt (parse (Int64, pqv))
332363end
333364
365+ function Base. parse (:: Type{UTCDateTime} , pqv:: PQValue{PQ_SYSTEM_TYPES[:int8]} )
366+ return UTCDateTime (unix2datetime (parse (Int64, pqv)))
367+ end
368+
334369# All postgresql timestamptz are stored in UTC time with the epoch of 2000-01-01.
335370const POSTGRES_EPOCH_DATE = Date (" 2000-01-01" )
336371const POSTGRES_EPOCH_DATETIME = DateTime (" 2000-01-01" )
@@ -351,6 +386,19 @@ function pqparse(::Type{ZonedDateTime}, ptr::Ptr{UInt8})
351386 return ZonedDateTime (dt, tz " UTC" ; from_utc= true )
352387end
353388
389+ function pqparse (:: Type{UTCDateTime} , ptr:: Ptr{UInt8} )
390+ value = ntoh (unsafe_load (Ptr {Int64} (ptr)))
391+ if value == typemax (Int64)
392+ depwarn_timetype_inf ()
393+ return UTCDateTime (typemax (DateTime))
394+ elseif value == typemin (Int64)
395+ depwarn_timetype_inf ()
396+ return UTCDateTime (typemin (DateTime))
397+ end
398+ dt = POSTGRES_EPOCH_DATETIME + Microsecond (value)
399+ return UTCDateTime (dt)
400+ end
401+
354402function pqparse (:: Type{DateTime} , ptr:: Ptr{UInt8} )
355403 value = ntoh (unsafe_load (Ptr {Int64} (ptr)))
356404 if value == typemax (Int64)
0 commit comments