diff --git a/cpp/include/cudf/datetime.hpp b/cpp/include/cudf/datetime.hpp index a276769c169..980c824fdf2 100644 --- a/cpp/include/cudf/datetime.hpp +++ b/cpp/include/cudf/datetime.hpp @@ -189,6 +189,23 @@ std::unique_ptr add_calendrical_months( cudf::column_view const& timestamps, cudf::column_view const& months, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + +/** + * @brief Check if the year of the given date is a leap year + * + * `output[i] == true` if year of `column[i]` is a leap year + * `output[i] == false` if year of `column[i]` is not a leap year + * `output[i] is null` if `column[i]` is null + * + * @param[in] cudf::column_view of the input datetime values + * + * @returns cudf::column of datatype BOOL8 truth value of the corresponding date + * @throw cudf::logic_error if input column datatype is not a TIMESTAMP + */ +std::unique_ptr is_leap_year( + cudf::column_view const& column, + rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + /** @} */ // end of group } // namespace datetime } // namespace cudf diff --git a/cpp/include/cudf/detail/datetime.hpp b/cpp/include/cudf/detail/datetime.hpp index 017fe0d96ff..9cc319b5011 100644 --- a/cpp/include/cudf/detail/datetime.hpp +++ b/cpp/include/cudf/detail/datetime.hpp @@ -124,6 +124,17 @@ std::unique_ptr add_calendrical_months( cudf::column_view const& months, rmm::cuda_stream_view stream = rmm::cuda_stream_default, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + +/** + * @copydoc cudf::is_leap_year(cudf::column_view const&, rmm::mr::device_memory_resource *) + * + * @param stream CUDA stream used for device memory operations and kernel launches. + */ +std::unique_ptr is_leap_year( + cudf::column_view const& column, + rmm::cuda_stream_view stream = rmm::cuda_stream_default, + rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); + } // namespace detail } // namespace datetime } // namespace cudf diff --git a/cpp/src/datetime/datetime_ops.cu b/cpp/src/datetime/datetime_ops.cu index 36c3605951e..41f3e7dcfee 100644 --- a/cpp/src/datetime/datetime_ops.cu +++ b/cpp/src/datetime/datetime_ops.cu @@ -19,9 +19,9 @@ #include #include #include +#include #include #include -#include #include #include #include @@ -127,6 +127,17 @@ struct extract_day_num_of_year { } }; +struct is_leap_year_op { + template + CUDA_DEVICE_CALLABLE bool operator()(Timestamp const ts) const + { + using namespace cuda::std::chrono; + auto const days_since_epoch = floor(ts); + auto const date = year_month_day(days_since_epoch); + return date.year().is_leap(); + } +}; + // Apply the functor for every element/row in the input column to create the output column template struct launch_functor { @@ -357,6 +368,14 @@ std::unique_ptr day_of_year(column_view const& column, return detail::apply_datetime_op( column, stream, mr); } + +std::unique_ptr is_leap_year(column_view const& column, + rmm::cuda_stream_view stream, + rmm::mr::device_memory_resource* mr) +{ + return apply_datetime_op(column, stream, mr); +} + } // namespace detail std::unique_ptr extract_year(column_view const& column, rmm::mr::device_memory_resource* mr) @@ -426,5 +445,12 @@ std::unique_ptr add_calendrical_months(cudf::column_view const& ti return detail::add_calendrical_months( timestamp_column, months_column, rmm::cuda_stream_default, mr); } + +std::unique_ptr is_leap_year(column_view const& column, rmm::mr::device_memory_resource* mr) +{ + CUDF_FUNC_RANGE(); + return detail::is_leap_year(column, rmm::cuda_stream_default, mr); +} + } // namespace datetime } // namespace cudf diff --git a/cpp/tests/datetime/datetime_ops_test.cpp b/cpp/tests/datetime/datetime_ops_test.cpp index 8aa83ce6b22..cdfc9de395c 100644 --- a/cpp/tests/datetime/datetime_ops_test.cpp +++ b/cpp/tests/datetime/datetime_ops_test.cpp @@ -26,6 +26,8 @@ #include #include +#define XXX false // stub for null values + template struct NonTimestampTest : public cudf::test::BaseFixture { cudf::data_type type() { return cudf::data_type{cudf::type_to_id()}; } @@ -532,4 +534,37 @@ TEST_F(BasicDatetimeOpsTest, TestAddMonthsWithSecondsAndNullValues) true); } +TEST_F(BasicDatetimeOpsTest, TestIsLeapYear) +{ + using namespace cudf::test; + using namespace cudf::datetime; + using namespace cuda::std::chrono; + + // Time in seconds since epoch + // Dates converted using epochconverter.com + auto timestamps_s = + cudf::test::fixed_width_column_wrapper{ + { + 1594332839L, // 2020-07-09 10:13:59 GMT - leap year + 0L, // null + 915148800L, // 1999-01-01 00:00:00 GMT - non leap year + -11663029161L, // 1600-5-31 05:40:39 GMT - leap year + 707904541L, // 1992-06-07 08:09:01 GMT - leap year + 2181048447L, // 1900-11-20 09:12:33 GMT - non leap year + 0L, // UNIX EPOCH 1970-01-01 00:00:00 GMT - non leap year + -12212553600L, // First full year of Gregorian Calandar 1583-01-01 00:00:00 - non-leap-year + 0L, // null + 13591632822L, // 2400-09-13 13:33:42 GMT - leap year + 4539564243L, // 2113-11-08 06:04:03 GMT - non leap year + 0L // null + }, + {true, false, true, true, true, true, true, true, false, true, true, false}}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL( + *is_leap_year(timestamps_s), + cudf::test::fixed_width_column_wrapper{ + {true, XXX, false, true, true, false, false, false, XXX, true, false, XXX}, + {true, false, true, true, true, true, true, true, false, true, true, false}}); +} + CUDF_TEST_PROGRAM_MAIN()