diff --git a/cpp/include/cudf/datetime.hpp b/cpp/include/cudf/datetime.hpp index 980c824fdf2..3d90ac063e1 100644 --- a/cpp/include/cudf/datetime.hpp +++ b/cpp/include/cudf/datetime.hpp @@ -206,6 +206,21 @@ std::unique_ptr is_leap_year( cudf::column_view const& column, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); +/** + * @brief Returns the quarter of the date + * + * `output[i]` will be a value from {1, 2, 3, 4} corresponding to the quater of month given by + * `column[i]`. It will be null if the input row at `column[i]` is null. + * + * @throw cudf::logic_error if input column datatype is not a TIMESTAMP + * + * @param The input column containing datetime values + * @return A column of INT16 type indicating which quarter the date is in + */ +std::unique_ptr extract_quarter( + 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 9cc319b5011..3a4459d9f95 100644 --- a/cpp/include/cudf/detail/datetime.hpp +++ b/cpp/include/cudf/detail/datetime.hpp @@ -135,6 +135,11 @@ std::unique_ptr is_leap_year( rmm::cuda_stream_view stream = rmm::cuda_stream_default, rmm::mr::device_memory_resource* mr = rmm::mr::get_current_device_resource()); +std::unique_ptr extract_quarter( + 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 41f3e7dcfee..4d8acb3bd3b 100644 --- a/cpp/src/datetime/datetime_ops.cu +++ b/cpp/src/datetime/datetime_ops.cu @@ -127,6 +127,23 @@ struct extract_day_num_of_year { } }; +// Extract the the quarter to which the timestamp belongs to +struct extract_quarter_op { + template + CUDA_DEVICE_CALLABLE int16_t operator()(Timestamp const ts) const + { + using namespace cuda::std::chrono; + + // Only has the days - time component is chopped off, which is what we want + auto const days_since_epoch = floor(ts); + auto const date = year_month_day(days_since_epoch); + auto const month = unsigned{date.month()}; + + // (x + y - 1) / y = ceil(x/y), where x and y are unsigned. x = month, y = 3 + return (month + 2) / 3; + } +}; + struct is_leap_year_op { template CUDA_DEVICE_CALLABLE bool operator()(Timestamp const ts) const @@ -376,6 +393,13 @@ std::unique_ptr is_leap_year(column_view const& column, return apply_datetime_op(column, stream, mr); } +std::unique_ptr extract_quarter(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) @@ -452,5 +476,12 @@ std::unique_ptr is_leap_year(column_view const& column, rmm::mr::device_ return detail::is_leap_year(column, rmm::cuda_stream_default, mr); } +std::unique_ptr extract_quarter(column_view const& column, + rmm::mr::device_memory_resource* mr) +{ + CUDF_FUNC_RANGE(); + return detail::extract_quarter(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 c200dc6c935..b57945676b5 100644 --- a/cpp/tests/datetime/datetime_ops_test.cpp +++ b/cpp/tests/datetime/datetime_ops_test.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -552,7 +553,7 @@ TEST_F(BasicDatetimeOpsTest, TestIsLeapYear) 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 + -2181005247L, // 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 @@ -569,4 +570,39 @@ TEST_F(BasicDatetimeOpsTest, TestIsLeapYear) {true, false, true, true, true, true, true, true, false, true, true, false}}); } +TEST_F(BasicDatetimeOpsTest, TestQuarter) +{ + using namespace cudf::test; + using namespace cudf::datetime; + using namespace cuda::std::chrono; + using namespace cudf::test::iterators; + + // 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 + 0L, // null + 915148800L, // 1999-01-01 00:00:00 GMT + -11663029161L, // 1600-5-31 05:40:39 GMT + 707904541L, // 1992-06-07 08:09:01 GMT + -2181005247L, // 1900-11-20 09:12:33 GMT + 0L, // UNIX EPOCH 1970-01-01 00:00:00 GMT + -12212553600L, // First full year of Gregorian Calandar 1583-01-01 00:00:00 + 0L, // null + 13591632822L, // 2400-09-13 13:33:42 GMT + 4539564243L, // 2113-11-08 06:04:03 GMT + 0L, // null + 1608581568L, // 2020-12-21 08:12:48 GMT + 1584821568L, // 2020-03-21 08:12:48 GMT + }, + nulls_at({1, 8, 11})}; + + auto quarter = cudf::test::fixed_width_column_wrapper{ + {3, 0 /*null*/, 1, 2, 2, 4, 1, 1, 0 /*null*/, 3, 4, 0 /*null*/, 4, 1}, nulls_at({1, 8, 11})}; + + CUDF_TEST_EXPECT_COLUMNS_EQUAL(*extract_quarter(timestamps_s), quarter); +} + CUDF_TEST_PROGRAM_MAIN()