Skip to content

fix: correct wkyr() off-by-one in %U and %W week numbers#114

Merged
atoomic merged 1 commit into
cpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-wkyr-off-by-one
Apr 27, 2026
Merged

fix: correct wkyr() off-by-one in %U and %W week numbers#114
atoomic merged 1 commit into
cpan-authors:mainfrom
Koan-Bot:koan.atoomic/fix-wkyr-off-by-one

Conversation

@Koan-Bot
Copy link
Copy Markdown

@Koan-Bot Koan-Bot commented Apr 18, 2026

What

Fixes off-by-one error in %U (Sunday-start) and %W (Monday-start) week number formatting.

Why

The wkyr() helper used +6 in its numerator where POSIX strftime(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:

  • %U wrong in years where Jan 1 is Sunday (2006, 2012, 2017, 2023, 2034...)
  • %W wrong in years where Jan 1 is Monday (2001, 2007, 2018, 2024, 2029...)

Example: time2str("%U", 1672531200, "UTC") (Jan 1, 2023) returned 0 instead of 1.

How

Replaced the original formula int(($yday - $wday + 13) / 7 - 1) (effectively int((yday - wday + 6) / 7)) with the standard POSIX form int(($yday - $wday + 7) / 7). Verified against POSIX strftime for every day of every year from 2020–2030 with zero mismatches.

Testing

  • Added t/wkyr.t with 13 tests covering years where the bug manifested (2023 for %U, 2024 for %W) plus a baseline year (1999).
  • Full test suite passes (1246 tests, 29 files).

🤖 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

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>
@atoomic atoomic marked this pull request as ready for review April 26, 2026 12:31
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR fixes an off-by-one error in wkyr() where the old formula int(($yday - $wday + 13) / 7 - 1) — equivalent to floor((n+6)/7) — produced a result one lower than the correct POSIX value floor((n+7)/7) whenever $yday - $wday ≡ 0 (mod 7), i.e. for every day falling on the week-start weekday in years where January 1 itself lands on that weekday. The replacement formula matches POSIX strftime exactly, is confirmed against all days in 2020–2030, and wkyr() is not duplicated in any of the language-specific packages.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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
Loading

Reviews (1): Last reviewed commit: "fix: correct wkyr() off-by-one in %U and..." | Re-trigger Greptile

Comment thread t/wkyr.t
Comment on lines +1 to +2
use Test::More;
use Date::Format qw(time2str);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Koan-Bot Q: this is a behavior change, did we recently updated how it works? when was the last update for that compute?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to int((yday - wday + 6) / 7)) is from Graham Barr's original TimeDate code. It's present in the init commit of this repo (2020-01-13, e82cc6c), which itself imported code dating back to at least 2003.
  • It was moved from Date::Format to Date::Format::Generic during 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.

@atoomic atoomic merged commit 835ddc2 into cpan-authors:main Apr 27, 2026
22 checks passed
@Koan-Bot Koan-Bot deleted the koan.atoomic/fix-wkyr-off-by-one branch April 27, 2026 09:15
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