From 5a186c7fc5a3386fbb59da953f75c29f820d47f8 Mon Sep 17 00:00:00 2001 From: Maximilian Muecke Date: Sat, 17 Oct 2026 00:14:24 +0200 Subject: [PATCH] isoweek()/isoyear(): use convertDate() instead of format() --- NEWS.md | 2 ++ R/IDateTime.R | 23 +++++++++-------------- inst/tests/tests.Rraw | 5 +++++ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index cb4a6231fb..3540ae821a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -50,6 +50,8 @@ 14. `melt()` is faster on large inputs when `measure.vars` is a list with `variable.factor=FALSE` (e.g. 3x) or uses `measure()` (e.g. 2x). Thanks @m-muecke for the PR. +15. `isoweek()` and `isoyear()` are much faster (e.g. 25x and 45x) by computing the result with date arithmetic instead of `format()`. Thanks @m-muecke for the PR. + ### BUG FIXES 1. `fread()` with `skip=0` and `(header=TRUE|FALSE)` no longer skips the first row when it has fewer fields than subsequent rows, [#7463](https://github.com/Rdatatable/data.table/issues/7463). Thanks @emayerhofer for the report and @ben-schwen for the fix. diff --git a/R/IDateTime.R b/R/IDateTime.R index 6e99f2722a..c061de3ca4 100644 --- a/R/IDateTime.R +++ b/R/IDateTime.R @@ -393,20 +393,15 @@ yday = function(x) convertDate(as.IDate(x), "yday") wday = function(x) convertDate(as.IDate(x), "wday") mday = function(x) convertDate(as.IDate(x), "mday") week = function(x) convertDate(as.IDate(x), "week") -# TODO(#3279): Investigate if improved as.IDate() makes our below implementation faster than this -isoweek = function(x) as.integer(format(as.IDate(x), "%V")) - # ISO 8601-conformant week, as described at - # https://en.wikipedia.org/wiki/ISO_week_date - # Approach: - # * Find nearest Thursday to each element of x - # * Find the number of weeks having passed between - # January 1st of the year of the nearest Thursdays and x - -# x = as.IDate(x) # number of days since 1 Jan 1970 (a Thurs) -# nearest_thurs = as.IDate(7L * (as.integer(x + 3L) %/% 7L)) -# year_start = as.IDate(format(nearest_thurs, '%Y-01-01')) -# 1L + (nearest_thurs - year_start) %/% 7L -isoyear = function(x) as.integer(format(as.IDate(x), "%G")) +# ISO 8601-conformant week, as described at +# https://en.wikipedia.org/wiki/ISO_week_date +# Approach: +# * Find nearest Thursday to each element of x +# * Find the number of weeks having passed between +# January 1st of the year of the nearest Thursdays and x +nearest_thurs = function(x) 7L * ((as.integer(as.IDate(x)) + 3L) %/% 7L) +isoweek = function(x) (convertDate(nearest_thurs(x), "yday") - 1L) %/% 7L + 1L +isoyear = function(x) convertDate(nearest_thurs(x), "year") month = function(x) convertDate(as.IDate(x), "month") quarter = function(x) convertDate(as.IDate(x), "quarter") diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 78123b73fe..4277ed1533 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -22011,3 +22011,8 @@ test(2388.21, nafill(c(1, NA, NA, 5), type="nocb", limit=1, nan=NaN), c(1, NA, 5 test(2388.22, nafill(c(1L, NA, NA, 5L), type="nocb", limit=1), c(1L, NA, 5L, 5L)) test(2388.23, nafill(c("a", NA, NA), type="const", fill="x", limit=1), c("a", "x", NA)) test(2388.24, nafill(c("a", NA, NA, "z"), type="nocb", limit=1), c("a", NA, "z", "z")) + +# isoweek() and isoyear() use date arithmetic instead of format() +x = seq(as.IDate("1999-01-01"), as.IDate("2027-12-31"), by="day") +test(2389.1, isoweek(x), as.integer(format(x, "%V"))) +test(2389.2, isoyear(x), as.integer(format(x, "%G")))