Skip to content

Commit 2d45074

Browse files
authored
Merge pull request #264 from invenia/rf/utcdatetimes-post-merge-changes
Addressing post-merge comments on #263
2 parents 4306505 + 6458712 commit 2d45074

File tree

3 files changed

+51
-83
lines changed

3 files changed

+51
-83
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "LibPQ"
22
uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
33
license = "MIT"
4-
version = "1.15.0"
4+
version = "1.15.1"
55

66
[deps]
77
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"

src/parsing.jl

+39-59
Original file line numberDiff line numberDiff line change
@@ -238,77 +238,66 @@ end
238238
# see https://github.com/invenia/LibPQ.jl/issues/33
239239
_trunc_seconds(str) = replace(str, r"(\.[\d]{3})\d+" => s"\g<1>")
240240

241-
_DEFAULT_TYPE_MAP[:timestamp] = DateTime
242-
const TIMESTAMP_FORMAT = dateformat"y-m-d HH:MM:SS.s" # .s is optional here
243-
function pqparse(::Type{DateTime}, str::AbstractString)
241+
# Utility function for handling "infinity" strings for datetime types to reduce duplication
242+
function _tryparse_datetime_inf(
243+
typ::Type{T}, str, f=typ
244+
)::Union{T, Nothing} where T <: Dates.AbstractDateTime
244245
if str == "infinity"
245246
depwarn_timetype_inf()
246-
return typemax(DateTime)
247+
return f(typemax(DateTime))
247248
elseif str == "-infinity"
248249
depwarn_timetype_inf()
249-
return typemin(DateTime)
250+
return f(typemin(DateTime))
250251
end
251252

252-
# Cut off digits after the third after the decimal point,
253-
# since DateTime in Julia currently handles only milliseconds, see Issue #33
254-
str = replace(str, r"(\.[\d]{3})\d+" => s"\g<1>")
255-
return parse(DateTime, str, TIMESTAMP_FORMAT)
253+
return nothing
254+
end
255+
256+
_DEFAULT_TYPE_MAP[:timestamp] = DateTime
257+
const TIMESTAMP_FORMAT = dateformat"y-m-d HH:MM:SS.s" # .s is optional here
258+
function pqparse(::Type{DateTime}, str::AbstractString)
259+
parsed = _tryparse_datetime_inf(DateTime, str)
260+
isnothing(parsed) || return parsed
261+
262+
parsed = tryparse(DateTime, str, TIMESTAMP_FORMAT)
263+
isnothing(parsed) || return parsed
264+
265+
return parse(DateTime, _trunc_seconds(str), TIMESTAMP_FORMAT)
256266
end
257267

258268
# ISO, YMD
259269
_DEFAULT_TYPE_MAP[:timestamptz] = ZonedDateTime
260-
const TIMESTAMPTZ_ZDT_FORMATS = (
270+
const TIMESTAMPTZ_FORMATS = (
261271
dateformat"y-m-d HH:MM:SSz",
262272
dateformat"y-m-d HH:MM:SS.sz",
263273
dateformat"y-m-d HH:MM:SS.ssz",
264274
dateformat"y-m-d HH:MM:SS.sssz",
265275
)
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-
)
272276

273-
timestamptz_formats(::Type{ZonedDateTime}) = TIMESTAMPTZ_ZDT_FORMATS
274-
timestamptz_formats(::Type{UTCDateTime}) = TIMESTAMPTZ_UTC_FORMATS
277+
function pqparse(::Type{ZonedDateTime}, str::AbstractString)
278+
parsed = _tryparse_datetime_inf(ZonedDateTime, str, Base.Fix2(ZonedDateTime, tz"UTC"))
279+
isnothing(parsed) || return parsed
275280

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+
for fmt in TIMESTAMPTZ_FORMATS[1:(end - 1)]
282+
parsed = tryparse(ZonedDateTime, str, fmt)
283+
isnothing(parsed) || return parsed
281284
end
282285

283-
return parse(T, _trunc_seconds(str), formats[end])
286+
return parse(ZonedDateTime, _trunc_seconds(str), TIMESTAMPTZ_FORMATS[end])
284287
end
285288

286-
function pqparse(::Type{ZonedDateTime}, str::AbstractString)
287-
if str == "infinity"
288-
depwarn_timetype_inf()
289-
return ZonedDateTime(typemax(DateTime), tz"UTC")
290-
elseif str == "-infinity"
291-
depwarn_timetype_inf()
292-
return ZonedDateTime(typemin(DateTime), tz"UTC")
293-
end
289+
function pqparse(::Type{UTCDateTime}, str::AbstractString)
290+
parsed = _tryparse_datetime_inf(UTCDateTime, str)
291+
isnothing(parsed) || return parsed
294292

295-
return _pqparse(ZonedDateTime, str)
296-
end
293+
# Postgres should always give us strings ending with +00 if our timezone is set to UTC
294+
# which is the default
295+
str = replace(str, "+00" => "")
297296

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))
305-
end
297+
parsed = tryparse(UTCDateTime, str, TIMESTAMP_FORMAT)
298+
isnothing(parsed) || return parsed
306299

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|:]*$" => ""))
300+
return parse(UTCDateTime, _trunc_seconds(str), TIMESTAMP_FORMAT)
312301
end
313302

314303
_DEFAULT_TYPE_MAP[:date] = Date
@@ -363,7 +352,7 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
363352
end
364353

365354
function Base.parse(::Type{UTCDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
366-
return UTCDateTime(unix2datetime(parse(Int64, pqv)))
355+
return UTCDateTime(parse(DateTime, pqv))
367356
end
368357

369358
# All postgresql timestamptz are stored in UTC time with the epoch of 2000-01-01.
@@ -387,16 +376,7 @@ function pqparse(::Type{ZonedDateTime}, ptr::Ptr{UInt8})
387376
end
388377

389378
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)
379+
return UTCDateTime(pqparse(DateTime, ptr))
400380
end
401381

402382
function pqparse(::Type{DateTime}, ptr::Ptr{UInt8})
@@ -408,7 +388,7 @@ function pqparse(::Type{DateTime}, ptr::Ptr{UInt8})
408388
depwarn_timetype_inf()
409389
return typemin(DateTime)
410390
end
411-
return POSTGRES_EPOCH_DATETIME + Microsecond(ntoh(unsafe_load(Ptr{Int64}(ptr))))
391+
return POSTGRES_EPOCH_DATETIME + Microsecond(value)
412392
end
413393

414394
function pqparse(::Type{Date}, ptr::Ptr{UInt8})

test/runtests.jl

+11-23
Original file line numberDiff line numberDiff line change
@@ -1327,29 +1327,6 @@ end
13271327
finally
13281328
close(result)
13291329
end
1330-
1331-
# Test parsing timestamptz as UTCDateTime
1332-
if data isa ZonedDateTime
1333-
try
1334-
result = execute(
1335-
conn,
1336-
"SELECT $test_str;";
1337-
binary_format=binary_format,
1338-
type_map=Dict(:timestamptz => UTCDateTime),
1339-
)
1340-
1341-
oid = LibPQ.column_oids(result)[1]
1342-
func = result.column_funcs[1]
1343-
parsed = func(LibPQ.PQValue{oid}(result, 1, 1))
1344-
@test isequal(parsed, data)
1345-
@test typeof(parsed) == UTCDateTime
1346-
parsed_no_oid = func(LibPQ.PQValue(result, 1, 1))
1347-
@test isequal(parsed_no_oid, data)
1348-
@test typeof(parsed_no_oid) == UTCDateTime
1349-
finally
1350-
close(result)
1351-
end
1352-
end
13531330
end
13541331

13551332
close(conn)
@@ -1369,6 +1346,13 @@ end
13691346
("'foobar'", Symbol, :foobar),
13701347
("0::int8", DateTime, DateTime(1970, 1, 1, 0)),
13711348
("0::int8", ZonedDateTime, ZonedDateTime(1970, 1, 1, 0, tz"UTC")),
1349+
("0::int8", UTCDateTime, UTCDateTime(1970, 1, 1, 0)),
1350+
("'2004-10-19 10:23:54+00'::timestamptz", UTCDateTime, UTCDateTime(2004, 10, 19, 10, 23, 54)),
1351+
("'2004-10-19 10:23:54-06'::timestamptz", UTCDateTime, UTCDateTime(2004, 10, 19, 16, 23, 54)),
1352+
("'[2010-01-01 14:30-00, 2010-01-01 15:30-00)'::tstzrange", Interval{UTCDateTime}, Interval{Closed, Open}(UTCDateTime(2010, 1, 1, 14, 30), UTCDateTime(2010, 1, 1, 15, 30))),
1353+
("'[2004-10-19 10:23:54-02, 2004-10-19 11:23:54-02)'::tstzrange", Interval{UTCDateTime}, Interval{Closed, Open}(UTCDateTime(2004, 10, 19, 12, 23, 54), UTCDateTime(2004, 10, 19, 13, 23, 54))),
1354+
("'[2004-10-19 10:23:54-02, Infinity)'::tstzrange", Interval{UTCDateTime}, Interval{Closed, Open}(UTCDateTime(2004, 10, 19, 12, 23, 54), UTCDateTime(typemax(DateTime)))),
1355+
("'(-Infinity, Infinity)'::tstzrange", Interval{UTCDateTime}, Interval{Open, Open}(UTCDateTime(typemin(DateTime)), UTCDateTime(typemax(DateTime)))),
13721356
("'{{{1,2,3},{4,5,6}}}'::int2[]", AbstractArray{Int16}, reshape(Int16[1 2 3; 4 5 6], 1, 2, 3)),
13731357
("DATE '2017-01-31'", InfExtendedTime{Date}, InfExtendedTime{Date}(Date(2017, 1, 31))),
13741358
("'infinity'::timestamp", InfExtendedTime{Date}, InfExtendedTime{Date}(∞)),
@@ -1378,6 +1362,10 @@ end
13781362
("'-infinity'::timestamptz", InfExtendedTime{ZonedDateTime}, InfExtendedTime{ZonedDateTime}(-∞)),
13791363
("'[2004-10-19 10:23:54-02, infinity)'::tstzrange", Interval{InfExtendedTime{ZonedDateTime}}, Interval{Closed, Open}(ZonedDateTime(2004, 10, 19, 12, 23, 54, tz"UTC"), ∞)),
13801364
("'(-infinity, infinity)'::tstzrange", Interval{InfExtendedTime{ZonedDateTime}}, Interval{InfExtendedTime{ZonedDateTime}, Open, Open}(-∞, ∞)),
1365+
("'infinity'::timestamptz", InfExtendedTime{UTCDateTime}, InfExtendedTime{UTCDateTime}(∞)),
1366+
("'-infinity'::timestamptz", InfExtendedTime{UTCDateTime}, InfExtendedTime{UTCDateTime}(-∞)),
1367+
("'[2004-10-19 10:23:54-02, infinity)'::tstzrange", Interval{InfExtendedTime{UTCDateTime}}, Interval{Closed, Open}(UTCDateTime(2004, 10, 19, 12, 23, 54), ∞)),
1368+
("'(-infinity, infinity)'::tstzrange", Interval{InfExtendedTime{UTCDateTime}}, Interval{InfExtendedTime{UTCDateTime}, Open, Open}(-∞, ∞)),
13811369
]
13821370

13831371
for (test_str, typ, data) in test_data

0 commit comments

Comments
 (0)