Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for parsing timestamptz as UTCDateTime #263

Merged
merged 2 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "LibPQ"
uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1"
license = "MIT"
version = "1.14.1"
version = "1.15.0"

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

[compat]
CEnum = "0.2, 0.3, 0.4"
Expand All @@ -36,6 +37,7 @@ OffsetArrays = "0.9.1, 0.10, 0.11, 1"
SQLStrings = "0.1"
Tables = "0.2, 1"
TimeZones = "0.9.2, 0.10, 0.11, 1"
UTCDateTimes = "1.5"
julia = "1.6"

[extras]
Expand Down
1 change: 1 addition & 0 deletions src/LibPQ.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ using Memento: Memento, getlogger, warn, info, error, debug
using OffsetArrays
using SQLStrings
using TimeZones
using UTCDateTimes

const Parameter = Union{String,Missing}
const LOGGER = getlogger(@__MODULE__)
Expand Down
65 changes: 56 additions & 9 deletions src/parsing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,31 @@ end

# ISO, YMD
_DEFAULT_TYPE_MAP[:timestamptz] = ZonedDateTime
const TIMESTAMPTZ_FORMATS = (
dateformat"y-m-d HH:MM:SSz",
dateformat"y-m-d HH:MM:SS.sz",
dateformat"y-m-d HH:MM:SS.ssz",
dateformat"y-m-d HH:MM:SS.sssz",
const TIMESTAMPTZ_FORMATS = Dict(
ZonedDateTime => (
dateformat"y-m-d HH:MM:SSz",
dateformat"y-m-d HH:MM:SS.sz",
dateformat"y-m-d HH:MM:SS.ssz",
dateformat"y-m-d HH:MM:SS.sssz",
),
UTCDateTime => (
dateformat"y-m-d HH:MM:SS",
dateformat"y-m-d HH:MM:SS.s",
dateformat"y-m-d HH:MM:SS.ss",
dateformat"y-m-d HH:MM:SS.sss",
),
)

function _pqparse(::Type{T}, str::AbstractString) where T<:Union{UTCDateTime, ZonedDateTime}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
function _pqparse(::Type{T}, str::AbstractString) where T<:Union{UTCDateTime, ZonedDateTime}
function _pqparse(
::Type{T}, str::AbstractString
) where {T<:Union{UTCDateTime,ZonedDateTime}}

formats = TIMESTAMPTZ_FORMATS[T]
rofinn marked this conversation as resolved.
Show resolved Hide resolved
for fmt in formats[1:(end - 1)]
parsed = tryparse(T, str, fmt)
parsed !== nothing && return parsed
end

return parse(T, _trunc_seconds(str), formats[end])
end

function pqparse(::Type{ZonedDateTime}, str::AbstractString)
if str == "infinity"
depwarn_timetype_inf()
Expand All @@ -272,12 +291,23 @@ function pqparse(::Type{ZonedDateTime}, str::AbstractString)
return ZonedDateTime(typemin(DateTime), tz"UTC")
end

for fmt in TIMESTAMPTZ_FORMATS[1:(end - 1)]
parsed = tryparse(ZonedDateTime, str, fmt)
parsed !== nothing && return parsed
return _pqparse(ZonedDateTime, str)
end

function pqparse(::Type{UTCDateTime}, str::AbstractString)
if str == "infinity"
depwarn_timetype_inf()
return UTCDateTime(typemax(DateTime))
elseif str == "-infinity"
depwarn_timetype_inf()
return UTCDateTime(typemin(DateTime))
end

return parse(ZonedDateTime, _trunc_seconds(str), TIMESTAMPTZ_FORMATS[end])
# Postgres should give us strings ending with +00, +00:00, -00:00
# We use the regex below to strip these character off before parsing, iff,
# the values after the `-`/`+` are `0` or `:`. This means parsing will fail if
# we're asked to parse a non-UTC string like +04:00.
return _pqparse(UTCDateTime, replace(str, r"[-|\+][0|:]*$" => ""))
rofinn marked this conversation as resolved.
Show resolved Hide resolved
end

_DEFAULT_TYPE_MAP[:date] = Date
Expand Down Expand Up @@ -331,6 +361,10 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
return TimeZones.unix2zdt(parse(Int64, pqv))
end

function Base.parse(::Type{UTCDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]})
return UTCDateTime(unix2datetime(parse(Int64, pqv)))
rofinn marked this conversation as resolved.
Show resolved Hide resolved
end

# All postgresql timestamptz are stored in UTC time with the epoch of 2000-01-01.
const POSTGRES_EPOCH_DATE = Date("2000-01-01")
const POSTGRES_EPOCH_DATETIME = DateTime("2000-01-01")
Expand All @@ -351,6 +385,19 @@ function pqparse(::Type{ZonedDateTime}, ptr::Ptr{UInt8})
return ZonedDateTime(dt, tz"UTC"; from_utc=true)
end

function pqparse(::Type{UTCDateTime}, ptr::Ptr{UInt8})
value = ntoh(unsafe_load(Ptr{Int64}(ptr)))
if value == typemax(Int64)
depwarn_timetype_inf()
return UTCDateTime(typemax(DateTime))
elseif value == typemin(Int64)
depwarn_timetype_inf()
return UTCDateTime(typemin(DateTime))
end
dt = POSTGRES_EPOCH_DATETIME + Microsecond(value)
return UTCDateTime(dt)
end
rofinn marked this conversation as resolved.
Show resolved Hide resolved

function pqparse(::Type{DateTime}, ptr::Ptr{UInt8})
value = ntoh(unsafe_load(Ptr{Int64}(ptr)))
if value == typemax(Int64)
Expand Down
24 changes: 24 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ using OffsetArrays
using SQLStrings
using TimeZones
using Tables
using UTCDateTimes

Memento.config!("critical")

Expand Down Expand Up @@ -1326,6 +1327,29 @@ end
finally
close(result)
end

# Test parsing timestamptz as UTCDateTime
if data isa ZonedDateTime
try
result = execute(
conn,
"SELECT $test_str;";
binary_format=binary_format,
type_map=Dict(:timestamptz => UTCDateTime),
)
Comment on lines +1334 to +1339

This comment was marked as outdated.


oid = LibPQ.column_oids(result)[1]
func = result.column_funcs[1]
parsed = func(LibPQ.PQValue{oid}(result, 1, 1))
@test isequal(parsed, data)
@test typeof(parsed) == UTCDateTime
parsed_no_oid = func(LibPQ.PQValue(result, 1, 1))
@test isequal(parsed_no_oid, data)
@test typeof(parsed_no_oid) == UTCDateTime
Comment on lines +1343 to +1348

This comment was marked as outdated.

finally
close(result)
end
end
rofinn marked this conversation as resolved.
Show resolved Hide resolved
end
Comment on lines +1352 to 1353
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
end
end


close(conn)
Expand Down