On Mon, Aug 25, 2025 at 10:17:37PM -0500, Andrew Hamilton wrote:
> 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 <[email protected]>
> Signed-off-by: Andrew Hamilton <[email protected]>
> ---
>  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);

Even if it works it is not fully correct...

is_bisextile = (datetime->year % 4 == 0 &&
                (datetime->year % 100 != 0 || datetime->year % 400 == 0)) ? 
true : false;

> +  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);

Again...

is_bisextile = (ay == 3 && (datetime->year % 100 != 0 || datetime->year % 400 
== 0)) ? true : false;

> +  if (is_bisextile && datetime->month >= 3)

is_bisextile == true && ...

>      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))

is_bisextile == true && ...

... and I would drop "is_" from variable name...

Daniel

_______________________________________________
Grub-devel mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/grub-devel

Reply via email to