From 1007eacf88aa31a827b674b9ab002889b2524874 Mon Sep 17 00:00:00 2001 From: Maximilian Muecke Date: Sun, 18 Oct 2026 18:03:29 +0200 Subject: [PATCH] isoweek()/isoyear(): compute in C via convertDate() --- R/IDateTime.R | 13 +++---------- src/idatetime.c | 20 +++++++++++++++++++- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/R/IDateTime.R b/R/IDateTime.R index c061de3ca4..471e197f02 100644 --- a/R/IDateTime.R +++ b/R/IDateTime.R @@ -393,15 +393,8 @@ 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") -# 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") +isoweek = function(x) convertDate(as.IDate(x), "isoweek") +isoyear = function(x) convertDate(as.IDate(x), "isoyear") month = function(x) convertDate(as.IDate(x), "month") quarter = function(x) convertDate(as.IDate(x), "quarter") @@ -432,6 +425,6 @@ yearqtr = function(x, format = c("numeric", "character")) { } convertDate = function(x, type) { - type = match.arg(type, c("yday", "wday", "mday", "week", "month", "quarter", "year", "yearmon", "yearqtr")) + type = match.arg(type, c("yday", "wday", "mday", "week", "isoweek", "isoyear", "month", "quarter", "year", "yearmon", "yearqtr")) .Call(CconvertDate, x, type) } diff --git a/src/idatetime.c b/src/idatetime.c index 026b0458b7..2634d849a6 100644 --- a/src/idatetime.c +++ b/src/idatetime.c @@ -5,7 +5,7 @@ static const int YEARS100 = 36524; static const int YEARS4 = 1461; static const int YEARS1 = 365; -typedef enum { YDAY, WDAY, MDAY, WEEK, MONTH, QUARTER, YEAR, YEARMON, YEARQTR} datetype; +typedef enum { YDAY, WDAY, MDAY, WEEK, ISOWEEK, ISOYEAR, MONTH, QUARTER, YEAR, YEARMON, YEARQTR} datetype; static inline bool isLeapYear(int year) { return (year % 100 != 0 || year % 400 == 0) && year % 4 == 0; @@ -29,6 +29,22 @@ void convertSingleDate(int x, datetype type, void *out) return; } + if (type == ISOWEEK || type == ISOYEAR) { + // ISO 8601-conformant week, as described at https://en.wikipedia.org/wiki/ISO_week_date + // The ISO week and year are those of the nearest Thursday + // Thursdays are multiples of 7, since x=0 (1970-01-01) was a Thursday + int thu = x + (3 - (x % 7 + 10) % 7); + int res; + if (type == ISOWEEK) { + convertSingleDate(thu, YDAY, &res); + res = (res - 1) / 7 + 1; + } else { + convertSingleDate(thu, YEAR, &res); + } + *(int *)out = res; + return; + } + int days = x - 11017; int years400 = days / YEARS400; @@ -134,6 +150,8 @@ SEXP convertDate(SEXP x, SEXP type) else if (!strcmp(ctype_str, "wday")) ctype = WDAY; else if (!strcmp(ctype_str, "mday")) ctype = MDAY; else if (!strcmp(ctype_str, "week")) ctype = WEEK; + else if (!strcmp(ctype_str, "isoweek")) ctype = ISOWEEK; + else if (!strcmp(ctype_str, "isoyear")) ctype = ISOYEAR; else if (!strcmp(ctype_str, "month")) ctype = MONTH; else if (!strcmp(ctype_str, "quarter")) ctype = QUARTER; else if (!strcmp(ctype_str, "year")) ctype = YEAR;