blob: a94555610747d8203ceadc867375f5c360130b16 [file] [log] [blame] [raw]
/* A part of the Native C Library for Windows NT
Copyright 2026 Rivoreo
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
*/
/*
* A part of this file comes from public domain source, so
* clarified as of June 5, 1996 by Arthur David Olson.
*/
#include <sys/types.h>
#include <stdint.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <float.h>
#define PCTS
#ifndef TYPE_BIT
#define TYPE_BIT(T) (sizeof(T) * CHAR_BIT)
#endif
#ifndef TYPE_SIGNED
#define TYPE_SIGNED(T) (((T)-1) < 0)
#endif
#ifndef TYPE_INTEGRAL
#define TYPE_INTEGRAL(T) (((T)0.5) != 0.5)
#endif
#ifndef YEARSPERREPEAT
#define YEARSPERREPEAT 400
#endif
#ifndef AVGSECSPERYEAR
#define AVGSECSPERYEAR 31556952L
#endif
#ifndef TZ_MAX_TIMES
#define TZ_MAX_TIMES 1200
#endif
#ifndef TZ_MAX_TYPES
#define TZ_MAX_TYPES 256 /* UINT8_MAX + 1 */
#endif
#ifndef TZ_MAX_CHARS
#define TZ_MAX_CHARS 50
#endif
#ifndef TZ_MAX_LEAPS
#define TZ_MAX_LEAPS 50
#endif
#define SECSPERMIN 60
#define MINSPERHOUR 60
#define HOURSPERDAY 24
#define DAYSPERWEEK 7
#define DAYSPERNYEAR 365
#define DAYSPERLYEAR 366
#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
#define SECSPERDAY ((long int)SECSPERHOUR * HOURSPERDAY)
#define MONSPERYEAR 12
#define TM_SUNDAY 0
#define TM_MONDAY 1
#define TM_TUESDAY 2
#define TM_WEDNESDAY 3
#define TM_THURSDAY 4
#define TM_FRIDAY 5
#define TM_SATURDAY 6
#define TM_JANUARY 0
#define TM_FEBRUARY 1
#define TM_MARCH 2
#define TM_APRIL 3
#define TM_MAY 4
#define TM_JUNE 5
#define TM_JULY 6
#define TM_AUGUST 7
#define TM_SEPTEMBER 8
#define TM_OCTOBER 9
#define TM_NOVEMBER 10
#define TM_DECEMBER 11
#define TM_YEAR_BASE 1900
#define EPOCH_YEAR 1970
#define EPOCH_WDAY TM_THURSDAY
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
#define _RWLOCK_RDLOCK(L)
#define _RWLOCK_UNLOCK(L)
#define GMT "UTC"
struct ttinfo { /* time type information */
long tt_gmtoff; /* UTC offset in seconds */
int tt_isdst; /* used to set tm_isdst */
int tt_abbrind; /* abbreviation list index */
int tt_ttisstd; /* TRUE if transition is std time */
int tt_ttisgmt; /* TRUE if transition is UTC */
};
struct lsinfo { /* leap second information */
time_t ls_trans; /* transition time */
long ls_corr; /* correction to apply */
};
#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
#ifdef TZNAME_MAX
#define MY_TZNAME_MAX TZNAME_MAX
#endif /* defined TZNAME_MAX */
#ifndef TZNAME_MAX
#define MY_TZNAME_MAX 255
#endif /* !defined TZNAME_MAX */
struct state {
int leapcnt;
int timecnt;
int typecnt;
int charcnt;
int goback;
int goahead;
time_t ats[TZ_MAX_TIMES];
unsigned char types[TZ_MAX_TIMES];
struct ttinfo ttis[TZ_MAX_TYPES];
char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof GMT),
(2 * (MY_TZNAME_MAX + 1)))];
struct lsinfo lsis[TZ_MAX_LEAPS];
};
static struct tm * gmtsub(const time_t *, long int, struct tm *);
static struct tm * localsub(const time_t *, long int, struct tm *);
#ifdef ALL_STATE
static struct state * lclptr;
static struct state * gmtptr;
#else
static struct state lclmem;
static struct state gmtmem;
#define lclptr (&lclmem)
#define gmtptr (&gmtmem)
#endif
static const int mon_lengths[2][MONSPERYEAR] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
static const int year_lengths[2] = {
DAYSPERNYEAR, DAYSPERLYEAR
};
/*
** Adapted from code provided by Robert Elz, who writes:
** The "best" way to do mktime I think is based on an idea of Bob
** Kridle's (so its said...) from a long time ago.
** It does a binary search of the time_t space. Since time_t's are
** just 32 bits, its a max of 32 iterations (even at 64 bits it
** would still be very reasonable).
*/
#ifndef WRONG
#define WRONG (-1)
#endif
/*
** Simplified normalize logic courtesy Paul Eggert.
*/
static int increment_overflow(int *number, int delta)
{
int number0 = *number;
*number += delta;
return (*number < number0) != (delta < 0);
}
static int normalize_overflow(int *const tensptr, int *const unitsptr, const int base)
{
int tensdelta = (*unitsptr >= 0) ?
(*unitsptr / base) :
(-1 - (-1 - *unitsptr) / base);
*unitsptr -= tensdelta * base;
return increment_overflow(tensptr, tensdelta);
}
static int tmcomp(const struct tm *a, const struct tm *b)
{
int diff = a->tm_year - b->tm_year;
if(diff) return diff;
diff = a->tm_mon - b->tm_mon;
if(diff) return diff;
diff = a->tm_mday - b->tm_mday;
if(diff) return diff;
diff = a->tm_hour - b->tm_hour;
if(diff) return diff;
diff = a->tm_min - b->tm_min;
if(diff) return diff;
return a->tm_sec - b->tm_sec;
}
static time_t time2sub(struct tm *const tmp, struct tm *(*const funcp)(const time_t *, long, struct tm *), const long int offset, int *const okayp, const int do_norm_secs)
{
const struct state * sp;
int dir;
int i, j;
int saved_seconds;
int li;
time_t lo;
time_t hi;
int y;
time_t newt;
time_t t;
struct tm yourtm, mytm;
*okayp = 0;
yourtm = *tmp;
if (do_norm_secs) {
if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
SECSPERMIN))
return WRONG;
}
if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
return WRONG;
if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
return WRONG;
y = yourtm.tm_year;
if (normalize_overflow(&y, &yourtm.tm_mon, MONSPERYEAR)) return WRONG;
/*
** Turn y into an actual year number for now.
** It is converted back to an offset from TM_YEAR_BASE later.
*/
if (increment_overflow(&y, TM_YEAR_BASE)) return WRONG;
while (yourtm.tm_mday <= 0) {
if (increment_overflow(&y, -1)) return WRONG;
li = y + (1 < yourtm.tm_mon);
yourtm.tm_mday += year_lengths[isleap(li)];
}
while (yourtm.tm_mday > DAYSPERLYEAR) {
li = y + (1 < yourtm.tm_mon);
yourtm.tm_mday -= year_lengths[isleap(li)];
if (increment_overflow(&y, 1)) return WRONG;
}
for ( ; ; ) {
i = mon_lengths[isleap(y)][yourtm.tm_mon];
if (yourtm.tm_mday <= i)
break;
yourtm.tm_mday -= i;
if (++yourtm.tm_mon >= MONSPERYEAR) {
yourtm.tm_mon = 0;
if (increment_overflow(&y, 1)) return WRONG;
}
}
if (increment_overflow(&y, -TM_YEAR_BASE)) return WRONG;
yourtm.tm_year = y;
if (yourtm.tm_year != y)
return WRONG;
/* Don't go below 1900 for POLA */
if (yourtm.tm_year < 0)
return WRONG;
if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
saved_seconds = 0;
else if (y + TM_YEAR_BASE < EPOCH_YEAR) {
/*
** We can't set tm_sec to 0, because that might push the
** time below the minimum representable time.
** Set tm_sec to 59 instead.
** This assumes that the minimum representable time is
** not in the same minute that a leap second was deleted from,
** which is a safer assumption than using 58 would be.
*/
if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
return WRONG;
saved_seconds = yourtm.tm_sec;
yourtm.tm_sec = SECSPERMIN - 1;
} else {
saved_seconds = yourtm.tm_sec;
yourtm.tm_sec = 0;
}
/*
** Do a binary search (this works whatever time_t's type is).
*/
if (!TYPE_SIGNED(time_t)) {
lo = 0;
hi = lo - 1;
} else if (!TYPE_INTEGRAL(time_t)) {
if (sizeof(time_t) > sizeof(float))
hi = (time_t) DBL_MAX;
else hi = (time_t) FLT_MAX;
lo = -hi;
} else {
lo = 1;
for (i = 0; i < (int) TYPE_BIT(time_t) - 1; ++i)
lo *= 2;
hi = -(lo + 1);
}
for ( ; ; ) {
t = lo / 2 + hi / 2;
if (t < lo)
t = lo;
else if (t > hi)
t = hi;
if (!funcp(&t, offset, &mytm)) {
/*
** Assume that t is too extreme to be represented in
** a struct tm; arrange things so that it is less
** extreme on the next pass.
*/
dir = (t > 0) ? 1 : -1;
} else dir = tmcomp(&mytm, &yourtm);
if (dir != 0) {
if (t == lo) {
++t;
if (t <= lo)
return WRONG;
++lo;
} else if (t == hi) {
--t;
if (t >= hi)
return WRONG;
--hi;
}
if (lo > hi)
return WRONG;
if (dir > 0)
hi = t;
else lo = t;
continue;
}
if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
break;
/*
** Right time, wrong type.
** Hunt for right time, right type.
** It's okay to guess wrong since the guess
** gets checked.
*/
sp = (const struct state *)
((funcp == localsub) ? lclptr : gmtptr);
#ifdef ALL_STATE
if (sp == NULL)
return WRONG;
#endif /* defined ALL_STATE */
for (i = sp->typecnt - 1; i >= 0; --i) {
if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
continue;
for (j = sp->typecnt - 1; j >= 0; --j) {
if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
continue;
newt = t + sp->ttis[j].tt_gmtoff -
sp->ttis[i].tt_gmtoff;
if (!funcp(&newt, offset, &mytm)) continue;
if (tmcomp(&mytm, &yourtm) != 0)
continue;
if (mytm.tm_isdst != yourtm.tm_isdst)
continue;
/*
** We have a match.
*/
t = newt;
goto label;
}
}
return WRONG;
}
label:
newt = t + saved_seconds;
if ((newt < t) != (saved_seconds < 0))
return WRONG;
t = newt;
if (funcp(&t, offset, tmp)) *okayp = 1;
return t;
}
static time_t time2(struct tm *const tmp, struct tm *(*const funcp)(const time_t *, long, struct tm *), const long offset, int *const okayp)
{
/*
** First try without normalization of seconds
** (in case tm_sec contains a value associated with a leap second).
** If that fails, try with normalization of seconds.
*/
time_t t = time2sub(tmp, funcp, offset, okayp, 0);
if(*okayp) return t;
return time2sub(tmp, funcp, offset, okayp, 1);
}
/*
** Return the number of leap years through the end of the given year
** where, to make the math easy, the answer for year zero is defined as zero.
*/
static int leaps_thru_end_of(const int y)
{
return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
-(leaps_thru_end_of(-(y + 1)) + 1);
}
static struct tm *timesub(const time_t *const timep, const long int offset, const struct state *const sp, struct tm *const tmp)
{
const struct lsinfo * lp;
time_t tdays;
int idays; /* unsigned would be so 2003 */
long rem;
int y;
const int * ip;
long corr;
int hit;
int i;
corr = 0;
hit = 0;
#ifdef ALL_STATE
i = (sp == NULL) ? 0 : sp->leapcnt;
#else
i = sp->leapcnt;
#endif
while (--i >= 0) {
lp = &sp->lsis[i];
if (*timep >= lp->ls_trans) {
if (*timep == lp->ls_trans) {
hit = ((i == 0 && lp->ls_corr > 0) ||
lp->ls_corr > sp->lsis[i - 1].ls_corr);
if (hit)
while (i > 0 &&
sp->lsis[i].ls_trans ==
sp->lsis[i - 1].ls_trans + 1 &&
sp->lsis[i].ls_corr ==
sp->lsis[i - 1].ls_corr + 1) {
++hit;
--i;
}
}
corr = lp->ls_corr;
break;
}
}
y = EPOCH_YEAR;
tdays = *timep / SECSPERDAY;
rem = *timep - tdays * SECSPERDAY;
while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
int newy;
register time_t tdelta;
register int idelta;
register int leapdays;
tdelta = tdays / DAYSPERLYEAR;
idelta = tdelta;
if (tdelta - idelta >= 1 || idelta - tdelta >= 1)
return NULL;
if (idelta == 0)
idelta = (tdays < 0) ? -1 : 1;
newy = y;
if (increment_overflow(&newy, idelta))
return NULL;
leapdays = leaps_thru_end_of(newy - 1) -
leaps_thru_end_of(y - 1);
tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
tdays -= leapdays;
y = newy;
}
{
register long seconds;
seconds = tdays * SECSPERDAY + 0.5;
tdays = seconds / SECSPERDAY;
rem += seconds - tdays * SECSPERDAY;
}
/*
** Given the range, we can now fearlessly cast...
*/
idays = tdays;
rem += offset - corr;
while (rem < 0) {
rem += SECSPERDAY;
--idays;
}
while (rem >= SECSPERDAY) {
rem -= SECSPERDAY;
++idays;
}
while (idays < 0) {
if (increment_overflow(&y, -1))
return NULL;
idays += year_lengths[isleap(y)];
}
while (idays >= year_lengths[isleap(y)]) {
idays -= year_lengths[isleap(y)];
if (increment_overflow(&y, 1))
return NULL;
}
tmp->tm_year = y;
if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
return NULL;
tmp->tm_yday = idays;
/*
** The "extra" mods below avoid overflow problems.
*/
tmp->tm_wday = EPOCH_WDAY +
((y - EPOCH_YEAR) % DAYSPERWEEK) *
(DAYSPERNYEAR % DAYSPERWEEK) +
leaps_thru_end_of(y - 1) -
leaps_thru_end_of(EPOCH_YEAR - 1) +
idays;
tmp->tm_wday %= DAYSPERWEEK;
if (tmp->tm_wday < 0)
tmp->tm_wday += DAYSPERWEEK;
tmp->tm_hour = (int) (rem / SECSPERHOUR);
rem %= SECSPERHOUR;
tmp->tm_min = (int) (rem / SECSPERMIN);
/*
** A positive leap second requires a special
** representation. This uses "... ??:59:60" et seq.
*/
tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
ip = mon_lengths[isleap(y)];
for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
idays -= ip[tmp->tm_mon];
tmp->tm_mday = (int) (idays + 1);
tmp->tm_isdst = 0;
return tmp;
}
/*
** The easy way to behave "as if no library function calls" localtime
** is to not call it--so we drop its guts into "localsub", which can be
** freely called. (And no, the PANS doesn't require the above behavior--
** but it *is* desirable.)
**
** The unused offset argument is for the benefit of mktime variants.
*/
/*ARGSUSED*/
static struct tm *localsub(const time_t *const timep, const long offset, struct tm *const tmp)
{
struct state * sp;
const struct ttinfo * ttisp;
int i;
struct tm * result;
const time_t t = *timep;
sp = lclptr;
#ifdef ALL_STATE
if (sp == NULL)
return gmtsub(timep, offset, tmp);
#endif /* defined ALL_STATE */
if ((sp->goback && t < sp->ats[0]) ||
(sp->goahead && t > sp->ats[sp->timecnt - 1])) {
time_t newt = t;
register time_t seconds;
register time_t tcycles;
register int_fast64_t icycles;
if (t < sp->ats[0])
seconds = sp->ats[0] - t;
else seconds = t - sp->ats[sp->timecnt - 1];
--seconds;
tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR;
++tcycles;
icycles = tcycles;
if (tcycles - icycles >= 1 || icycles - tcycles >= 1)
return NULL;
seconds = icycles;
seconds *= YEARSPERREPEAT;
seconds *= AVGSECSPERYEAR;
if (t < sp->ats[0])
newt += seconds;
else newt -= seconds;
if (newt < sp->ats[0] ||
newt > sp->ats[sp->timecnt - 1])
return NULL; /* "cannot happen" */
result = localsub(&newt, offset, tmp);
if (result == tmp) {
register time_t newy;
newy = tmp->tm_year;
if (t < sp->ats[0])
newy -= icycles * YEARSPERREPEAT;
else newy += icycles * YEARSPERREPEAT;
tmp->tm_year = newy;
if (tmp->tm_year != newy)
return NULL;
}
return result;
}
if (sp->timecnt == 0 || t < sp->ats[0]) {
i = 0;
while (sp->ttis[i].tt_isdst)
if (++i >= sp->typecnt) {
i = 0;
break;
}
} else {
register int lo = 1;
register int hi = sp->timecnt;
while (lo < hi) {
register int mid = (lo + hi) >> 1;
if (t < sp->ats[mid])
hi = mid;
else lo = mid + 1;
}
i = (int) sp->types[lo - 1];
}
ttisp = &sp->ttis[i];
/*
** To get (wrong) behavior that's compatible with System V Release 2.0
** you'd replace the statement below with
** t += ttisp->tt_gmtoff;
** timesub(&t, 0L, sp, tmp);
*/
result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
tmp->tm_isdst = ttisp->tt_isdst;
tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
return result;
}
/*
** gmtsub is to gmtime as localsub is to localtime.
*/
static struct tm *gmtsub(const time_t *const timep, const long int offset, struct tm *const tmp)
{
return timesub(timep, offset, gmtptr, tmp);
}
static time_t time1(struct tm *const tmp, struct tm *(*const funcp)(const time_t *, long, struct tm *), const long int offset)
{
time_t t;
const struct state * sp;
int samei, otheri;
int sameind, otherind;
int i;
int nseen;
int seen[TZ_MAX_TYPES];
int types[TZ_MAX_TYPES];
int okay;
if (tmp == NULL) {
errno = EINVAL;
return WRONG;
}
if (tmp->tm_isdst > 1)
tmp->tm_isdst = 1;
t = time2(tmp, funcp, offset, &okay);
#ifdef PCTS
/*
** PCTS code courtesy Grant Sullivan.
*/
if (okay)
return t;
if (tmp->tm_isdst < 0)
tmp->tm_isdst = 0; /* reset to std and try again */
#else
if (okay || tmp->tm_isdst < 0)
return t;
#endif /* !defined PCTS */
/*
** We're supposed to assume that somebody took a time of one type
** and did some math on it that yielded a "struct tm" that's bad.
** We try to divine the type they started from and adjust to the
** type they need.
*/
sp = (const struct state *) ((funcp == localsub) ? lclptr : gmtptr);
#ifdef ALL_STATE
if (sp == NULL)
return WRONG;
#endif /* defined ALL_STATE */
for (i = 0; i < sp->typecnt; ++i) seen[i] = 0;
nseen = 0;
for (i = sp->timecnt - 1; i >= 0; --i) {
if (!seen[sp->types[i]]) {
seen[sp->types[i]] = 1;
types[nseen++] = sp->types[i];
}
}
for (sameind = 0; sameind < nseen; ++sameind) {
samei = types[sameind];
if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
continue;
for (otherind = 0; otherind < nseen; ++otherind) {
otheri = types[otherind];
if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst)
continue;
tmp->tm_sec += sp->ttis[otheri].tt_gmtoff -
sp->ttis[samei].tt_gmtoff;
tmp->tm_isdst = !tmp->tm_isdst;
t = time2(tmp, funcp, offset, &okay);
if (okay)
return t;
tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff -
sp->ttis[samei].tt_gmtoff;
tmp->tm_isdst = !tmp->tm_isdst;
}
}
return WRONG;
}
time_t
mktime(struct tm *const tmp)
{
time_t mktime_return_value;
_RWLOCK_RDLOCK(&lcl_rwlock);
tzset();
mktime_return_value = time1(tmp, localsub, 0L);
_RWLOCK_UNLOCK(&lcl_rwlock);
return(mktime_return_value);
}
time_t
timelocal(struct tm *const tmp)
{
if (tmp != NULL)
tmp->tm_isdst = -1; /* in case it wasn't initialized */
return mktime(tmp);
}
time_t
timegm(struct tm *const tmp)
{
if (tmp != NULL)
tmp->tm_isdst = 0;
return time1(tmp, gmtsub, 0L);
}