Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser by lidavidm · Pull Request #11358 · apache/arrow · GitHub
Skip to content

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser - #11358

Closed
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820
Closed

ARROW-12820: [C++] Support zone offset in ISO8601, strptime parser#11358
lidavidm wants to merge 2 commits into
apache:masterfrom
lidavidm:arrow-12820

Conversation

@lidavidm

Copy link
Copy Markdown
Member

For ISO8601, this seems to have a small (~5%) impact on benchmarks.

For strptime, this is only supported on platforms exposing tm_gmtoff in struct tm. %Z still is ignored; it seems implementations don't really support it anyways. (For instance GNU libc will skip over the time zone, omitting it from the result.)

@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

⚠️ Ticket has not been started in JIRA, please click 'Start Progress'.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

For any R experts, I'm not quite sure what to do about the R bindings here:

# ParseTimestampStrptime currently ignores the timezone information (ARROW-12820).

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

@lidavidm
lidavidm marked this pull request as ready for review October 7, 2021 21:18
@rok

rok commented Oct 8, 2021

Copy link
Copy Markdown
Member

To me it seems like what R does is convert the timezone after parsing, i.e. we need a timezone conversion kernel, and it's not related to actually parsing the value as implied by the comment.

Probably we can solve this by casting to the desired timezone after parsing. See strftime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the expected type include timezone="UTC"?
That would preserve the fact that the strings actually were timezone-aware, and were shifted to UTC.

@jorisvandenbosschejorisvandenbosscheOct 8, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although I suppose a complication is that we should only do this if all strings in the array have an offset

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was debating this. I suppose if you have %z, then you expect an offset so we should return a timestamp with timezone. Do we also want to do this for the ISO8601 parser? That would have implications in a lot of places, e.g. CSV type inference/parsing, or (I think) pyarrow.array inferring timestamps from strings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose ISO8601 parser should be as close to the standard as possible to avoid surprises?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean? I'm talking about whether the return type of the ISO8601 parser (or really, the places that use the parser) should reflect whether there was a zone offset in the input string(s) or not.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error by default is fine and correct if there are a mix of naive and aware timestamps.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware. The timezone is really more of a consumption-time concern than a production-time concern (i.e. it is probably most likely going to be converted to the local timezone of the consuming user).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks all for the comments.

I'm OOO this week but when I get a chance next, I'll update the CSV parser to track the zone offset and return either "UTC", no timezone, or an error. (If a user wants to preserve a consistent non-UTC offset that can be tackled later.) I think casting is another place that needs to be updated, as well as Python pyarrow.array inference (though that may just also use casting? Not sure off the top of my head).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a matter of varying offsets (but all aware timestamps) then it would probably be also correct to just pick a timezone (e.g. UTC) and use that for everything. In fact, it would probably even be valid to just always use UTC if all timestamps are aware.

Yes, I agree we could always use UTC, even if the offsets are all the same. Having varying offsets is quite normal, if you have data across a DST, so I think we should handle that by default (and return in UTC).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UTC default sounds good!

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the CSV reader and added a doc blurb.

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

@lidavidm

Copy link
Copy Markdown
MemberAuthor

There are some errors here for R/MacOS that I'll fix when I get a chance.

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading this now, I am not sure this is a good idea. At least my initial expectation, if parsing a string like "2021-01-01 09:00" and saying the type of that column should be timestamp("us", tz="Europe/Brussels"), would be that the string is interpreted in the timezone I am explicitly passing.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what currently happens without this PR, but I hear you. I'll give this a fix

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't try it with actual code on current master. I was looking at the last changes giving me the impression you changed this compared to how it was working before ;)

Hmm, so if we change that, that's another change in behaviour.

@jorisvandenbosschejorisvandenbosscheOct 20, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So currently we have this behaviour:

In [26]: s="""col ...: 2021-01-01 09:00:00 ...: """In [27]: csv.read_csv(io.BytesIO(s.encode()))
Out[27]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [28]: s2="""col ...: 2021-01-01 09:00:00+01:00 ...: """In [29]: csv.read_csv(io.BytesIO(s2.encode()))
Out[29]: pyarrow.Tablecol: string----col: [["2021-01-01 09:00:00+01:00"]]

So with a offset the "inference" doesn't actually infer timestamp (does this PR change that?).

And when explicitly mentioning the type for values without a timezone offset:

In [35]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s')}))
Out[35]: pyarrow.Tablecol: timestamp[s]
----col: [[2021-01-0109:00:00]]
In [36]: csv.read_csv(io.BytesIO(s.encode()), convert_options=csv.ConvertOptions(column_types={"col": pa.timestamp('s', tz="Europe/Brussels")}))
Out[36]: pyarrow.Tablecol: timestamp[s, tz=Europe/Brussels]
----col: [[2021-01-0109:00:00]]

So here we indeed kind of "ignore" the timezone of the specified type and kind of assume the naive strings are in UTC.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, that is the same for casting strings to timestamp, though:

In [46]: arr=pa.array(["2021-01-01 09:00:00"])
In [47]: arr.cast(pa.timestamp('s', tz='Europe/Brussels'))
Out[47]: <pyarrow.lib.TimestampArrayobjectat0x7f95c9d72fa0>
[
2021-01-0109:00:00
]

CSV parsing and casting strings should probably behave the same in this aspect. But personally I would argue that both are "wrong" (or at least unexpected to me. I would rather prefer it to error than silently interpreting the strings as UTC)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that read_csv(...).column("start_time").cast(pa.timestamp('s', 'Europe/Brussels')) would give a different answer than read_csv(..., types={'start_time': pa.timestamp('s', 'Europe/Brussels')}).column("start_time").

I think we should ensure those two are equivalent. If we interpret native strings as local time when specifying a timezone-aware type in the csv parsing, I think casting should have the same behaviour.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to Neal offline it looks like the test isn't really meant to check this case, plus he noted we could always start with an error and make it more implicit later - so I'll roll these changes back (and update the table below as noted by Joris).

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I missed the casting comment - I thought casting to different timezone was always assumed to be a metadata-only operation? i.e. it wouldn't change the values

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but I assumed that it was about a string -> timestamp[tz] cast (and so this should IMO be consistent for csv parsing vs explicit casting).

But, if the CSV reader infers timestamp, the example of Weston is actually doing a timestamp -> timestamp[tz] cast.
Now, personally, I also think that such a cast is ambiguous (it's only for a timestamp[tz] -> timestamp[tz] cast that I find it clear that this will be a metadata-only operation)

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, string->timestamp[tz] should be consistent with the CSV reader, I agree.

In this case, the CSV reader would normally infer timestamp, yes. I would argue the conversion should be handled by assume_timezone and that the cast should be metadata-only. (In general, our casts are a mix of conversions and "reinterpretations" of data, which gets a little confusing...)

Comment threaddocs/source/cpp/csv.rst Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we keep the behaviour as is now (related to my comment above), I would certainly add the case of timestamp[s, non-UTC-tz] in this table, to clearly document that behaviour as well.

@jorisvandenbossche

Copy link
Copy Markdown
Member

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"?
Didn't checkout this branch yet, but so currently on master this actually fails:

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

@lidavidm

Copy link
Copy Markdown
MemberAuthor

I ignored casting since the user specifies the timezone (or lack thereof) so presumably it's up to them to do any adjustments they want, and pyarrow.array doesn't infer timestamps from strings (I thought it did, apparently not).

What do you mean exactly with "ingored casting"? Didn't checkout this branch yet, but so currently on master this actually fails:

Sorry, I meant that I didn't really test it.

In [17]: arr = pa.array(["2021-01-01 09:00:00+01:00"])
In [18]: arr.cast(pa.timestamp("s", tz="UTC"))
...
ArrowInvalid: Failed to parse string: '2021-01-01 09:00:00+01:00' as a scalar of type timestamp[s, tz=UTC]

Will this work and take into account the timezone offset?

I'll test this more thoroughly.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

This is what happens on this branch:

>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="UTC"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111760>
[
2021-01-01 09:00:00
]
>>> pa.array(["2021-01-01 09:00:00-0500"]).cast(pa.timestamp("s", tz="America/New_York"))
<pyarrow.lib.TimestampArray object at 0x7f312c111700>
[
2021-01-01 14:00:00
]
>>> pa.array(["2021-01-01 09:00:00+0500"]).cast(pa.timestamp("s"))
<pyarrow.lib.TimestampArray object at 0x7f312c111520>
[
2021-01-01 04:00:00
]

so as with CSV, this needs to be fixed - I'll take a look.

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Ah, we probably then want an option (for both cast/CSV parsing), much like the assume_timezone kernel, that controls what to do with ambiguous or nonexistent local times.

I also realize, this needs to account for what to do with custom timezone parsers in CSV…

@lidavidm

Copy link
Copy Markdown
MemberAuthor

Casts are fixed, now to go update the CSV parser as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cur < options.format.size() - 1 perhaps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the templating is useful. Parsing the timestamp should be more costly than a mostly predictable branch.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the templating.

Filed ARROW-14581 for the Travis test failure.

Comment threadcpp/src/arrow/csv/converter.cc Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, I don't think templating is terribly useful here.

@lidavidm
lidavidmforce-pushed the arrow-12820 branch 2 times, most recently from fdf33a8 to 98b1b52CompareNovember 5, 2021 13:30
@lidavidm

Copy link
Copy Markdown
MemberAuthor

Rebased & fixed conflicts.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first condition should be superfluous now :-)

@jorisvandenbosschejorisvandenbossche left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also test strings here for the case of fully unzoned? (although in code that probably triggers the same check as the mixed case?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also add the case of parsing a string with trailing Z?

That now seems to work with this PR:

In [26]: pc.strptime(["2012-01-01 09:00:00Z"], format="%Y-%m-%d %H:%M:%S%z", unit="s")
Out[26]: <pyarrow.lib.TimestampArray object at 0x7fb709a65760>
[
2012-01-01 09:00:00
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, so this actually already worked before as well, but the resulting type is different (timezone naive vs aware). So might still be worth checking that change in type explicitly, unless that is already covered elsewhere in the tests.

@lidavidmlidavidmNov 8, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple things here:

  • BSD strptime doesn't support "Z" as noted in the comment. It only supports the syntax here, making it hard to test.
  • The result type is based on the presence of a "%z" so that's covered here already.

@pitroupitrou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, thank you :-)

@ursabot

ursabot commented Nov 10, 2021

Copy link
Copy Markdown

Benchmark runs are scheduled for baseline = 2b10648 and contender = a9f2091. a9f2091 is a master commit associated with this PR. Results will be available as each benchmark for each run completes.
Conbench compare runs links:
[Finished ⬇️0.0% ⬆️0.0%] ec2-t3-xlarge-us-east-2
[Finished ⬇️0.51% ⬆️0.0%] ursa-i9-9960x
[Finished ⬇️0.66% ⬆️0.13%] ursa-thinkcentre-m75q
Supported benchmarks:
ursa-i9-9960x: langs = Python, R, JavaScript
ursa-thinkcentre-m75q: langs = C++, Java
ec2-t3-xlarge-us-east-2: cloud = True

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@lidavidm@rok@jorisvandenbossche@ursabot@westonpace@pitrou