Skip to content

Commit 2fc84d8

Browse files
committed
Added support for parsing timestamptz as UTCDateTime.
1 parent e65ee52 commit 2fc84d8

File tree

4 files changed

+84
-10
lines changed

4 files changed

+84
-10
lines changed

Project.toml

Lines changed: 3 additions & 1 deletion
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.14.1"
4+
version = "1.15.0"
55

66
[deps]
77
CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"
@@ -20,6 +20,7 @@ OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
2020
SQLStrings = "af517c2e-c243-48fa-aab8-efac3db270f5"
2121
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
2222
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"
23+
UTCDateTimes = "0f7cfa37-7abf-4834-b969-a8aa512401c2"
2324

2425
[compat]
2526
CEnum = "0.2, 0.3, 0.4"
@@ -36,6 +37,7 @@ OffsetArrays = "0.9.1, 0.10, 0.11, 1"
3637
SQLStrings = "0.1"
3738
Tables = "0.2, 1"
3839
TimeZones = "0.9.2, 0.10, 0.11, 1"
40+
UTCDateTimes = "1.5"
3941
julia = "1.6"
4042

4143
[extras]

src/LibPQ.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ using Memento: Memento, getlogger, warn, info, error, debug
2828
using OffsetArrays
2929
using SQLStrings
3030
using TimeZones
31+
using UTCDateTimes
3132

3233
const Parameter = Union{String,Missing}
3334
const LOGGER = getlogger(@__MODULE__)

src/parsing.jl

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
266285
function 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|:]*$" => ""))
281311
end
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))
332362
end
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.
335369
const POSTGRES_EPOCH_DATE = Date("2000-01-01")
336370
const 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)
352386
end
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+
354401
function pqparse(::Type{DateTime}, ptr::Ptr{UInt8})
355402
value = ntoh(unsafe_load(Ptr{Int64}(ptr)))
356403
if value == typemax(Int64)

test/runtests.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ using OffsetArrays
1313
using SQLStrings
1414
using TimeZones
1515
using Tables
16+
using UTCDateTimes
1617

1718
Memento.config!("critical")
1819

@@ -1326,6 +1327,29 @@ end
13261327
finally
13271328
close(result)
13281329
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
13291353
end
13301354

13311355
close(conn)

0 commit comments

Comments
 (0)