Fix support for PostgreSQL timestamp fields.

PostgreSQL uses a format similar to IS8601 but allows values expressed
with tenths or hundreths of seconds rather than just milliseconds.
`2020-01-01 12:34:98.3+00` as opposed to `2020-01-01 12:34:98.300+00`,
for example. The `times` module in the Nim stdlib supports only
milliseconds with exactly three digits. To bridge this gap we detect the
two unsupported cases and pad the fractional seconds out to millisecond
precision.
This commit is contained in:
Jonathan Bernard 2020-01-02 18:46:54 -06:00
parent 61e06842af
commit 1f57e0dccc

View File

@ -1,14 +1,16 @@
import json, macros, options, sequtils, strutils, times, timeutils, unicode,
uuids
import nre except toSeq
const UNDERSCORE_RUNE = "_".toRunes[0]
const PG_TIMESTAMP_FORMATS = [
"yyyy-MM-dd HH:mm:sszz",
"yyyy-MM-dd HH:mm:ss'.'fzz",
"yyyy-MM-dd HH:mm:ss'.'ffzz",
"yyyy-MM-dd HH:mm:ss'.'fffzz"
]
var PG_PARTIAL_FORMAT_REGEX = re"(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.)(\d{1,3})(\S+)?"
type
MutateClauses* = object
columns*: seq[string]
@ -69,9 +71,27 @@ type DbArrayParseState = enum
proc parsePGDatetime*(val: string): DateTime =
var errStr = ""
# Try to parse directly using known format strings.
for df in PG_TIMESTAMP_FORMATS:
try: return val.parse(df)
except: errStr &= "\n" & getCurrentExceptionMsg()
except: errStr &= "\n\t" & getCurrentExceptionMsg()
# PostgreSQL will truncate any trailing 0's in the millisecond value leading
# to values like `2020-01-01 16:42.3+00`. This cannot currently be parsed by
# the standard times format as it expects exactly three digits for
# millisecond values. So we have to detect this and pad out the millisecond
# value to 3 digits.
let match = val.match(PG_PARTIAL_FORMAT_REGEX)
if match.isSome:
let c = match.get.captures
try:
let corrected = c[0] & alignLeft(c[1], 3, '0') & c[2]
return corrected.parse(PG_TIMESTAMP_FORMATS[1])
except:
errStr &= "\n\t" & PG_TIMESTAMP_FORMATS[1] &
" after padding out milliseconds to full 3-digits"
raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr)
proc parseDbArray*(val: string): seq[string] =