From: Andrew Hamilton <adhamilt@gmail.com>
To: grub-devel@gnu.org
Cc: daniel.kiper@oracle.com, masayuki.moriyama@miraclelinux.com,
andrea.biardi@viavisolutions.com, phcoder@gmail.com,
Andrew Hamilton <adhamilt@gmail.com>
Subject: [PATCH v4 1/2] datetime: Support dates outside of 1901..2038 range
Date: Mon, 25 Aug 2025 22:17:37 -0500 [thread overview]
Message-ID: <20250826031738.86977-2-adhamilt@gmail.com> (raw)
In-Reply-To: <20250826031738.86977-1-adhamilt@gmail.com>
Support dates outside of 1901..2038.
Fixes: https://savannah.gnu.org/bugs/?63894
Fixes: https://savannah.gnu.org/bugs/?66301
Signed-off-by: Vladimir Serbinenko <phcoder@gmail.com>
Signed-off-by: Andrew Hamilton <adhamilt@gmail.com>
---
grub-core/lib/datetime.c | 64 ++++++++++++++++++++++++++++++++--------
include/grub/datetime.h | 27 ++++++++++++-----
2 files changed, 71 insertions(+), 20 deletions(-)
diff --git a/grub-core/lib/datetime.c b/grub-core/lib/datetime.c
index 8f0922fb0..a0f84d989 100644
--- a/grub-core/lib/datetime.c
+++ b/grub-core/lib/datetime.c
@@ -64,7 +64,10 @@ grub_get_weekday_name (struct grub_datetime *datetime)
#define SECPERDAY (24*SECPERHOUR)
#define DAYSPERYEAR 365
#define DAYSPER4YEARS (4*DAYSPERYEAR+1)
-
+/* 24 leap years in 100 years */
+#define DAYSPER100YEARS (100 * DAYSPERYEAR + 24)
+/* 97 leap years in 400 years */
+#define DAYSPER400YEARS (400 * DAYSPERYEAR + 97)
void
grub_unixtime2datetime (grub_int64_t nix, struct grub_datetime *datetime)
@@ -76,10 +79,12 @@ grub_unixtime2datetime (grub_int64_t nix, struct grub_datetime *datetime)
/* Convenience: let's have 3 consecutive non-bissextile years
at the beginning of the counting date. So count from 1901. */
int days_epoch;
- /* Number of days since 1st Januar, 1901. */
+ /* Number of days since 1st January, 1 (proleptic). */
unsigned days;
/* Seconds into current day. */
unsigned secs_in_day;
+ /* Tracks whether this is a leap year. */
+ bool is_bisextile;
/* Transform C divisions and modulos to mathematical ones */
if (nix < 0)
@@ -92,27 +97,62 @@ grub_unixtime2datetime (grub_int64_t nix, struct grub_datetime *datetime)
days_epoch = grub_divmod64 (nix, SECPERDAY, NULL);
secs_in_day = nix - days_epoch * SECPERDAY;
- days = days_epoch + 69 * DAYSPERYEAR + 17;
+ /*
+ * 1970 is Unix Epoch. Adjust to a year 1 epoch:
+ * Leap year logic:
+ * - Years evenly divisible by 400 are leap years
+ * - Otherwise, if divisible by 100 are not leap years
+ * - Otherwise, if divisible by 4 are leap years
+ * There are four 400-year periods (1600 years worth of days with leap days)
+ * There are three 100-year periods worth of leap days (3*24)
+ * There are 369 years in addition to the four 400 year periods
+ * There are 17 leap days in 69 years (beyond the three 100 year periods)
+ */
+ days = days_epoch + 369 * DAYSPERYEAR + 17 + 24 * 3 + 4 * DAYSPER400YEARS;
+
+ datetime->year = 1 + 400 * (days / DAYSPER400YEARS);
+ days %= DAYSPER400YEARS;
- datetime->year = 1901 + 4 * (days / DAYSPER4YEARS);
+ /*
+ * On 31st December of bissextile (leap) years 365 days from the beginning
+ * of the year elapsed but year isn't finished yet - every 400 years
+ * 396 is 4 years less than 400 year leap cycle
+ * 96 is 1 day less than number of leap days in 400 years
+ */
+ if (days / DAYSPER100YEARS == 4)
+ {
+ datetime->year += 396;
+ days -= 396 * DAYSPERYEAR + 96;
+ }
+ else
+ {
+ datetime->year += 100 * (days / DAYSPER100YEARS);
+ days %= DAYSPER100YEARS;
+ }
+
+ datetime->year += 4 * (days / DAYSPER4YEARS);
days %= DAYSPER4YEARS;
- /* On 31st December of bissextile years 365 days from the beginning
- of the year elapsed but year isn't finished yet */
+ /*
+ * On 31st December of bissextile (leap) years 365 days from the beginning
+ * of the year elapsed but year isn't finished yet - every 4 years
+ */
if (days / DAYSPERYEAR == 4)
{
datetime->year += 3;
- days -= 3*DAYSPERYEAR;
+ days -= 3 * DAYSPERYEAR;
}
else
{
datetime->year += days / DAYSPERYEAR;
days %= DAYSPERYEAR;
}
- for (i = 0; i < 12
- && days >= (i==1 && datetime->year % 4 == 0
- ? 29 : months[i]); i++)
- days -= (i==1 && datetime->year % 4 == 0
- ? 29 : months[i]);
+
+ is_bisextile = datetime->year % 4 == 0
+ && (datetime->year % 100 != 0 || datetime->year % 400 == 0);
+ for (i = 0;
+ i < 12 && days >= ((i == 1 && is_bisextile == true) ? 29 : months[i]);
+ i++)
+ days -= ((i == 1 && is_bisextile == true) ? 29 : months[i]);
datetime->month = i + 1;
datetime->day = 1 + days;
datetime->hour = (secs_in_day / SECPERHOUR);
diff --git a/include/grub/datetime.h b/include/grub/datetime.h
index bcec636f0..ff847e847 100644
--- a/include/grub/datetime.h
+++ b/include/grub/datetime.h
@@ -54,8 +54,9 @@ void grub_unixtime2datetime (grub_int64_t nix,
static inline int
grub_datetime2unixtime (const struct grub_datetime *datetime, grub_int64_t *nix)
{
- grub_int32_t ret;
+ grub_int64_t ret;
int y4, ay;
+ bool is_bisextile;
const grub_uint16_t monthssum[12]
= { 0,
31,
@@ -75,15 +76,11 @@ grub_datetime2unixtime (const struct grub_datetime *datetime, grub_int64_t *nix)
const int SECPERHOUR = 60 * SECPERMIN;
const int SECPERDAY = 24 * SECPERHOUR;
const int SECPERYEAR = 365 * SECPERDAY;
- const int SECPER4YEARS = 4 * SECPERYEAR + SECPERDAY;
+ const grub_int64_t SECPER4YEARS = 4 * SECPERYEAR + SECPERDAY;
- if (datetime->year > 2038 || datetime->year < 1901)
- return 0;
if (datetime->month > 12 || datetime->month < 1)
return 0;
- /* In the period of validity of unixtime all years divisible by 4
- are bissextile*/
/* Convenience: let's have 3 consecutive non-bissextile years
at the beginning of the epoch. So count from 1973 instead of 1970 */
ret = 3 * SECPERYEAR + SECPERDAY;
@@ -94,13 +91,27 @@ grub_datetime2unixtime (const struct grub_datetime *datetime, grub_int64_t *nix)
ret += y4 * SECPER4YEARS;
ret += ay * SECPERYEAR;
+ /*
+ * Correct above calculation (which assumes every 4 years is a leap year)
+ * to remove those "false leap years" that are divisible by 100 but not 400.
+ * Since this logic starts with seconds since 1973, 15 is used because:
+ * - (1973 - 1) / 100 = 19 (floor due to integer math)
+ * - (1973 - 1) / 400 = 4 (floor due to integer math)
+ * - 19 - 4 - 15 = 0 (we want to start with no "false leap years" at time
+ * zero of 1973)
+ */
+ ret -= ((datetime->year - 1) / 100 - (datetime->year - 1) / 400 - 15)
+ * SECPERDAY;
+
ret += monthssum[datetime->month - 1] * SECPERDAY;
- if (ay == 3 && datetime->month >= 3)
+ is_bisextile = ay == 3
+ && (datetime->year % 100 != 0 || datetime->year % 400 == 0);
+ if (is_bisextile && datetime->month >= 3)
ret += SECPERDAY;
ret += (datetime->day - 1) * SECPERDAY;
if ((datetime->day > months[datetime->month - 1]
- && (!ay || datetime->month != 2 || datetime->day != 29))
+ && !(is_bisextile && datetime->month == 2 && datetime->day == 29))
|| datetime->day < 1)
return 0;
--
2.39.5
_______________________________________________
Grub-devel mailing list
Grub-devel@gnu.org
https://lists.gnu.org/mailman/listinfo/grub-devel
next prev parent reply other threads:[~2025-08-26 3:18 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-08-26 3:17 [PATCH v4 0/2] Support dates outside of 1901..2038 range Andrew Hamilton
2025-08-26 3:17 ` Andrew Hamilton [this message]
2025-08-26 15:06 ` [PATCH v4 1/2] datetime: " Daniel Kiper via Grub-devel
2025-08-26 15:28 ` Andrew Hamilton
2025-08-26 16:53 ` Daniel Kiper via Grub-devel
2025-08-26 17:00 ` Andrew Hamilton
2025-08-26 3:17 ` [PATCH v4 2/2] date_unit_test: test dates outside of 32-bit unix range Andrew Hamilton
2025-08-26 15:25 ` Daniel Kiper via Grub-devel
2025-08-26 15:35 ` Andrew Hamilton
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250826031738.86977-2-adhamilt@gmail.com \
--to=adhamilt@gmail.com \
--cc=andrea.biardi@viavisolutions.com \
--cc=daniel.kiper@oracle.com \
--cc=grub-devel@gnu.org \
--cc=masayuki.moriyama@miraclelinux.com \
--cc=phcoder@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).