Fix date parsing for a bad inputs

This commit is contained in:
data-man 2018-02-19 16:57:17 +03:00
commit b30b920751
2 changed files with 189 additions and 60 deletions

View file

@ -936,13 +936,22 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
var sv: int var sv: int
case token case token
of "d": of "d":
if value.len >= j+1:
var pd = parseInt(value[j..j+1], sv) var pd = parseInt(value[j..j+1], sv)
dt.monthday = sv dt.monthday = sv
j += pd j += pd
else:
raise newException(ValueError,
"Couldn't parse day of month (d), got: " & value)
of "dd": of "dd":
if value.len >= j+1:
dt.monthday = value[j..j+1].parseInt() dt.monthday = value[j..j+1].parseInt()
else:
raise newException(ValueError,
"Couldn't parse day of month (dd), got: " & value)
j += 2 j += 2
of "ddd": of "ddd":
if value.len >= j+2:
case value[j..j+2].toLowerAscii() case value[j..j+2].toLowerAscii()
of "sun": dt.weekday = dSun of "sun": dt.weekday = dSun
of "mon": dt.weekday = dMon of "mon": dt.weekday = dMon
@ -954,6 +963,9 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse day of week (ddd), got: " & value[j..j+2]) "Couldn't parse day of week (ddd), got: " & value[j..j+2])
else:
raise newException(ValueError,
"Couldn't parse day of week (ddd), got: " & value)
j += 3 j += 3
of "dddd": of "dddd":
if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0: if value.len >= j+6 and value[j..j+5].cmpIgnoreCase("sunday") == 0:
@ -981,28 +993,53 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse day of week (dddd), got: " & value) "Couldn't parse day of week (dddd), got: " & value)
of "h", "H": of "h", "H":
if value.len >= j+1:
var pd = parseInt(value[j..j+1], sv) var pd = parseInt(value[j..j+1], sv)
dt.hour = sv dt.hour = sv
j += pd j += pd
else:
raise newException(ValueError,
"Couldn't parse hour (h), got: " & value)
of "hh", "HH": of "hh", "HH":
if value.len >= j+1:
dt.hour = value[j..j+1].parseInt() dt.hour = value[j..j+1].parseInt()
else:
raise newException(ValueError,
"Couldn't parse hour (hh), got: " & value)
j += 2 j += 2
of "m": of "m":
if value.len >= j+1:
var pd = parseInt(value[j..j+1], sv) var pd = parseInt(value[j..j+1], sv)
dt.minute = sv dt.minute = sv
j += pd j += pd
else:
raise newException(ValueError,
"Couldn't parse minute (m), got: " & value)
of "mm": of "mm":
if value.len >= j+1:
dt.minute = value[j..j+1].parseInt() dt.minute = value[j..j+1].parseInt()
else:
raise newException(ValueError,
"Couldn't parse minute (mm), got: " & value)
j += 2 j += 2
of "M": of "M":
if value.len >= j+1:
var pd = parseInt(value[j..j+1], sv) var pd = parseInt(value[j..j+1], sv)
dt.month = sv.Month dt.month = sv.Month
j += pd j += pd
else:
raise newException(ValueError,
"Couldn't parse month (M), got: " & value)
of "MM": of "MM":
if value.len >= j+1:
var month = value[j..j+1].parseInt() var month = value[j..j+1].parseInt()
j += 2 j += 2
dt.month = month.Month dt.month = month.Month
else:
raise newException(ValueError,
"Couldn't parse month (MM), got: " & value)
of "MMM": of "MMM":
if value.len >= j+2:
case value[j..j+2].toLowerAscii(): case value[j..j+2].toLowerAscii():
of "jan": dt.month = mJan of "jan": dt.month = mJan
of "feb": dt.month = mFeb of "feb": dt.month = mFeb
@ -1019,6 +1056,9 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse month (MMM), got: " & value) "Couldn't parse month (MMM), got: " & value)
else:
raise newException(ValueError,
"Couldn't parse month (MMM), got: " & value)
j += 3 j += 3
of "MMMM": of "MMMM":
if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0: if value.len >= j+7 and value[j..j+6].cmpIgnoreCase("january") == 0:
@ -1061,35 +1101,63 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse month (MMMM), got: " & value) "Couldn't parse month (MMMM), got: " & value)
of "s": of "s":
if value.len >= j+1:
var pd = parseInt(value[j..j+1], sv) var pd = parseInt(value[j..j+1], sv)
dt.second = sv dt.second = sv
j += pd j += pd
else:
raise newException(ValueError,
"Couldn't parse second (s), got: " & value)
of "ss": of "ss":
if value.len >= j+1:
dt.second = value[j..j+1].parseInt() dt.second = value[j..j+1].parseInt()
else:
raise newException(ValueError,
"Couldn't parse second (ss), got: " & value)
j += 2 j += 2
of "t": of "t":
if value[j] == 'P' and dt.hour > 0 and dt.hour < 12: if value[j] == 'P' and dt.hour > 0 and dt.hour < 12:
dt.hour += 12 dt.hour += 12
j += 1 j += 1
of "tt": of "tt":
if value.len >= j+1:
if value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12: if value[j..j+1] == "PM" and dt.hour > 0 and dt.hour < 12:
dt.hour += 12 dt.hour += 12
else:
raise newException(ValueError,
"Couldn't parse hour (tt), got: " & value)
j += 2 j += 2
of "yy": of "yy":
# Assumes current century # Assumes current century
if value.len >= j+1:
var year = value[j..j+1].parseInt() var year = value[j..j+1].parseInt()
var thisCen = now().year div 100 var thisCen = now().year div 100
dt.year = thisCen*100 + year dt.year = thisCen*100 + year
else:
raise newException(ValueError,
"Couldn't parse year (yy), got: " & value)
j += 2 j += 2
of "yyyy": of "yyyy":
if value.len >= j+3:
dt.year = value[j..j+3].parseInt() dt.year = value[j..j+3].parseInt()
else:
raise newException(ValueError,
"Couldn't parse year (yyyy), got: " & value)
j += 4 j += 4
of "z": of "z":
dt.isDst = false dt.isDst = false
if value[j] == '+': if value[j] == '+':
if value.len >= j+1:
dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour dt.utcOffset = 0 - parseInt($value[j+1]) * secondsInHour
else:
raise newException(ValueError,
"Couldn't parse timezone offset (z), got: " & value)
elif value[j] == '-': elif value[j] == '-':
if value.len >= j+1:
dt.utcOffset = parseInt($value[j+1]) * secondsInHour dt.utcOffset = parseInt($value[j+1]) * secondsInHour
else:
raise newException(ValueError,
"Couldn't parse timezone offset (z), got: " & value)
elif value[j] == 'Z': elif value[j] == 'Z':
dt.utcOffset = 0 dt.utcOffset = 0
j += 1 j += 1
@ -1101,9 +1169,17 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
of "zz": of "zz":
dt.isDst = false dt.isDst = false
if value[j] == '+': if value[j] == '+':
if value.len >= j+2:
dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = 0 - value[j+1..j+2].parseInt() * secondsInHour
else:
raise newException(ValueError,
"Couldn't parse timezone offset (zz), got: " & value)
elif value[j] == '-': elif value[j] == '-':
if value.len >= j+2:
dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = value[j+1..j+2].parseInt() * secondsInHour
else:
raise newException(ValueError,
"Couldn't parse timezone offset (zz), got: " & value)
elif value[j] == 'Z': elif value[j] == 'Z':
dt.utcOffset = 0 dt.utcOffset = 0
j += 1 j += 1
@ -1124,10 +1200,14 @@ proc parseToken(dt: var DateTime; token, value: string; j: var int) =
else: else:
raise newException(ValueError, raise newException(ValueError,
"Couldn't parse timezone offset (zzz), got: " & value[j]) "Couldn't parse timezone offset (zzz), got: " & value[j])
if value.len >= j+5:
dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour dt.utcOffset = factor * value[j+1..j+2].parseInt() * secondsInHour
j += 4 j += 4
dt.utcOffset += factor * value[j..j+1].parseInt() * 60 dt.utcOffset += factor * value[j..j+1].parseInt() * 60
j += 2 j += 2
else:
raise newException(ValueError,
"Couldn't parse timezone offset (zzz), got: " & value)
else: else:
# Ignore the token and move forward in the value string by the same length # Ignore the token and move forward in the value string by the same length
j += token.len j += token.len

View file

@ -131,6 +131,10 @@ template parseTest(s, f, sExpected: string, ydExpected: int) =
echo parsed.yearday, " exp: ", ydExpected echo parsed.yearday, " exp: ", ydExpected
check(parsed.yearday == ydExpected) check(parsed.yearday == ydExpected)
template parseTestExcp(s, f: string) =
expect ValueError:
let parsed = s.parse(f)
template parseTestTimeOnly(s, f, sExpected: string) = template parseTestTimeOnly(s, f, sExpected: string) =
check sExpected in $s.parse(f, utc()) check sExpected in $s.parse(f, utc())
@ -281,6 +285,51 @@ suite "ttimes":
test "parseTest": test "parseTest":
runTimezoneTests() runTimezoneTests()
test "incorrect inputs: empty string":
parseTestExcp("", "yyyy-MM-dd")
test "incorrect inputs: year":
parseTestExcp("20-02-19", "yyyy-MM-dd")
test "incorrect inputs: month number":
parseTestExcp("2018-2-19", "yyyy-MM-dd")
test "incorrect inputs: month name":
parseTestExcp("2018-Fe", "yyyy-MMM-dd")
test "incorrect inputs: day":
parseTestExcp("2018-02-1", "yyyy-MM-dd")
test "incorrect inputs: day of week":
parseTestExcp("2018-Feb-Mo", "yyyy-MMM-ddd")
test "incorrect inputs: hour":
parseTestExcp("2018-02-19 1:30", "yyyy-MM-dd hh:mm")
test "incorrect inputs: minute":
parseTestExcp("2018-02-19 16:3", "yyyy-MM-dd hh:mm")
test "incorrect inputs: second":
parseTestExcp("2018-02-19 16:30:0", "yyyy-MM-dd hh:mm:ss")
test "incorrect inputs: timezone (z)":
parseTestExcp("2018-02-19 16:30:00 ", "yyyy-MM-dd hh:mm:ss z")
test "incorrect inputs: timezone (zz) 1":
parseTestExcp("2018-02-19 16:30:00 ", "yyyy-MM-dd hh:mm:ss zz")
test "incorrect inputs: timezone (zz) 2":
parseTestExcp("2018-02-19 16:30:00 +1", "yyyy-MM-dd hh:mm:ss zz")
test "incorrect inputs: timezone (zzz) 1":
parseTestExcp("2018-02-19 16:30:00 ", "yyyy-MM-dd hh:mm:ss zzz")
test "incorrect inputs: timezone (zzz) 2":
parseTestExcp("2018-02-19 16:30:00 +01:", "yyyy-MM-dd hh:mm:ss zzz")
test "incorrect inputs: timezone (zzz) 3":
parseTestExcp("2018-02-19 16:30:00 +01:0", "yyyy-MM-dd hh:mm:ss zzz")
test "dynamic timezone": test "dynamic timezone":
proc staticOffset(offset: int): Timezone = proc staticOffset(offset: int): Timezone =
proc zoneInfoFromTz(adjTime: Time): ZonedTime = proc zoneInfoFromTz(adjTime: Time): ZonedTime =