From d737745c9f369880977d9a65afa7dd741c5e5498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Fri, 1 May 2026 05:00:57 -0600 Subject: [PATCH] feat: add %g format code for ISO 8601 week-based year MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add format_g() which returns the 2-digit ISO 8601 week-based year, complementing the existing %V (ISO week number). The ISO week year differs from the calendar year at year boundaries — e.g., Dec 31 2025 (week 01) has ISO week year 26, while Jan 1 2016 (week 53) has ISO week year 15. Refactors format_V to share a common _iso_week_year() helper that computes both the ISO week year and week number via the Thursday-based algorithm. Co-Authored-By: Claude Opus 4.6 --- lib/Date/Format.pm | 1 + lib/Date/Format/Generic.pm | 29 +++++++++++++++++++++-------- t/format.t | 15 ++++++++++++++- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/Date/Format.pm b/lib/Date/Format.pm index 00dca5c..2788c9f 100644 --- a/lib/Date/Format.pm +++ b/lib/Date/Format.pm @@ -134,6 +134,7 @@ category of the program's locale. %e like %d, but a leading zero is replaced by a space (eg 1..31) %D MM/DD/YY %F ISO 8601 date format: YYYY-MM-DD + %g ISO 8601 week-based year (2 digits) %G GPS week number (weeks since January 6, 1980) %h month abbr %H hour, 24 hour clock, leading 0's) diff --git a/lib/Date/Format/Generic.pm b/lib/Date/Format/Generic.pm index b3b8bdf..3ffe52c 100644 --- a/lib/Date/Format/Generic.pm +++ b/lib/Date/Format/Generic.pm @@ -244,21 +244,34 @@ sub format_OY { roman(format_Y(@_)) } sub format_G { int(($_[0]->[9] - 315964800) / 604800) } -sub format_V { +sub _iso_week_year { my $yday = $_[0]->[7]; my $year = $_[0]->[5] + 1900; - # Convert Perl wday (Sun=0) to Monday-based (Mon=0..Sun=6) my $mwday = ($_[0]->[6] + 6) % 7; - # Day-of-year of the Thursday in this ISO week my $thu = $yday - $mwday + 3; if ($thu < 0) { - # Thursday falls in the previous year - my $py = $year - 1; - $thu += (($py % 4 == 0 && $py % 100 != 0) || $py % 400 == 0) ? 366 : 365; + return ($year - 1, undef); } my $ylen = (($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0) ? 366 : 365; - return sprintf("%02d", 1) if $thu >= $ylen; - sprintf("%02d", int($thu / 7) + 1); + if ($thu >= $ylen) { + return ($year + 1, 1); + } + return ($year, int($thu / 7) + 1); } +sub format_V { + my ($iso_year, $wk) = _iso_week_year($_[0]); + unless (defined $wk) { + my $py = $iso_year; + my $yday = $_[0]->[7]; + my $mwday = ($_[0]->[6] + 6) % 7; + my $thu = $yday - $mwday + 3; + $thu += (($py % 4 == 0 && $py % 100 != 0) || $py % 400 == 0) ? 366 : 365; + $wk = int($thu / 7) + 1; + } + sprintf("%02d", $wk); +} + +sub format_g { sprintf("%02d", (_iso_week_year($_[0]))[0] % 100) } + 1; diff --git a/t/format.t b/t/format.t index 6b0e930..76f363b 100644 --- a/t/format.t +++ b/t/format.t @@ -1,4 +1,4 @@ -use Test::More tests => 314; +use Test::More tests => 324; use Date::Format qw(ctime time2str); use Date::Language; use utf8; @@ -35,6 +35,7 @@ __DATA__ %d 07 %e 7 %D 09/07/99 +%g 99 %G 1026 %h Sep %H 13 @@ -351,3 +352,15 @@ Dutch 1283926923 # Wed Sep 8 06:22:03 2010 GMT (AM) %p VM %P vm +1767182400 # Wed Dec 31 12:00:00 2025 GMT — ISO week 1 of 2026 +%g 26 +%V 01 +%y 25 +1451649600 # Fri Jan 1 12:00:00 2016 GMT — ISO week 53 of 2015 +%g 15 +%V 53 +%y 16 +1419854400 # Mon Dec 29 12:00:00 2014 GMT — ISO week 1 of 2015 +%g 15 +%V 01 +%y 14