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 <phco...@gmail.com> Signed-off-by: Andrew Hamilton <adham...@gmail.com> --- grub-core/lib/datetime.c | 48 ++++++++++++++++++++++++++++++++-------- include/grub/datetime.h | 15 ++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/grub-core/lib/datetime.c b/grub-core/lib/datetime.c index 8f0922fb0..4e68eabc6 100644 --- a/grub-core/lib/datetime.c +++ b/grub-core/lib/datetime.c @@ -64,6 +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 @@ -76,7 +80,7 @@ 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; @@ -92,12 +96,39 @@ 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 = 1901 + 4 * (days / DAYSPER4YEARS); + datetime->year = 1 + 400 * (days / DAYSPER400YEARS); + days %= DAYSPER400YEARS; + + /* 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; @@ -108,11 +139,10 @@ grub_unixtime2datetime (grub_int64_t nix, struct grub_datetime *datetime) datetime->year += days / DAYSPERYEAR; days %= DAYSPERYEAR; } + int isbisextile = datetime->year % 4 == 0 && (datetime->year % 100 != 0 || datetime->year % 400 == 0); 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]); + && days >= (i==1 && isbisextile ? 29 : months[i]); i++) + days -= (i==1 && isbisextile ? 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..9289b0d00 100644 --- a/include/grub/datetime.h +++ b/include/grub/datetime.h @@ -54,7 +54,7 @@ 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; const grub_uint16_t monthssum[12] = { 0, @@ -75,15 +75,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 +90,16 @@ grub_datetime2unixtime (const struct grub_datetime *datetime, grub_int64_t *nix) ret += y4 * SECPER4YEARS; ret += ay * SECPERYEAR; + ret -= ((datetime->year - 1) / 100 - (datetime->year - 1) / 400 - 15) * SECPERDAY; + ret += monthssum[datetime->month - 1] * SECPERDAY; - if (ay == 3 && datetime->month >= 3) + int isbisextile = ay == 3 && (datetime->year % 100 != 0 || datetime->year % 400 == 0); + if (isbisextile && datetime->month >= 3) ret += SECPERDAY; ret += (datetime->day - 1) * SECPERDAY; if ((datetime->day > months[datetime->month - 1] - && (!ay || datetime->month != 2 || datetime->day != 29)) + && !(isbisextile && 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