Skip to content

Commit 3f080f1

Browse files
authored
Implement abs() and sign() for duration types (#244)
1 parent 73402d7 commit 3f080f1

File tree

7 files changed

+222
-2
lines changed

7 files changed

+222
-2
lines changed

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ S3method(vec_cast,clock_year_month_day.clock_year_month_day)
498498
S3method(vec_cast,clock_year_month_weekday.clock_year_month_weekday)
499499
S3method(vec_cast,clock_year_quarter_day.clock_year_quarter_day)
500500
S3method(vec_cast,clock_zoned_time.clock_zoned_time)
501+
S3method(vec_math,clock_duration)
501502
S3method(vec_math,clock_rcrd)
502503
S3method(vec_math,clock_weekday)
503504
S3method(vec_proxy,clock_duration)

NEWS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
* New `invalid_remove()` for removing invalid dates. This is just a wrapper
1010
around `x[!invalid_detect(x)]`, but works nicely with the pipe (#229).
1111

12-
* All clock types now support `is.nan()`, `is.finite()`, and `is.infinite()`
13-
(#235).
12+
* All clock types now support `is.nan()`, `is.finite()`, and `is.infinite()`.
13+
Additionally, duration types now support `abs()` and `sign()` (#235).
1414

1515
# clock 0.3.1
1616

R/cpp11.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ duration_as_double_cpp <- function(fields, precision_int) {
7272
.Call(`_clock_duration_as_double_cpp`, fields, precision_int)
7373
}
7474

75+
duration_abs_cpp <- function(fields, precision_int) {
76+
.Call(`_clock_duration_abs_cpp`, fields, precision_int)
77+
}
78+
79+
duration_sign_cpp <- function(fields, precision_int) {
80+
.Call(`_clock_duration_sign_cpp`, fields, precision_int)
81+
}
82+
7583
duration_seq_by_lo_cpp <- function(from, precision_int, by, length_out) {
7684
.Call(`_clock_duration_seq_by_lo_cpp`, from, precision_int, by, length_out)
7785
}

R/duration.R

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,31 @@ seq_impl <- function(from, to, by, length.out, along.with, precision, ...) {
756756

757757
# ------------------------------------------------------------------------------
758758

759+
#' @export
760+
vec_math.clock_duration <- function(.fn, .x, ...) {
761+
switch(
762+
.fn,
763+
abs = duration_abs(.x),
764+
sign = duration_sign(.x),
765+
# Pass on to `vec_math.clock_rcrd()`
766+
NextMethod()
767+
)
768+
}
769+
770+
duration_abs <- function(x) {
771+
precision <- duration_precision_attribute(x)
772+
fields <- duration_abs_cpp(x, precision)
773+
new_duration_from_fields(fields, precision, clock_rcrd_names(x))
774+
}
775+
776+
duration_sign <- function(x) {
777+
out <- duration_sign_cpp(x, duration_precision_attribute(x))
778+
names(out) <- names(x)
779+
out
780+
}
781+
782+
# ------------------------------------------------------------------------------
783+
759784
#' @export
760785
#' @method vec_arith clock_duration
761786
vec_arith.clock_duration <- function(op, x, y, ...) {

src/cpp11.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,20 @@ extern "C" SEXP _clock_duration_as_double_cpp(SEXP fields, SEXP precision_int) {
131131
END_CPP11
132132
}
133133
// duration.cpp
134+
cpp11::writable::list duration_abs_cpp(cpp11::list_of<cpp11::integers> fields, const cpp11::integers& precision_int);
135+
extern "C" SEXP _clock_duration_abs_cpp(SEXP fields, SEXP precision_int) {
136+
BEGIN_CPP11
137+
return cpp11::as_sexp(duration_abs_cpp(cpp11::as_cpp<cpp11::decay_t<cpp11::list_of<cpp11::integers>>>(fields), cpp11::as_cpp<cpp11::decay_t<const cpp11::integers&>>(precision_int)));
138+
END_CPP11
139+
}
140+
// duration.cpp
141+
cpp11::writable::integers duration_sign_cpp(cpp11::list_of<cpp11::integers> fields, const cpp11::integers& precision_int);
142+
extern "C" SEXP _clock_duration_sign_cpp(SEXP fields, SEXP precision_int) {
143+
BEGIN_CPP11
144+
return cpp11::as_sexp(duration_sign_cpp(cpp11::as_cpp<cpp11::decay_t<cpp11::list_of<cpp11::integers>>>(fields), cpp11::as_cpp<cpp11::decay_t<const cpp11::integers&>>(precision_int)));
145+
END_CPP11
146+
}
147+
// duration.cpp
134148
cpp11::writable::list duration_seq_by_lo_cpp(cpp11::list_of<cpp11::integers> from, const cpp11::integers& precision_int, cpp11::list_of<cpp11::integers> by, const cpp11::integers& length_out);
135149
extern "C" SEXP _clock_duration_seq_by_lo_cpp(SEXP from, SEXP precision_int, SEXP by, SEXP length_out) {
136150
BEGIN_CPP11
@@ -883,6 +897,7 @@ extern SEXP _clock_collect_year_day_fields(SEXP, SEXP);
883897
extern SEXP _clock_collect_year_month_day_fields(SEXP, SEXP);
884898
extern SEXP _clock_collect_year_month_weekday_fields(SEXP, SEXP);
885899
extern SEXP _clock_collect_year_quarter_day_fields(SEXP, SEXP, SEXP);
900+
extern SEXP _clock_duration_abs_cpp(SEXP, SEXP);
886901
extern SEXP _clock_duration_as_double_cpp(SEXP, SEXP);
887902
extern SEXP _clock_duration_as_integer_cpp(SEXP, SEXP);
888903
extern SEXP _clock_duration_cast_cpp(SEXP, SEXP, SEXP);
@@ -901,6 +916,7 @@ extern SEXP _clock_duration_scalar_multiply_cpp(SEXP, SEXP, SEXP);
901916
extern SEXP _clock_duration_seq_by_lo_cpp(SEXP, SEXP, SEXP, SEXP);
902917
extern SEXP _clock_duration_seq_to_by_cpp(SEXP, SEXP, SEXP, SEXP);
903918
extern SEXP _clock_duration_seq_to_lo_cpp(SEXP, SEXP, SEXP, SEXP);
919+
extern SEXP _clock_duration_sign_cpp(SEXP, SEXP);
904920
extern SEXP _clock_duration_unary_minus_cpp(SEXP, SEXP);
905921
extern SEXP _clock_format_duration_cpp(SEXP, SEXP);
906922
extern SEXP _clock_format_iso_year_week_day_cpp(SEXP, SEXP);
@@ -1007,6 +1023,7 @@ static const R_CallMethodDef CallEntries[] = {
10071023
{"_clock_collect_year_month_day_fields", (DL_FUNC) &_clock_collect_year_month_day_fields, 2},
10081024
{"_clock_collect_year_month_weekday_fields", (DL_FUNC) &_clock_collect_year_month_weekday_fields, 2},
10091025
{"_clock_collect_year_quarter_day_fields", (DL_FUNC) &_clock_collect_year_quarter_day_fields, 3},
1026+
{"_clock_duration_abs_cpp", (DL_FUNC) &_clock_duration_abs_cpp, 2},
10101027
{"_clock_duration_as_double_cpp", (DL_FUNC) &_clock_duration_as_double_cpp, 2},
10111028
{"_clock_duration_as_integer_cpp", (DL_FUNC) &_clock_duration_as_integer_cpp, 2},
10121029
{"_clock_duration_cast_cpp", (DL_FUNC) &_clock_duration_cast_cpp, 3},
@@ -1025,6 +1042,7 @@ static const R_CallMethodDef CallEntries[] = {
10251042
{"_clock_duration_seq_by_lo_cpp", (DL_FUNC) &_clock_duration_seq_by_lo_cpp, 4},
10261043
{"_clock_duration_seq_to_by_cpp", (DL_FUNC) &_clock_duration_seq_to_by_cpp, 4},
10271044
{"_clock_duration_seq_to_lo_cpp", (DL_FUNC) &_clock_duration_seq_to_lo_cpp, 4},
1045+
{"_clock_duration_sign_cpp", (DL_FUNC) &_clock_duration_sign_cpp, 2},
10281046
{"_clock_duration_unary_minus_cpp", (DL_FUNC) &_clock_duration_unary_minus_cpp, 2},
10291047
{"_clock_format_duration_cpp", (DL_FUNC) &_clock_format_duration_cpp, 2},
10301048
{"_clock_format_iso_year_week_day_cpp", (DL_FUNC) &_clock_format_iso_year_week_day_cpp, 2},

src/duration.cpp

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1226,6 +1226,152 @@ duration_as_double_cpp(cpp11::list_of<cpp11::integers> fields,
12261226

12271227
// -----------------------------------------------------------------------------
12281228

1229+
template <class ClockDuration>
1230+
static
1231+
inline
1232+
cpp11::writable::list
1233+
duration_abs_impl(const ClockDuration& x) {
1234+
using Duration = typename ClockDuration::duration;
1235+
using Rep = typename Duration::rep;
1236+
1237+
const r_ssize size = x.size();
1238+
ClockDuration out(size);
1239+
1240+
const Rep zero{0};
1241+
1242+
for (r_ssize i = 0; i < size; ++i) {
1243+
if (x.is_na(i)) {
1244+
out.assign_na(i);
1245+
continue;
1246+
}
1247+
1248+
const Duration elt = x[i];
1249+
const Rep elt_rep = elt.count();
1250+
const Rep out_rep = (elt_rep < zero) ? std::abs(elt_rep) : elt_rep;
1251+
const Duration out_elt{out_rep};
1252+
1253+
out.assign(out_elt, i);
1254+
}
1255+
1256+
return out.to_list();
1257+
}
1258+
1259+
[[cpp11::register]]
1260+
cpp11::writable::list
1261+
duration_abs_cpp(cpp11::list_of<cpp11::integers> fields,
1262+
const cpp11::integers& precision_int) {
1263+
using namespace rclock;
1264+
1265+
cpp11::integers ticks = duration::get_ticks(fields);
1266+
cpp11::integers ticks_of_day = duration::get_ticks_of_day(fields);
1267+
cpp11::integers ticks_of_second = duration::get_ticks_of_second(fields);
1268+
1269+
duration::years dy{ticks};
1270+
duration::quarters dq{ticks};
1271+
duration::months dm{ticks};
1272+
duration::weeks dw{ticks};
1273+
duration::days dd{ticks};
1274+
duration::hours dh{ticks, ticks_of_day};
1275+
duration::minutes dmin{ticks, ticks_of_day};
1276+
duration::seconds ds{ticks, ticks_of_day};
1277+
duration::milliseconds dmilli{ticks, ticks_of_day, ticks_of_second};
1278+
duration::microseconds dmicro{ticks, ticks_of_day, ticks_of_second};
1279+
duration::nanoseconds dnano{ticks, ticks_of_day, ticks_of_second};
1280+
1281+
switch (parse_precision(precision_int)) {
1282+
case precision::year: return duration_abs_impl(dy);
1283+
case precision::quarter: return duration_abs_impl(dq);
1284+
case precision::month: return duration_abs_impl(dm);
1285+
case precision::week: return duration_abs_impl(dw);
1286+
case precision::day: return duration_abs_impl(dd);
1287+
case precision::hour: return duration_abs_impl(dh);
1288+
case precision::minute: return duration_abs_impl(dmin);
1289+
case precision::second: return duration_abs_impl(ds);
1290+
case precision::millisecond: return duration_abs_impl(dmilli);
1291+
case precision::microsecond: return duration_abs_impl(dmicro);
1292+
case precision::nanosecond: return duration_abs_impl(dnano);
1293+
}
1294+
1295+
never_reached("duration_abs_cpp");
1296+
}
1297+
1298+
// -----------------------------------------------------------------------------
1299+
1300+
template <class ClockDuration>
1301+
static
1302+
inline
1303+
cpp11::writable::integers
1304+
duration_sign_impl(const ClockDuration& x) {
1305+
using Duration = typename ClockDuration::duration;
1306+
using Rep = typename Duration::rep;
1307+
1308+
const r_ssize size = x.size();
1309+
cpp11::writable::integers out(size);
1310+
1311+
const Rep zero{0};
1312+
1313+
for (r_ssize i = 0; i < size; ++i) {
1314+
if (x.is_na(i)) {
1315+
out[i] = r_int_na;
1316+
continue;
1317+
}
1318+
1319+
const Duration elt = x[i];
1320+
const Rep elt_rep = elt.count();
1321+
1322+
if (elt_rep == zero) {
1323+
out[i] = 0;
1324+
} else if (elt_rep > zero) {
1325+
out[i] = 1;
1326+
} else {
1327+
out[i] = -1;
1328+
}
1329+
}
1330+
1331+
return out;
1332+
}
1333+
1334+
[[cpp11::register]]
1335+
cpp11::writable::integers
1336+
duration_sign_cpp(cpp11::list_of<cpp11::integers> fields,
1337+
const cpp11::integers& precision_int) {
1338+
using namespace rclock;
1339+
1340+
cpp11::integers ticks = duration::get_ticks(fields);
1341+
cpp11::integers ticks_of_day = duration::get_ticks_of_day(fields);
1342+
cpp11::integers ticks_of_second = duration::get_ticks_of_second(fields);
1343+
1344+
duration::years dy{ticks};
1345+
duration::quarters dq{ticks};
1346+
duration::months dm{ticks};
1347+
duration::weeks dw{ticks};
1348+
duration::days dd{ticks};
1349+
duration::hours dh{ticks, ticks_of_day};
1350+
duration::minutes dmin{ticks, ticks_of_day};
1351+
duration::seconds ds{ticks, ticks_of_day};
1352+
duration::milliseconds dmilli{ticks, ticks_of_day, ticks_of_second};
1353+
duration::microseconds dmicro{ticks, ticks_of_day, ticks_of_second};
1354+
duration::nanoseconds dnano{ticks, ticks_of_day, ticks_of_second};
1355+
1356+
switch (parse_precision(precision_int)) {
1357+
case precision::year: return duration_sign_impl(dy);
1358+
case precision::quarter: return duration_sign_impl(dq);
1359+
case precision::month: return duration_sign_impl(dm);
1360+
case precision::week: return duration_sign_impl(dw);
1361+
case precision::day: return duration_sign_impl(dd);
1362+
case precision::hour: return duration_sign_impl(dh);
1363+
case precision::minute: return duration_sign_impl(dmin);
1364+
case precision::second: return duration_sign_impl(ds);
1365+
case precision::millisecond: return duration_sign_impl(dmilli);
1366+
case precision::microsecond: return duration_sign_impl(dmicro);
1367+
case precision::nanosecond: return duration_sign_impl(dnano);
1368+
}
1369+
1370+
never_reached("duration_sign_impl");
1371+
}
1372+
1373+
// -----------------------------------------------------------------------------
1374+
12291375
template <class ClockDuration>
12301376
static
12311377
inline

tests/testthat/test-duration.R

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,3 +297,25 @@ test_that("is.infinite() works", {
297297
x <- duration_years(c(1, NA))
298298
expect_identical(is.infinite(x), c(FALSE, FALSE))
299299
})
300+
301+
test_that("abs() works", {
302+
x <- duration_hours(c(-2, -1, 0, 1, 2, NA))
303+
expect <- duration_hours(c(2, 1, 0, 1, 2, NA))
304+
expect_identical(abs(x), expect)
305+
})
306+
307+
test_that("abs() propagates names", {
308+
x <- set_names(duration_years(1:2), c("a", "b"))
309+
expect_named(abs(x), c("a", "b"))
310+
})
311+
312+
test_that("sign() works", {
313+
x <- duration_hours(c(-2, -1, 0, 1, 2, NA))
314+
expect <- c(-1L, -1L, 0L, 1L, 1L, NA)
315+
expect_identical(sign(x), expect)
316+
})
317+
318+
test_that("sign() propagates names", {
319+
x <- set_names(duration_years(1:2), c("a", "b"))
320+
expect_named(sign(x), c("a", "b"))
321+
})

0 commit comments

Comments
 (0)