From 2505ade028545ba1a07d35ba758757e108099877 Mon Sep 17 00:00:00 2001 From: rofinn Date: Wed, 22 Feb 2023 15:30:34 -0800 Subject: [PATCH 1/4] Move UTCDateTime tests to 'Specified Types' section. --- test/runtests.jl | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index b5fc8898..37a3ba84 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1327,29 +1327,6 @@ 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), - ) - - 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 - finally - close(result) - end - end end close(conn) @@ -1369,6 +1346,13 @@ end ("'foobar'", Symbol, :foobar), ("0::int8", DateTime, DateTime(1970, 1, 1, 0)), ("0::int8", ZonedDateTime, ZonedDateTime(1970, 1, 1, 0, tz"UTC")), + ("0::int8", UTCDateTime, UTCDateTime(1970, 1, 1, 0)), + ("'2004-10-19 10:23:54+00'::timestamptz", UTCDateTime, UTCDateTime(2004, 10, 19, 10, 23, 54)), + ("'2004-10-19 10:23:54-06'::timestamptz", UTCDateTime, UTCDateTime(2004, 10, 19, 16, 23, 54)), + ("'[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))), + ("'[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))), + ("'[2004-10-19 10:23:54-02, Infinity)'::tstzrange", Interval{UTCDateTime}, Interval{Closed, Open}(UTCDateTime(2004, 10, 19, 12, 23, 54), UTCDateTime(typemax(DateTime)))), + ("'(-Infinity, Infinity)'::tstzrange", Interval{UTCDateTime}, Interval{Open, Open}(UTCDateTime(typemin(DateTime)), UTCDateTime(typemax(DateTime)))), ("'{{{1,2,3},{4,5,6}}}'::int2[]", AbstractArray{Int16}, reshape(Int16[1 2 3; 4 5 6], 1, 2, 3)), ("DATE '2017-01-31'", InfExtendedTime{Date}, InfExtendedTime{Date}(Date(2017, 1, 31))), ("'infinity'::timestamp", InfExtendedTime{Date}, InfExtendedTime{Date}(∞)), @@ -1378,6 +1362,10 @@ end ("'-infinity'::timestamptz", InfExtendedTime{ZonedDateTime}, InfExtendedTime{ZonedDateTime}(-∞)), ("'[2004-10-19 10:23:54-02, infinity)'::tstzrange", Interval{InfExtendedTime{ZonedDateTime}}, Interval{Closed, Open}(ZonedDateTime(2004, 10, 19, 12, 23, 54, tz"UTC"), ∞)), ("'(-infinity, infinity)'::tstzrange", Interval{InfExtendedTime{ZonedDateTime}}, Interval{InfExtendedTime{ZonedDateTime}, Open, Open}(-∞, ∞)), + ("'infinity'::timestamptz", InfExtendedTime{UTCDateTime}, InfExtendedTime{UTCDateTime}(∞)), + ("'-infinity'::timestamptz", InfExtendedTime{UTCDateTime}, InfExtendedTime{UTCDateTime}(-∞)), + ("'[2004-10-19 10:23:54-02, infinity)'::tstzrange", Interval{InfExtendedTime{UTCDateTime}}, Interval{Closed, Open}(UTCDateTime(2004, 10, 19, 12, 23, 54), ∞)), + ("'(-infinity, infinity)'::tstzrange", Interval{InfExtendedTime{UTCDateTime}}, Interval{InfExtendedTime{UTCDateTime}, Open, Open}(-∞, ∞)), ] for (test_str, typ, data) in test_data From 6a5b9f859b209c148738a29b4e58051ff5420619 Mon Sep 17 00:00:00 2001 From: rofinn Date: Wed, 22 Feb 2023 16:03:13 -0800 Subject: [PATCH 2/4] Add some simplifications to the UTCDateTime parsing code. --- src/parsing.jl | 51 +++++++++++++------------------------------------- 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index 4b3eda68..1f436348 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -257,31 +257,12 @@ end # ISO, YMD _DEFAULT_TYPE_MAP[:timestamptz] = ZonedDateTime -const TIMESTAMPTZ_ZDT_FORMATS = ( +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_UTC_FORMATS = ( - 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", -) - -timestamptz_formats(::Type{ZonedDateTime}) = TIMESTAMPTZ_ZDT_FORMATS -timestamptz_formats(::Type{UTCDateTime}) = TIMESTAMPTZ_UTC_FORMATS - -function _pqparse(::Type{T}, str::AbstractString) where T<:Union{UTCDateTime, ZonedDateTime} - formats = timestamptz_formats(T) - 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" @@ -292,7 +273,12 @@ function pqparse(::Type{ZonedDateTime}, str::AbstractString) return ZonedDateTime(typemin(DateTime), tz"UTC") end - return _pqparse(ZonedDateTime, str) + for fmt in TIMESTAMPTZ_FORMATS[1:(end - 1)] + parsed = tryparse(ZonedDateTime, str, fmt) + parsed !== nothing && return parsed + end + + return parse(ZonedDateTime, _trunc_seconds(str), TIMESTAMPTZ_FORMATS[end]) end function pqparse(::Type{UTCDateTime}, str::AbstractString) @@ -304,11 +290,9 @@ function pqparse(::Type{UTCDateTime}, str::AbstractString) return UTCDateTime(typemin(DateTime)) 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|:]*$" => "")) + # Postgres should always give us strings ending with +00 if our timezone is set to UTC + # which is the default + return parse(UTCDateTime, _trunc_seconds(replace(str, "+00" => "")), TIMESTAMP_FORMAT) end _DEFAULT_TYPE_MAP[:date] = Date @@ -363,7 +347,7 @@ function Base.parse(::Type{ZonedDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]}) end function Base.parse(::Type{UTCDateTime}, pqv::PQValue{PQ_SYSTEM_TYPES[:int8]}) - return UTCDateTime(unix2datetime(parse(Int64, pqv))) + return UTCDateTime(parse(DateTime, pqv)) end # All postgresql timestamptz are stored in UTC time with the epoch of 2000-01-01. @@ -387,16 +371,7 @@ function pqparse(::Type{ZonedDateTime}, ptr::Ptr{UInt8}) 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) + return UTCDateTime(pqparse(DateTime, ptr)) end function pqparse(::Type{DateTime}, ptr::Ptr{UInt8}) @@ -408,7 +383,7 @@ function pqparse(::Type{DateTime}, ptr::Ptr{UInt8}) depwarn_timetype_inf() return typemin(DateTime) end - return POSTGRES_EPOCH_DATETIME + Microsecond(ntoh(unsafe_load(Ptr{Int64}(ptr)))) + return POSTGRES_EPOCH_DATETIME + Microsecond(value) end function pqparse(::Type{Date}, ptr::Ptr{UInt8}) From b82a753e0c898172e70fa981e6dd28a8152847db Mon Sep 17 00:00:00 2001 From: rofinn Date: Thu, 23 Feb 2023 13:01:02 -0800 Subject: [PATCH 3/4] Revise all datetime pqparse methods to be more consistent. --- src/parsing.jl | 55 +++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/parsing.jl b/src/parsing.jl index 1f436348..5af837a1 100644 --- a/src/parsing.jl +++ b/src/parsing.jl @@ -238,21 +238,31 @@ end # see https://github.com/invenia/LibPQ.jl/issues/33 _trunc_seconds(str) = replace(str, r"(\.[\d]{3})\d+" => s"\g<1>") -_DEFAULT_TYPE_MAP[:timestamp] = DateTime -const TIMESTAMP_FORMAT = dateformat"y-m-d HH:MM:SS.s" # .s is optional here -function pqparse(::Type{DateTime}, str::AbstractString) +# Utility function for handling "infinity" strings for datetime types to reduce duplication +function _tryparse_datetime_inf( + typ::Type{T}, str, f=typ +)::Union{T, Nothing} where T <: Dates.AbstractDateTime if str == "infinity" depwarn_timetype_inf() - return typemax(DateTime) + return f(typemax(DateTime)) elseif str == "-infinity" depwarn_timetype_inf() - return typemin(DateTime) + return f(typemin(DateTime)) end - # Cut off digits after the third after the decimal point, - # since DateTime in Julia currently handles only milliseconds, see Issue #33 - str = replace(str, r"(\.[\d]{3})\d+" => s"\g<1>") - return parse(DateTime, str, TIMESTAMP_FORMAT) + return nothing +end + +_DEFAULT_TYPE_MAP[:timestamp] = DateTime +const TIMESTAMP_FORMAT = dateformat"y-m-d HH:MM:SS.s" # .s is optional here +function pqparse(::Type{DateTime}, str::AbstractString) + parsed = _tryparse_datetime_inf(DateTime, str) + isnothing(parsed) || return parsed + + parsed = tryparse(DateTime, str, TIMESTAMP_FORMAT) + isnothing(parsed) || return parsed + + return parse(DateTime, _trunc_seconds(str), TIMESTAMP_FORMAT) end # ISO, YMD @@ -265,34 +275,29 @@ const TIMESTAMPTZ_FORMATS = ( ) function pqparse(::Type{ZonedDateTime}, str::AbstractString) - if str == "infinity" - depwarn_timetype_inf() - return ZonedDateTime(typemax(DateTime), tz"UTC") - elseif str == "-infinity" - depwarn_timetype_inf() - return ZonedDateTime(typemin(DateTime), tz"UTC") - end + parsed = _tryparse_datetime_inf(ZonedDateTime, str, Base.Fix2(ZonedDateTime, tz"UTC")) + isnothing(parsed) || return parsed for fmt in TIMESTAMPTZ_FORMATS[1:(end - 1)] parsed = tryparse(ZonedDateTime, str, fmt) - parsed !== nothing && return parsed + isnothing(parsed) || return parsed end return parse(ZonedDateTime, _trunc_seconds(str), TIMESTAMPTZ_FORMATS[end]) 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 + parsed = _tryparse_datetime_inf(UTCDateTime, str) + isnothing(parsed) || return parsed # Postgres should always give us strings ending with +00 if our timezone is set to UTC # which is the default - return parse(UTCDateTime, _trunc_seconds(replace(str, "+00" => "")), TIMESTAMP_FORMAT) + str = replace(str, "+00" => "") + + parsed = tryparse(UTCDateTime, str, TIMESTAMP_FORMAT) + isnothing(parsed) || return parsed + + return parse(UTCDateTime, _trunc_seconds(str), TIMESTAMP_FORMAT) end _DEFAULT_TYPE_MAP[:date] = Date From 64587122263cbeed9ba191e752953d30e9cfab4b Mon Sep 17 00:00:00 2001 From: Rory Finnegan Date: Thu, 23 Feb 2023 13:51:58 -0800 Subject: [PATCH 4/4] Bump patch release --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 6f18bc84..ee3cd68d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "LibPQ" uuid = "194296ae-ab2e-5f79-8cd4-7183a0a5a0d1" license = "MIT" -version = "1.15.0" +version = "1.15.1" [deps] CEnum = "fa961155-64e5-5f13-b03f-caf6b980ea82"