Skip to content

isoweek()/isoyear(): use convertDate() instead of format() - #7926

Merged
ben-schwen merged 2 commits into
masterfrom
isoweek-arith
Oct 18, 2026
Merged

ben-schwen merged 2 commits into
masterfrom
isoweek-arith

Conversation

@m-muecke

Copy link
Copy Markdown
Contributor

Follows the implementation that was commented out and mentioned in the wiki article. I tried out a rough version in C that would speed it up by another 2x, not sure if it's worth it, perhaps as a follow-up.

master

library(data.table, warn.conflicts = FALSE)
setDTthreads(1L)

set.seed(349)
x = sample(as.Date("2026-10-17") - 0:5000, 1e6, replace = TRUE)
bench::mark(isoweek(x), isoyear(x), check = FALSE, min_iterations = 10)
#> Warning: Some expressions had a GC in every iteration; so filtering is
#> disabled.
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 isoweek(x)    316ms    335ms      3.00     122MB     6.31
#> 2 isoyear(x)    364ms    368ms      2.72     122MB     5.43

Created on 2026-10-17 with reprex v2.1.1

PR

library(data.table, warn.conflicts = FALSE)
setDTthreads(1L)

set.seed(349)
x = sample(as.Date("2026-10-17") - 0:5000, 1e6, replace = TRUE)
bench::mark(isoweek(x), isoyear(x), check = FALSE, min_iterations = 10)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 isoweek(x)  13.29ms  13.54ms      73.4    7.65MB     20.3
#> 2 isoyear(x)   7.64ms   7.81ms     127.     7.63MB     51.9

Created on 2026-10-17 with reprex v2.1.1

@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (master@c838f30). Learn more about missing BASE report.

Additional details and impacted files
@@            Coverage Diff            @@
##             master    #7926   +/-   ##
=========================================
  Coverage          ?   99.02%           
=========================================
  Files             ?       88           
  Lines             ?    17388           
  Branches          ?        0           
=========================================
  Hits              ?    17218           
  Misses            ?      170           
  Partials          ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ben-schwen

Copy link
Copy Markdown
Member

Change LGTM. How much code would we bring in with the C code?

Could you maybe compare it with fasttime and fastymd to see if its worth for proposing to add it to R?

@github-actions

Copy link
Copy Markdown
  • HEAD=isoweek-arith much faster for isoweek improved in #7144
  • HEAD=isoweek-arith slower P<0.001 for memrecycle regression fixed in #5463

Comparison Plot

Generated via commit a6f8c6a

Download link for the artifact containing the test results: ↓ atime-results.zip

Task Duration
R setup and installing dependencies 1 minutes and 49 seconds
Installing different package versions 13 minutes and 39 seconds
Running and plotting the test cases 5 minutes and 38 seconds

@m-muecke

m-muecke commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

It's only about 20 lines added to src/idatetime.c

pure C

library(data.table, warn.conflicts = FALSE)
setDTthreads(1L)

set.seed(349)
x <- sample(as.Date("2026-10-17") - 0:5000, 1e6, replace = TRUE)
bench::mark(isoweek(x), isoyear(x), check = FALSE, min_iterations = 10)
#> # A tibble: 2 × 6
#>   expression      min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 isoweek(x)   7.02ms   7.09ms      140.    7.65MB     46.7
#> 2 isoyear(x)    5.9ms   5.94ms      168.    7.63MB     66.5

Created on 2026-10-18 with reprex v2.1.1

I didn't know of fasttime and fastymd, but only the latter seems relevant. However, it also doesn't have isoyear() and isoweek(). But it is faster by a margin, since they're using Howard Hinnant's algorithm with a branchless version:

Basic idea one could copy as well:

const unsigned mp = (5 * doy + 2) / 153;          // month index, March = 0
const unsigned d  = doy - (153 * mp + 2) / 5 + 1; // day of month
const unsigned m  = mp + (mp < 10 ? 3 : -9);      // 1..12
library(data.table, warn.conflicts = FALSE)
setDTthreads(1L)

set.seed(349)
x = sample(as.Date("2026-10-17") - 0:5000, 1e6, replace = TRUE)
bench::mark(fastymd::get_year(x), year(x), min_iterations = 10)
#> # A tibble: 2 × 6
#>   expression                min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>           <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 fastymd::get_year(x)   5.09ms   5.21ms      190.    15.3MB    373. 
#> 2 year(x)                3.41ms   3.46ms      289.    7.65MB     73.5
bench::mark(fastymd::get_month(x), month(x), min_iterations = 10)
#> # A tibble: 2 × 6
#>   expression                 min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>            <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 fastymd::get_month(x)   5.13ms   5.22ms     191.    15.26MB    332. 
#> 2 month(x)               17.64ms  17.75ms      56.2    7.63MB     12.8
bench::mark(fastymd::get_mday(x), mday(x), min_iterations = 10)
#> # A tibble: 2 × 6
#>   expression                min   median `itr/sec` mem_alloc `gc/sec`
#>   <bch:expr>           <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl>
#> 1 fastymd::get_mday(x)    5.5ms   5.56ms     178.    15.26MB    207. 
#> 2 mday(x)                17.8ms  18.29ms      54.4    7.63MB     12.4

Created on 2026-10-18 with reprex v2.1.1

@ben-schwen
ben-schwen merged commit 9dcf82f into master Oct 18, 2026
14 checks passed
@ben-schwen
ben-schwen deleted the isoweek-arith branch October 18, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants