Fix parsing of fractional seconds.#65
Conversation
|
What do you think about failing over to a secondary parser instead? If we can parse with nanoseconds, great, otherwise failover to without nanoseconds, or vice versa. It'd be nice to stick with the strptime utility instead of custom logic. |
|
There's no secondary parser — |
4fcfa4c to
ac5966e
Compare
|
I've tried it, and it would be fairly hard to detect cases like |
supriyagarg
left a comment
There was a problem hiding this comment.
The original implementation LGTM. Minor comment.
| if (length > 9) { | ||
| // More digits than can be stored as nanoseconds. | ||
| // TODO: Should this round (std::lround)? | ||
| ns /= Exp10(length - 9); |
There was a problem hiding this comment.
round the value, rather than take floor?
There was a problem hiding this comment.
That would require changing the test as well. Which semantics do we want?
3a267c6 to
ac3797d
Compare
igorpeshansky
left a comment
There was a problem hiding this comment.
Turns out it wasn't that hard to catch the cases of more than 2 digits in seconds. So the new implementation is viable as well. Would really like your opinion on which one is better/more readable. PTAL.
| if (length > 9) { | ||
| // More digits than can be stored as nanoseconds. | ||
| // TODO: Should this round (std::lround)? | ||
| ns /= Exp10(length - 9); |
There was a problem hiding this comment.
That would require changing the test as well. Which semantics do we want?
| return std::chrono::system_clock::time_point(); | ||
| } | ||
| tm.tm_sec = sec_i; | ||
| long ns = std::lround((seconds - sec_i) * 10000000000) / 10; |
There was a problem hiding this comment.
The new implementation looks good - much simpler.
Please add a test for the rounding logic
There was a problem hiding this comment.
This is actually truncation logic. There just isn't an std::ltrunc, so I use std::lround and then truncate the last int digit. Added a comment.
bmoyles0117
left a comment
There was a problem hiding this comment.
LGTM, one comment / concern that may not be valid.
| if (zone <= end + 1 || *zone != 'Z' || *(zone+1) != '\0') { | ||
| // TODO | ||
| double seconds = std::strtod(end, &zone); | ||
| if (sec_i < 0 || sec_i != static_cast<long>(seconds)) { |
There was a problem hiding this comment.
Only minor note, I couldn't find a correlating unit test to this conditional statement. Every time there was a strange value in the place of the seconds, it was longer than 2 characters, that would have been caught by the previous condition. Might be overlooking the unit test that covers this case, just wanted to make a note.
There was a problem hiding this comment.
Good catch. A negative value would be caught by the earlier isdigit check, so the first test is useless.
The second test was intended to catch a double in the scientific notation. However, adding e+0 to an integer produces a double that is equal to that integer. I've added a test, but if fails with the new implementation. Unfortunately, there are way too many valid formats that strtod accepts, so it seems like I'm blacklisting them one-by-one, rather than whitelisting the one format I know I'll want.
The new test passes with the original implementation, so let's just go with that one and clean it up later. WDYT?
There was a problem hiding this comment.
Managed to get it to work. Back to the question of which one's more readable.
288d579 to
fa90d5a
Compare
igorpeshansky
left a comment
There was a problem hiding this comment.
Thanks for the reviews. I'm still struggling with the new implementation. I'll take one more stab at it later today, but we can always fall back to the original one.
| if (zone <= end + 1 || *zone != 'Z' || *(zone+1) != '\0') { | ||
| // TODO | ||
| double seconds = std::strtod(end, &zone); | ||
| if (sec_i < 0 || sec_i != static_cast<long>(seconds)) { |
There was a problem hiding this comment.
Good catch. A negative value would be caught by the earlier isdigit check, so the first test is useless.
The second test was intended to catch a double in the scientific notation. However, adding e+0 to an integer produces a double that is equal to that integer. I've added a test, but if fails with the new implementation. Unfortunately, there are way too many valid formats that strtod accepts, so it seems like I'm blacklisting them one-by-one, rather than whitelisting the one format I know I'll want.
The new test passes with the original implementation, so let's just go with that one and clean it up later. WDYT?
| return std::chrono::system_clock::time_point(); | ||
| } | ||
| tm.tm_sec = sec_i; | ||
| long ns = std::lround((seconds - sec_i) * 10000000000) / 10; |
There was a problem hiding this comment.
This is actually truncation logic. There just isn't an std::ltrunc, so I use std::lround and then truncate the last int digit. Added a comment.
igorpeshansky
left a comment
There was a problem hiding this comment.
The new implementation now passes the tests again. Let's figure out which one's more readable and use that as the baseline -- we can tweak it going forward.
| if (zone <= end + 1 || *zone != 'Z' || *(zone+1) != '\0') { | ||
| // TODO | ||
| double seconds = std::strtod(end, &zone); | ||
| if (sec_i < 0 || sec_i != static_cast<long>(seconds)) { |
There was a problem hiding this comment.
Managed to get it to work. Back to the question of which one's more readable.
|
I've updated the new implementation to actually use the full time spec as well -- my one concern with the earlier implementation was that we cut off the seconds and parse them separately, which is now fixed. @bmoyles0117 @supriyagarg PTAL. |
supriyagarg
left a comment
There was a problem hiding this comment.
Thanks - this looks much better! The commit title was a bit confusing, but using the full time spec sounds good.
| (void)std::strtol(end + 1, &zone, 10); | ||
| if (zone <= end + 1) { | ||
| // TODO: Missing nanoseconds. | ||
| return std::chrono::system_clock::time_point(); |
There was a problem hiding this comment.
Is it not safe to assume 0 for nanoseconds?
There was a problem hiding this comment.
It depends on whether we treat a trailing decimal point with no digits after as valid input or an error. As I read the spec, a decimal point must be followed by at least one digit (search for time-fraction).
| // TODO: Internal error. | ||
| return std::chrono::system_clock::time_point(); | ||
| } | ||
| static_assert(sizeof(long) == 8, "long is too small"); |
There was a problem hiding this comment.
When would this happen?
There was a problem hiding this comment.
For example with a 32-bit compiler. I'd rather fail compilation if someone ever tries to build for Amazon's 32-bit AMI or Windows than silently overflow.
| (void)std::strtol(end + 1, &zone, 10); | ||
| if (zone <= end + 1) { | ||
| // TODO: Missing nanoseconds. | ||
| return std::chrono::system_clock::time_point(); |
There was a problem hiding this comment.
It depends on whether we treat a trailing decimal point with no digits after as valid input or an error. As I read the spec, a decimal point must be followed by at least one digit (search for time-fraction).
| // TODO: Internal error. | ||
| return std::chrono::system_clock::time_point(); | ||
| } | ||
| static_assert(sizeof(long) == 8, "long is too small"); |
There was a problem hiding this comment.
For example with a 32-bit compiler. I'd rather fail compilation if someone ever tries to build for Amazon's 32-bit AMI or Windows than silently overflow.
bd8316f to
e70bb6c
Compare
|
Rebased off |
This addresses cases when fractional seconds aren't present, or when fractional seconds don't have exactly 9 digits.