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, import json, macros, options, sequtils, strutils, times, timeutils, unicode,
uuids uuids
import nre except toSeq
const UNDERSCORE_RUNE = "_".toRunes[0] const UNDERSCORE_RUNE = "_".toRunes[0]
const PG_TIMESTAMP_FORMATS = [ const PG_TIMESTAMP_FORMATS = [
"yyyy-MM-dd HH:mm:sszz", "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" "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 type
MutateClauses* = object MutateClauses* = object
columns*: seq[string] columns*: seq[string]
@ -69,9 +71,27 @@ type DbArrayParseState = enum
proc parsePGDatetime*(val: string): DateTime = proc parsePGDatetime*(val: string): DateTime =
var errStr = "" var errStr = ""
# Try to parse directly using known format strings.
for df in PG_TIMESTAMP_FORMATS: for df in PG_TIMESTAMP_FORMATS:
try: return val.parse(df) 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) raise newException(ValueError, "Cannot parse PG date. Tried:" & errStr)
proc parseDbArray*(val: string): seq[string] = proc parseDbArray*(val: string): seq[string] =