From 7704f229ab3cc3faef78ed9c7a85ec41e64ed6e0 Mon Sep 17 00:00:00 2001 From: saddamr3e Date: Mon, 22 Jun 2026 16:52:35 +0530 Subject: [PATCH 1/2] fix(WDate): fix UB and wrong results for negative (BCE) years in setYmd WDate::setYmd packed the year into an unsigned int field using a left-shift on a signed int (y << 16). When the year is negative (BCE dates), this is undefined behavior in C++11/14/17. Even where the platform produces a deterministic result, the unsigned storage caused operator<() to return wrong results (a BCE date compared greater than any CE date) and year() to return a garbage positive value (e.g., 65535 for year -1). The API explicitly supports negative years: fromJulianDay() produces years as far back as -4713 (Julian Day epoch), and setDate() accepts any year in [date::year::min(), date::year::max()] = [-32768, 32767]. Fix: - Change ymd_ from unsigned to int so that operator<() performs a signed comparison (correct for BCE vs CE ordering) and year() returns the right value via arithmetic right-shift. - In setYmd(), cast y to unsigned before shifting (shifting unsigned is always well-defined in C++) then static_cast the result back into ymd_, preserving the two's-complement bit pattern. - Fix isValid() from (ymd_ > 1) to (ymd_ != 0 && ymd_ != 1). The old guard relied on valid dates always being > 1, which holds for CE years but fails for BCE years whose packed ymd_ is negative. All downstream methods (dayOfWeek, daysTo, toJulianDay, toTimePoint, addDays, addMonths, addYears, toString, fromJulianDay, previousWeekday) are fixed transitively since they all call year(). --- src/Wt/WDate.C | 9 ++++++++- src/Wt/WDate.h | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Wt/WDate.C b/src/Wt/WDate.C index 90cf95ae1..88987871f 100644 --- a/src/Wt/WDate.C +++ b/src/Wt/WDate.C @@ -124,7 +124,14 @@ void WDate::setDate(int year, int month, int day) void WDate::setYmd(int y, int m, int d) { - ymd_ = (y << 16) | ((m & 0xFF) << 8) | (d & 0xFF); + // Cast to unsigned before shifting to avoid UB on negative years (C++11/14/17). + // The two's-complement bit pattern is then stored in signed int ymd_ so that + // operator<() performs a correct signed comparison and year() returns the right + // value via arithmetic right-shift for BCE (negative) years. + ymd_ = static_cast( + (static_cast(y) << 16) | + ((static_cast(m) & 0xFF) << 8) | + (static_cast(d) & 0xFF)); } bool WDate::isLeapYear(int year) diff --git a/src/Wt/WDate.h b/src/Wt/WDate.h index 8b385ae86..10b345c65 100644 --- a/src/Wt/WDate.h +++ b/src/Wt/WDate.h @@ -162,7 +162,7 @@ class WT_API WDate * * \sa isNull(), WDate(int, int, int), setDate() */ - bool isValid() const { return ymd_ > 1; } + bool isValid() const { return ymd_ != 0 && ymd_ != 1; } /*! \brief Returns the year. * @@ -447,7 +447,7 @@ class WT_API WDate static RegExpInfo formatToRegExp(const WT_USTRING& format); private: - unsigned ymd_; + int ymd_; void setYmd(int year, int month, int day); From 8bbea9ef4892aae1044cb5de66ff55b55cd05e96 Mon Sep 17 00:00:00 2001 From: saddamr3e Date: Mon, 22 Jun 2026 17:29:48 +0530 Subject: [PATCH 2/2] fix(WDate): handle negative (BCE) years correctly --- src/Wt/WDate.C | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Wt/WDate.C b/src/Wt/WDate.C index 88987871f..841c2c743 100644 --- a/src/Wt/WDate.C +++ b/src/Wt/WDate.C @@ -124,10 +124,6 @@ void WDate::setDate(int year, int month, int day) void WDate::setYmd(int y, int m, int d) { - // Cast to unsigned before shifting to avoid UB on negative years (C++11/14/17). - // The two's-complement bit pattern is then stored in signed int ymd_ so that - // operator<() performs a correct signed comparison and year() returns the right - // value via arithmetic right-shift for BCE (negative) years. ymd_ = static_cast( (static_cast(y) << 16) | ((static_cast(m) & 0xFF) << 8) |