fix: correct wkyr() off-by-one in %U and %W week numbers#114
Conversation
The week number calculation was wrong by one in years where January 1 falls on the week-start day (Sunday for %U, Monday for %W). The formula used +6 in the numerator where POSIX strftime(3) requires +7, causing all week numbers to be one less than expected for the entire year. For example, Jan 1 2023 (Sunday) gave %U=0 instead of the correct 1, and Jan 1 2024 (Monday) gave %W=0 instead of 1. Simplified the formula to the standard POSIX form: int(($yday - $wday + 7) / 7) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Greptile SummaryThis PR fixes an off-by-one error in Confidence Score: 5/5Safe to merge; the fix is a well-scoped one-line correction with targeted regression tests. The formula change is mathematically correct, affects only the known-broken edge case (Jan 1 on week-start day), and is verified by both the new tests and POSIX reference. The only finding is a P2 style omission in the test file. No files require special attention.
|
| Filename | Overview |
|---|---|
| lib/Date/Format/Generic.pm | Single-line fix replacing int(($yday - $wday + 13) / 7 - 1) with int(($yday - $wday + 7) / 7), correctly aligning with the POSIX strftime formula for %U/%W week numbers. |
| t/wkyr.t | New regression test covering 2023 (%U off-by-one) and 2024 (%W off-by-one) trigger years plus a 1999 baseline; missing use strict / use warnings. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["format_U / format_W called\n(wday, yday from localtime array)"] --> B["wkyr(wstart, wday, yday)"]
B --> C["Adjust wday\n(wday + 7 - wstart) % 7"]
C --> D{"n = yday - adj_wday\ndivisible by 7?"}
D -- "No (most days)" --> E["Old == New\nint((n+6)/7) == int((n+7)/7)"]
D -- "Yes (week-start days when\nJan 1 = week-start day)" --> F["Old: int((n+6)/7) = n/7\nNew: int((n+7)/7) = n/7 + 1 ✓"]
E --> G["Return week number"]
F --> G
Reviews (1): Last reviewed commit: "fix: correct wkyr() off-by-one in %U and..." | Re-trigger Greptile
| use Test::More; | ||
| use Date::Format qw(time2str); |
There was a problem hiding this comment.
Missing
use strict / use warnings
The test file omits use strict and use warnings, which are standard practice in modern Perl test files. Without them, typos in variable names (e.g. $expecetd instead of $expected) silently succeed rather than raising a compile-time error.
| use Test::More; | |
| use Date::Format qw(time2str); | |
| use strict; | |
| use warnings; | |
| use Test::More; | |
| use Date::Format qw(time2str); |
| my($wstart, $wday, $yday) = @_; | ||
| $wday = ($wday + 7 - $wstart) % 7; | ||
| return int(($yday - $wday + 13) / 7 - 1); | ||
| return int(($yday - $wday + 7) / 7); |
There was a problem hiding this comment.
@Koan-Bot Q: this is a behavior change, did we recently updated how it works? when was the last update for that compute?
There was a problem hiding this comment.
Yes, this is a behavior change — it fixes a bug that's been there since the original codebase.
History of wkyr():
- The formula
int(($yday - $wday + 13) / 7 - 1)(equivalent toint((yday - wday + 6) / 7)) is from Graham Barr's original TimeDate code. It's present in theinitcommit of this repo (2020-01-13,e82cc6c), which itself imported code dating back to at least 2003. - It was moved from
Date::FormattoDate::Format::Genericduring the Dist::Zilla migration (fdbe035, 2026-02-26) — no logic change. - PR fix: correct wkyr() off-by-one in %U and %W week numbers #114 is the first-ever modification to the
wkyr()computation. No one has touched this formula before.
What changed and why:
The old formula gives the wrong answer (off by one) whenever (yday - wday) is divisible by 7 — which happens for every day that falls on the week-start weekday in years where Jan 1 is also on that weekday. Roughly ~14% of years for each of %U and %W. The fix aligns with the standard POSIX strftime(3) formula.
So yes: behavior change, but it's correcting a long-standing bug against the POSIX spec, not changing intentional behavior.
What
Fixes off-by-one error in
%U(Sunday-start) and%W(Monday-start) week number formatting.Why
The
wkyr()helper used+6in its numerator where POSIXstrftime(3)requires+7. This caused all week numbers to be one less than expected for the entire year whenever January 1 falls on the week-start day:%Uwrong in years where Jan 1 is Sunday (2006, 2012, 2017, 2023, 2034...)%Wwrong in years where Jan 1 is Monday (2001, 2007, 2018, 2024, 2029...)Example:
time2str("%U", 1672531200, "UTC")(Jan 1, 2023) returned0instead of1.How
Replaced the original formula
int(($yday - $wday + 13) / 7 - 1)(effectivelyint((yday - wday + 6) / 7)) with the standard POSIX formint(($yday - $wday + 7) / 7). Verified against POSIXstrftimefor every day of every year from 2020–2030 with zero mismatches.Testing
t/wkyr.twith 13 tests covering years where the bug manifested (2023 for %U, 2024 for %W) plus a baseline year (1999).🤖 Generated with Claude Code
Quality Report
Changes: 2 files changed, 39 insertions(+), 1 deletion(-)
Code scan: clean
Tests: skipped
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline