ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

@aucahuasi@lidavidm@rok@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n 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;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

@aucahuasi@lidavidm@rok@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

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

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

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

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

@aucahuasi@lidavidm@rok@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

@aucahuasi@lidavidm@rok@pitrou
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

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

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types - #11075

Closed
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32
Closed

ARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 types#11075
aucahuasi wants to merge 1 commit into
apache:masterfrom
aucahuasi:temporal-functions-date32

Conversation

@aucahuasi

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown

Thanks for opening a pull request!

If this is not a minor PR. Could you open an issue for this pull request on JIRA? https://issues.apache.org/jira/browse/ARROW

Opening JIRAs ahead of time contributes to the Openness of the Apache Arrow project.

Then could you also rename pull request title in the following format?

ARROW-${JIRA_ID}: [${COMPONENT}] ${SUMMARY}

or

MINOR: [${COMPONENT}] ${SUMMARY}

See also:

@aucahuasiaucahuasi changed the title Implement extract temporal components (year, month, day, etc) from date typesImplement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@lidavidm
lidavidm self-requested a review September 3, 2021 12:37
@lidavidmlidavidm changed the title Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 3, 2021
@github-actions

Copy link
Copy Markdown

@lidavidmlidavidm 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.

Thanks for tackling this, this looks like a great start. I think my main questions are 1) whether we should support strftime, hour, minute, etc. on dates as I don't think they're meaningful, and 2) whether we can share the implementation of ISOCalendar instead of duplicating it.

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.

nit: since this is for dates only, maybe have the base struct Strftime have no implementation, and implement both dates and timestamps as specializations?

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates. I would say if you want a string representation of a date, you should cast, since strftime would let you do misleading things like trying to extract the hour from a date.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I think you are right. Do you know if we have tests for casting date32/64 to 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.

We do, they're a bit thin though:

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.

Actually, given that the regular strftime kernel won't format timestamps without timezones, I wonder if it's meaningful to have strftime work on dates.

There's work to have Strftime work on timestamps without timezones (assuming UTC)
#10998. It's a philosophical discussion if it's correct or not but it will be available.

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.

Sorry for the back-and-forth @aucahuasi. However, it might be prudent to split strftime support into a separate JIRA to minimize conflicts with the existing PR and so we can evaluate how best to share the implementation (to avoid too much code duplication).

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

No worries @lidavidm :)
Indeed, it makes sense to have a new jira. I can create the ticket if there is none.

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 there's a JIRA so please do file one.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Here is the jira ticket for strftime, thanks @lidavidm@rok
https://issues.apache.org/jira/browse/ARROW-13916

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 Strftime ticket was merged yesterday so maybe something has changed here. Dunno.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Would it be possible to avoid specialization by instead making the helper functions used work with timestamps and dates? For instance, you could modify GetInputTimezone to be GetInputTimezone<InType> where it would always return an empty string for InType == Date32Type.

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 you'd also need to change functions to templates to be able to specialize things like that...)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks @lidavidm !

I think the provided implementation (using specialization) is good enough.
Please note that this function is returning a const ref string:
const std::string& GetInputTimezone
So the compiler will throw an error if we want to return local empty strings.
And if I change the declaration to return a string (without const ref) we will be copying the timezones instead of using the refs.

Also, for the case of ISOCalendar with TimestampType we had this code:
string timezone = GetInputTimezone
and we had been performing a copy.
So I changed that to
const auto& timezone = GetInputTimezone

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Sure, that's a good idea, I can try to have a single implementation.
Where should I put the empty static string? in which file?

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.

It would go in this file, though FWIW, it would probably be easiest to just factor things into functions since these are all static methods anyways.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

@lidavidm I think this last version reuse the kernel logic and it specializes only the parts related to the timestamp logic. Please let me know if it is good enough.
TODO: I need to fix the some R tests (I'll tackle this tomorrow)

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.

Frankly I think kernels like hour should just not work on dates - it's not meaningful.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Make sense, pandas doesn't has that behavior too

pd.to_datetime("2019-01-03", format='%Y-%m-%d', errors='coerce').date().hour()
AttributeError: 'datetime.date'objecthasnoattribute'hour'

I'll remove the support of date32/64 for hour, min, sec ...

@aucahuasi
aucahuasi marked this pull request as ready for review September 3, 2021 15:53

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.

Instead of a bool, I think it'd be cleaner to split the date kernel registration into a separate function:

template <
template <typename...> classOp,
template <template <typename...> classOpExec, typename Duration, typename InType, typename OutType>
classExecTemplate,
typename OutType>
std::shared_ptr<ScalarFunction> AddDateKernels(ScalarFunction* func, const std::shared_ptr<arrow::DataType>& out_type, KernelInit init = nullptr) {
{
auto exec = ExecTemplate<Op, days, Date32Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date32()}, out_Type, std::move(exec), init);
}
{
auto exec = ExecTemplate<Op, std::chrono::milliseconds, Date64Type, OutType>::Exec;
DCHECK_OK(func->AddKernel({date64()}, out_Type, std::move(exec), init);
}
}

Then you can just call (for instance) AddDateKernels<Year, TemporalComponentExtract, Int64Type>(year.get(), int64()); below.

@aucahuasiaucahuasiSep 7, 2021

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, but I suggest to keep the provided implementation in this PR:

  • It requires less code/It change less parts.
  • It integrates well into the MakeTemporal* functions.
  • It is setting up the date kernels (optionally) at the moment of creating the function.

Also, I think that if I add AddDateKernels we would need 2 versions: one for MakeTemporal and other for MakeSimpleUnaryTemporal.
So I think it makes sense to keep the current code. Let me know what do you think here @lidavidm

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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Thanks, I added /*enable_date=*/ to indicate the intention of the boolean parameter.
Regarding the SimpleUnaryTemporal struct, sounds like a nice idea for a future refactor, however if you feel strongly about carrying out these sort of changes in this PR, let me know please!

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.

We could get around that with a static string somewhere, or by returning const std::string*. Or the actual implementation could be factored out, and the date kernel could call it with a literal string. At the very least, I'm not super enthused about essentially copy-pasting this kernel.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.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.

Hmm, it's reasonable. In that case, below, can we do the following, so it's clear what the bare boolean parameter is for?

 auto quarter = MakeTemporal<Quarter, TemporalComponentExtract, Int64Type>(
/*enable_date=*/true, "quarter", int64(), &quarter_doc);

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 think this factorization is good, however, just a nit: methods should use UpperCamelCase unless they're const getters so this should be called Get.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Done, thanks @lidavidm@rok !

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 will note though, a struct like this can collapse MakeTemporal and MakeSimpleUnaryTemporal into one function:

template <template <typename...> classOp, typename Duration, typename InType>
structSimpleUnaryTemporal {
static Status Exec(KernelContext* ctx, const ExecBatch& batch, Datum* out) {
return SimpleUnary<Op<Duration, InType>>(ctx, batch, out);
}
};

You would then replace OutType with typename... Args in MakeTemporal and you could replace MakeSimpleUnaryTemporal<Foo> with MakeTemporal<Foo, SimpleUnaryTemporal>. Then you wouldn't need two versions of AddDateKernels.

Also note that the function creation is only done once on initialization, so it doesn't really matter whether it's done in the same function or not.

@lidavidm

Copy link
Copy Markdown
Member

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case. Seems this can be changed to expect_dplyr_equal:

# We can support this feature when ARROW-13138 is resolved
test_that("date32 objects are not supported", {
date<- ymd("2017-01-01")
df<-tibble::tibble(date=date)
expect_error(
Table$create(df) %>%
mutate(x= year(date)) %>%
collect(),
"Function year has no kernel matching input types"
)
})

@lidavidmlidavidm 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.

Thanks!

One last thing that I forgot earlier, sorry: we should update compute.rst as well: https://github.com/apache/arrow/blob/f40856a768f2c397082da70af034080994587807/docs/source/cpp/compute.rst#temporal-component-extraction

@lidavidm

Copy link
Copy Markdown
Member

Oh, it also seems we need to rebase here.

Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal.cc Outdated
Comment threadcpp/src/arrow/compute/kernels/scalar_temporal_test.cc Outdated
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 8f366d7 to 19b1cf6CompareSeptember 7, 2021 17:50
@aucahuasiaucahuasi changed the title ARROW-13138: [C++] Implement extract temporal components (year, month, day, etc) from date32/64 typesARROW-13138: [C++][R] Implement extract temporal components (year, month, day, etc) from date32/64 typesSep 7, 2021
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 19b1cf6 to a839acdCompareSeptember 7, 2021 23:45
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

For the failed R test, it looks like it expected year(date) to fail but of course that's no longer the case.
Seems this can be changed to expect_dplyr_equal:

@lidavidm Thank you! I fixed the issue with the R test and added some additional R tests!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from a839acd to f7c9e3aCompareSeptember 7, 2021 23:54
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

One last thing that I forgot earlier, sorry: we should update compute.rst as well:

Done!

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch 2 times, most recently from 130ea03 to 41d6bebCompareSeptember 8, 2021 03:09
@aucahuasi

aucahuasi commented Sep 8, 2021

Copy link
Copy Markdown
ContributorAuthor

It seems there are some build issues on windows. Tomorrow I'll check that!

@lidavidm

Copy link
Copy Markdown
Member

Note the TestNonexistentTimezone test is also failing on Linux.

@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 41d6beb to 49ee568CompareSeptember 8, 2021 22:18
…functions
Reuse most of the iso_calendar algorithm for different types
cleaning & minor changes
fix and add R tests for year, month, etc functions on date types
update cpp docs for temporal compute functions
c++ format
format all
fix windows build
fix TestNonexistentTimezone test and clang errors
@aucahuasi
aucahuasiforce-pushed the temporal-functions-date32 branch from 49ee568 to a47b78aCompareSeptember 8, 2021 23:22
@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Note the TestNonexistentTimezone test is also failing on Linux.

Thanks, fixed!

@rok

rok commented Sep 9, 2021

Copy link
Copy Markdown
Member

Looks good. Appveyor fail doesn't seem related.

It would probably be good to also add Python tests.

@lidavidmlidavidm 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.

LGTM, thank you.

I filed https://issues.apache.org/jira/browse/ARROW-13957 for the flaky AppVeyor test.

I'm not sure if we need explicit Python tests, maybe comparing against Pandas would be nice, but unlike R we don't have higher level bindings we're trying to test.


template <typename Duration, typename InType>
struct ISOCalendarWrapper {
static Status Get(const Scalar& in, std::array<int64_t, 3>& iso_calendar) {

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.

For later: either pass a const reference or a non-const pointer. Non-const references require more care from the caller.

struct ISOCalendarVisitValueFunction {
static Status Get(std::vector<BuilderType*>& field_builders, const ArrayData&,
StructBuilder* struct_builder,
std::function<Status(typename InType::c_type arg)>& visit_value) {

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.

Same here: visit_value should be passed by pointer.

@lidavidm

Copy link
Copy Markdown
Member

Thanks @pitrou, I filed ARROW-13960

@aucahuasi

Copy link
Copy Markdown
ContributorAuthor

Thanks @lidavidm@rok@pitrou !

ViniciusSouzaRoque pushed a commit to s1mbi0se/arrow that referenced this pull request Oct 20, 2021
…nth, day, etc) from date32/64 types
https://issues.apache.org/jira/browse/ARROW-13138Closesapache#11075 from aucahuasi/temporal-functions-date32
Authored-by: Percy Camilo Triveño Aucahuasi <percy.camilo.ta@gmail.com>
Signed-off-by: David Li <li.davidm96@gmail.com>
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.

4 participants

@aucahuasi@lidavidm@rok@pitrou