From 21c24a73e0b36f89878627b6f4ea9d2ff13eaa42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Thu, 7 May 2026 06:49:00 -0600 Subject: [PATCH] =?UTF-8?q?fix:=20@Dsuf=20arrays=20too=20short=20=E2=80=94?= =?UTF-8?q?=20format=5Fo=20drops=20suffix=20for=20day=2031?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 20 language modules had @Dsuf with only 31 elements (indices 0-30). format_o accesses $dsuf->[$day] where $day is 1-31, so day 31 reads index 31 which was undef — producing "31" with no suffix and an uninitialized value warning. Visible in 8 modules without format_o overrides (Romanian, Amharic, Tigrinya, TigrinyaEritrean, TigrinyaEthiopian, Arabic, Occitan, Spanish). The remaining 12 modules had the same short array but were masked by custom format_o implementations. Fix: extend all @Dsuf arrays to 32 elements. Tighten the lang-data.t validation threshold from >= 31 to >= 32 and add a regression test exercising format_o on day 31 for every language. Co-Authored-By: Claude Opus 4.6 --- lib/Date/Language/Amharic.pm | 4 ++-- lib/Date/Language/Arabic.pm | 2 +- lib/Date/Language/Chinese.pm | 2 +- lib/Date/Language/Chinese_GB.pm | 2 +- lib/Date/Language/Czech.pm | 2 +- lib/Date/Language/Dutch.pm | 2 +- lib/Date/Language/Finnish.pm | 2 +- lib/Date/Language/Greek.pm | 2 +- lib/Date/Language/Hungarian.pm | 2 +- lib/Date/Language/Occitan.pm | 2 +- lib/Date/Language/Romanian.pm | 2 +- lib/Date/Language/Russian.pm | 2 +- lib/Date/Language/Russian_cp1251.pm | 2 +- lib/Date/Language/Russian_koi8r.pm | 2 +- lib/Date/Language/Spanish.pm | 2 +- lib/Date/Language/Swedish.pm | 2 +- lib/Date/Language/Tigrinya.pm | 2 +- lib/Date/Language/TigrinyaEritrean.pm | 4 ++-- lib/Date/Language/TigrinyaEthiopian.pm | 4 ++-- lib/Date/Language/Turkish.pm | 2 +- t/lang-data.t | 19 ++++++++++++++++++- 21 files changed, 41 insertions(+), 24 deletions(-) diff --git a/lib/Date/Language/Amharic.pm b/lib/Date/Language/Amharic.pm index 95e62d8..bb6b9e8 100644 --- a/lib/Date/Language/Amharic.pm +++ b/lib/Date/Language/Amharic.pm @@ -43,7 +43,7 @@ if ( $] >= 5.006 ) { @MoYs = map { substr($_,0,3) } @MoY; @AMPM = ( "\x{1320}\x{12cb}\x{1275}", "\x{12a8}\x{1230}\x{12d3}\x{1275}" ); -@Dsuf = ("\x{129b}") x 31; +@Dsuf = ("\x{129b}") x 32; } else { @DoW = ( @@ -73,7 +73,7 @@ else { @MoYs = map { substr($_,0,9) } @MoY; @AMPM = ( "ጠዋት", "ከሰዓት" ); -@Dsuf = ("ኛ") x 31; +@Dsuf = ("ኛ") x 32; } Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Arabic.pm b/lib/Date/Language/Arabic.pm index 7764845..9375cbb 100644 --- a/lib/Date/Language/Arabic.pm +++ b/lib/Date/Language/Arabic.pm @@ -18,7 +18,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); $MoYs[6] = 'يوليو'; @AMPM = qw(صباحا مساءا); -@Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er'); #To be amended +@Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er', 'e'); #To be amended Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Chinese.pm b/lib/Date/Language/Chinese.pm index e85b4f1..f09ff8a 100644 --- a/lib/Date/Language/Chinese.pm +++ b/lib/Date/Language/Chinese.pm @@ -21,7 +21,7 @@ our @DoWs = map { $_ } @DoW; our @MoYs = map { $_ } @MoY; our @AMPM = qw(上午 下午); -our @Dsuf = ("日") x 31; +our @Dsuf = ("日") x 32; our ( %MoY, %DoW ); Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Chinese_GB.pm b/lib/Date/Language/Chinese_GB.pm index c6c3916..d7263b3 100644 --- a/lib/Date/Language/Chinese_GB.pm +++ b/lib/Date/Language/Chinese_GB.pm @@ -49,7 +49,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); "\xcf\xc2\xce\xe7", # 下午 ); -@Dsuf = ("\xc8\xd5") x 31; # 日 +@Dsuf = ("\xc8\xd5") x 32; # 日 Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Czech.pm b/lib/Date/Language/Czech.pm index c4b345b..634035f 100644 --- a/lib/Date/Language/Czech.pm +++ b/lib/Date/Language/Czech.pm @@ -26,7 +26,7 @@ our @DoW = qw(neděle pondělí úterý středa čtvrtek pátek sobota); our @DoWs = qw(Ne Po Út St Čt Pá So); our @AMPM = qw(dop. odp.); -our @Dsuf = ('.') x 31; +our @Dsuf = ('.') x 32; our ( %MoY, %DoW ); Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Dutch.pm b/lib/Date/Language/Dutch.pm index 5c865f9..8512963 100644 --- a/lib/Date/Language/Dutch.pm +++ b/lib/Date/Language/Dutch.pm @@ -24,7 +24,7 @@ $MoYs[2] = 'mrt'; # mrt is more common (Frank Maas) # these aren't normally used... @AMPM = qw(VM NM); -@Dsuf = ('e') x 31; +@Dsuf = ('e') x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Finnish.pm b/lib/Date/Language/Finnish.pm index a3cde18..af6e219 100644 --- a/lib/Date/Language/Finnish.pm +++ b/lib/Date/Language/Finnish.pm @@ -26,7 +26,7 @@ our @MoYs = @MoY; our @DoWs = @DoW; # the short form of ordinals -our @Dsuf = ('.') x 31; +our @Dsuf = ('.') x 32; # doesn't look like this is normally used... our @AMPM = qw(ap ip); diff --git a/lib/Date/Language/Greek.pm b/lib/Date/Language/Greek.pm index cca4db1..b0e1033 100644 --- a/lib/Date/Language/Greek.pm +++ b/lib/Date/Language/Greek.pm @@ -75,7 +75,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); @AMPM = ("πμ", "μμ"); -@Dsuf = ("η") x 31; +@Dsuf = ("η") x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Hungarian.pm b/lib/Date/Language/Hungarian.pm index 3866a1b..677b852 100644 --- a/lib/Date/Language/Hungarian.pm +++ b/lib/Date/Language/Hungarian.pm @@ -64,7 +64,7 @@ our @MoYs = map { substr($_,0,3) } @MoY; our @AMPM = qw(DE. DU.); # There is no 'th or 'nd in Hungarian, just a dot -our @Dsuf = (".") x 31; +our @Dsuf = (".") x 32; our ( %MoY, %DoW ); Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Occitan.pm b/lib/Date/Language/Occitan.pm index afa2b81..42c7f67 100644 --- a/lib/Date/Language/Occitan.pm +++ b/lib/Date/Language/Occitan.pm @@ -22,7 +22,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); $MoYs[6] = 'jul'; @AMPM = qw(AM PM); -@Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er'); +@Dsuf = ((qw(er e e e e e e e e e)) x 3, 'er', 'e'); Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Romanian.pm b/lib/Date/Language/Romanian.pm index 87fa114..ec070f1 100644 --- a/lib/Date/Language/Romanian.pm +++ b/lib/Date/Language/Romanian.pm @@ -23,7 +23,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); @AMPM = qw(AM PM); -@Dsuf = ('') x 31; +@Dsuf = ('') x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Russian.pm b/lib/Date/Language/Russian.pm index a893504..93e3317 100644 --- a/lib/Date/Language/Russian.pm +++ b/lib/Date/Language/Russian.pm @@ -97,7 +97,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @MoY2, @DoWs2, @AMPM, @Dsuf, %MoY, %DoW); "\xd0\xd0", # пп ); -@Dsuf = ('.') x 31; +@Dsuf = ('.') x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Russian_cp1251.pm b/lib/Date/Language/Russian_cp1251.pm index 74a28d6..b8ced1f 100755 --- a/lib/Date/Language/Russian_cp1251.pm +++ b/lib/Date/Language/Russian_cp1251.pm @@ -54,7 +54,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); -@Dsuf = ('e') x 31; +@Dsuf = ('e') x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Russian_koi8r.pm b/lib/Date/Language/Russian_koi8r.pm index 204b90a..a724451 100755 --- a/lib/Date/Language/Russian_koi8r.pm +++ b/lib/Date/Language/Russian_koi8r.pm @@ -54,7 +54,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); @MoYs = map { substr($_,0,3) } @MoY; @AMPM = qw(AM PM); -@Dsuf = ('e') x 31; +@Dsuf = ('e') x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Spanish.pm b/lib/Date/Language/Spanish.pm index e57a699..0a00acb 100644 --- a/lib/Date/Language/Spanish.pm +++ b/lib/Date/Language/Spanish.pm @@ -21,7 +21,7 @@ our @DoWs = map { substr($_,0,3) } @DoW; our @MoYs = map { substr($_,0,3) } @MoY; our @AMPM = qw(AM PM); -our @Dsuf = ((qw(ro do ro to to to mo vo no mo)) x 3, 'ro'); +our @Dsuf = ((qw(ro do ro to to to mo vo no mo)) x 3, 'ro', 'do'); our ( %MoY, %DoW ); Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Swedish.pm b/lib/Date/Language/Swedish.pm index 67f54d4..ef4708f 100644 --- a/lib/Date/Language/Swedish.pm +++ b/lib/Date/Language/Swedish.pm @@ -20,7 +20,7 @@ our @DoW = map($_ . "dagen", qw(sön mån tis ons tors fre lör)); our @DoWs = map { substr($_,0,2) } @DoW; # the ordinals are not typically used in modern times -our @Dsuf = (('a') x 2, ('e') x 29); +our @Dsuf = (('a') x 2, ('e') x 30); our @AMPM = @{Date::Language::English::AMPM}; diff --git a/lib/Date/Language/Tigrinya.pm b/lib/Date/Language/Tigrinya.pm index 320a632..9fb347e 100644 --- a/lib/Date/Language/Tigrinya.pm +++ b/lib/Date/Language/Tigrinya.pm @@ -45,7 +45,7 @@ our (@DoW, @DoWs, @MoY, @MoYs, @AMPM, @Dsuf, %MoY, %DoW); "\x{12F5}/\x{1230}" ); -@Dsuf = ("\x{12ed}") x 31; +@Dsuf = ("\x{12ed}") x 32; Date::Language::_build_lookups(); diff --git a/lib/Date/Language/TigrinyaEritrean.pm b/lib/Date/Language/TigrinyaEritrean.pm index 44103b1..8a2d1d6 100644 --- a/lib/Date/Language/TigrinyaEritrean.pm +++ b/lib/Date/Language/TigrinyaEritrean.pm @@ -44,7 +44,7 @@ if ( $] >= 5.006 ) { "\x{12F5}/\x{1230}" ); -@Dsuf = ("\x{12ed}") x 31; +@Dsuf = ("\x{12ed}") x 32; } else { @DoW = ( @@ -77,7 +77,7 @@ else { "ድ/ሰ" ); -@Dsuf = ("ይ") x 31; +@Dsuf = ("ይ") x 32; } Date::Language::_build_lookups(); diff --git a/lib/Date/Language/TigrinyaEthiopian.pm b/lib/Date/Language/TigrinyaEthiopian.pm index 600b663..365f50a 100644 --- a/lib/Date/Language/TigrinyaEthiopian.pm +++ b/lib/Date/Language/TigrinyaEthiopian.pm @@ -46,7 +46,7 @@ if ( $] >= 5.006 ) { "\x{12F5}/\x{1230}" ); -@Dsuf = ("\x{12ed}") x 31; +@Dsuf = ("\x{12ed}") x 32; } else { @DoW = ( @@ -79,7 +79,7 @@ else { "ድ/ሰ" ); -@Dsuf = ("ይ") x 31; +@Dsuf = ("ይ") x 32; } Date::Language::_build_lookups(); diff --git a/lib/Date/Language/Turkish.pm b/lib/Date/Language/Turkish.pm index 24a8f74..78f9d87 100755 --- a/lib/Date/Language/Turkish.pm +++ b/lib/Date/Language/Turkish.pm @@ -41,7 +41,7 @@ our %DsufMAP = ( 31 => 'inci', ); -our @Dsuf = map{ $DsufMAP{$_} } sort {$a <=> $b} keys %DsufMAP; +our @Dsuf = ((map{ $DsufMAP{$_} } sort {$a <=> $b} keys %DsufMAP), ''); our ( %MoY, %DoW ); Date::Language::_build_lookups(); diff --git a/t/lang-data.t b/t/lang-data.t index 54671ed..ba5f5ad 100644 --- a/t/lang-data.t +++ b/t/lang-data.t @@ -67,7 +67,7 @@ for my $lang (sort keys %expected) { is(scalar @{"${pkg}::MoY"}, 12, "$lang: 12 month names"); is(scalar @{"${pkg}::MoYs"}, 12, "$lang: 12 short month names"); is(scalar @{"${pkg}::AMPM"}, 2, "$lang: 2 AM/PM entries"); - cmp_ok(scalar @{"${pkg}::Dsuf"}, '>=', 31, "$lang: at least 31 day-suffix entries"); + cmp_ok(scalar @{"${pkg}::Dsuf"}, '>=', 32, "$lang: at least 32 day-suffix entries (indices 0-31)"); } # Regression: Austrian October abbreviation must be "Okt" (German), not "Oct" (English) @@ -116,4 +116,21 @@ for my $lang (sort keys %expected) { "Russian: Monday formats as Понедельник"); } +# Regression: format_o (%o) must produce a suffix for day 31 in all languages. +# Bug: modules with @Dsuf of only 31 elements (indices 0-30) returned undef for +# day 31 (index 31), producing a bare "31" instead of "31". +{ + # Fri Jan 31 00:00:00 2025 UTC — day 31 + my $day31_time = 1738281600; + + for my $lang (sort keys %expected) { + my $obj = Date::Language->new($lang); + my $result = $obj->time2str('%o', $day31_time, 'GMT'); + # Result must start with "31" and must not emit an undef warning. + # Languages with empty suffix (e.g. Romanian) produce "31" — that's fine, + # but it must be the empty string '', not undef. + like($result, qr/^\s*31/, "$lang: format_o day 31 produces output"); + } +} + done_testing;