Quelle localtime.c
Sprache: C
/* Convert timestamp from pg_time_t to struct pg_tm. */
/*
* This file is in the public domain , so clarified as of
* 1996 - 06 - 05 by Arthur David Olson .
*
* IDENTIFICATION
* src / timezone / localtime . c
*/
/*
* Leap second handling from Bradley White .
* POSIX - style TZ environment variable handling from Guy Harris .
*/
/* this file needs to build in both frontend and backend contexts */
#include "c.h"
#include <fcntl.h>
#include "datatype/timestamp.h"
#include "pgtz.h"
#include "private.h"
#include "tzfile.h"
#ifndef WILDABBR
/*
* Someone might make incorrect use of a time zone abbreviation :
* 1 . They might reference tzname [ 0 ] before calling tzset ( explicitly
* or implicitly ) .
* 2 . They might reference tzname [ 1 ] before calling tzset ( explicitly
* or implicitly ) .
* 3 . They might reference tzname [ 1 ] after setting to a time zone
* in which Daylight Saving Time is never observed .
* 4 . They might reference tzname [ 0 ] after setting to a time zone
* in which Standard Time is never observed .
* 5 . They might reference tm . tm_zone after calling offtime .
* What ' s best to do in the above cases is open to debate ;
* for now , we just set things up so that in any of the five cases
* WILDABBR is used . Another possibility : initialize tzname [ 0 ] to the
* string " tzname [ 0 ] used before set " , and similarly for the other cases .
* And another : initialize tzname [ 0 ] to " ERA " , with an explanation in the
* manual page of what this " time zone abbreviation " means ( doing this so
* that tzname [ 0 ] has the " normal " length of three characters ) .
*/
#define WILDABBR " "
#endif /* !defined WILDABBR */
static const char wildabbr[] = WILDABBR;
static const char gmt[] = "GMT" ;
/*
* The DST rules to use if a POSIX TZ string has no rules .
* Default to US rules as of 2017 - 05 - 07 .
* POSIX does not specify the default DST rules ;
* for historical reasons , US rules are a common default .
*/
#define TZDEFRULESTRING ",M3.2.0,M11.1.0"
/* structs ttinfo, lsinfo, state have been moved to pgtz.h */
enum r_type
{
JULIAN_DAY, /* Jn = Julian day */
DAY_OF_YEAR, /* n = day of year */
MONTH_NTH_DAY_OF_WEEK, /* Mm.n.d = month, week, day of week */
};
struct rule
{
enum r_type r_type; /* type of rule */
int r_day; /* day number of rule */
int r_week; /* week number of rule */
int r_mon; /* month number of rule */
int32 r_time; /* transition time of rule */
};
/*
* Prototypes for static functions .
*/
static struct pg_tm *gmtsub(pg_time_t const *timep, int32 offset,
struct pg_tm *tmp);
static bool increment_overflow(int *ip, int j);
static bool increment_overflow_time(pg_time_t *tp, int32 j);
static int64 leapcorr(struct state const *sp, pg_time_t t);
static struct pg_tm *timesub(pg_time_t const *timep,
int32 offset, struct state const *sp,
struct pg_tm *tmp);
static bool typesequiv(struct state const *sp, int a, int b);
/*
* Section 4 . 12 . 3 of X3 . 159 - 1989 requires that
* Except for the strftime function , these functions [ asctime ,
* ctime , gmtime , localtime ] return values in one of two static
* objects : a broken - down time structure and an array of char .
* Thanks to Paul Eggert for noting this .
*/
static struct pg_tm tm;
/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */
static void
init_ttinfo(struct ttinfo *s, int32 utoff, bool isdst, int desigidx)
{
s->tt_utoff = utoff;
s->tt_isdst = isdst;
s->tt_desigidx = desigidx;
s->tt_ttisstd = false ;
s->tt_ttisut = false ;
}
static int32
detzcode(const char *const codep)
{
int32 result;
int i;
int32 one = 1 ;
int32 halfmaxval = one << (32 - 2 );
int32 maxval = halfmaxval - 1 + halfmaxval;
int32 minval = -1 - maxval;
result = codep[0 ] & 0 x7f;
for (i = 1 ; i < 4 ; ++i)
result = (result << 8 ) | (codep[i] & 0 xff);
if (codep[0 ] & 0 x80)
{
/*
* Do two ' s - complement negation even on non - two ' s - complement machines .
* If the result would be minval - 1 , return minval .
*/
result -= !TWOS_COMPLEMENT(int32) && result != 0 ;
result += minval;
}
return result;
}
static int64
detzcode64(const char *const codep)
{
uint64 result;
int i;
int64 one = 1 ;
int64 halfmaxval = one << (64 - 2 );
int64 maxval = halfmaxval - 1 + halfmaxval;
int64 minval = -TWOS_COMPLEMENT(int64) - maxval;
result = codep[0 ] & 0 x7f;
for (i = 1 ; i < 8 ; ++i)
result = (result << 8 ) | (codep[i] & 0 xff);
if (codep[0 ] & 0 x80)
{
/*
* Do two ' s - complement negation even on non - two ' s - complement machines .
* If the result would be minval - 1 , return minval .
*/
result -= !TWOS_COMPLEMENT(int64) && result != 0 ;
result += minval;
}
return result;
}
static bool
differ_by_repeat(const pg_time_t t1, const pg_time_t t0)
{
if (TYPE_BIT(pg_time_t) - TYPE_SIGNED(pg_time_t) < SECSPERREPEAT_BITS)
return 0 ;
return t1 - t0 == SECSPERREPEAT;
}
/* Input buffer for data read from a compiled tz file. */
union input_buffer
{
/* The first part of the buffer, interpreted as a header. */
struct tzhead tzhead;
/* The entire buffer. */
char buf[2 * sizeof (struct tzhead) + 2 * sizeof (struct state)
+ 4 * TZ_MAX_TIMES];
};
/* Local storage needed for 'tzloadbody'. */
union local_storage
{
/* The results of analyzing the file's contents after it is opened. */
struct file_analysis
{
/* The input buffer. */
union input_buffer u;
/* A temporary state used for parsing a TZ string in the file. */
struct state st;
} u;
/* We don't need the "fullname" member */
};
/* Load tz data from the file named NAME into *SP. Read extended
* format if DOEXTEND . Use * LSP for temporary storage . Return 0 on
* success , an errno value on failure .
* PG : If " canonname " is not NULL , then on success the canonical spelling of
* given name is stored there ( the buffer must be > TZ_STRLEN_MAX bytes ! ) .
*/
static int
tzloadbody(char const *name, char *canonname, struct state *sp, bool doextend,
union local_storage *lsp)
{
int i;
int fid;
int stored;
ssize_t nread;
union input_buffer *up = &lsp->u.u;
int tzheadsize = sizeof (struct tzhead);
sp->goback = sp->goahead = false ;
if (!name)
{
name = TZDEFAULT;
if (!name)
return EINVAL;
}
if (name[0 ] == ':' )
++name;
fid = pg_open_tzfile(name, canonname);
if (fid < 0 )
return ENOENT; /* pg_open_tzfile may not set errno */
nread = read(fid, up->buf, sizeof up->buf);
if (nread < tzheadsize)
{
int err = nread < 0 ? errno : EINVAL;
close(fid);
return err;
}
if (close(fid) < 0 )
return errno;
for (stored = 4 ; stored <= 8 ; stored *= 2 )
{
int32 ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt);
int32 ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt);
int64 prevtr = 0 ;
int32 prevcorr = 0 ;
int32 leapcnt = detzcode(up->tzhead.tzh_leapcnt);
int32 timecnt = detzcode(up->tzhead.tzh_timecnt);
int32 typecnt = detzcode(up->tzhead.tzh_typecnt);
int32 charcnt = detzcode(up->tzhead.tzh_charcnt);
char const *p = up->buf + tzheadsize;
/*
* Although tzfile ( 5 ) currently requires typecnt to be nonzero ,
* support future formats that may allow zero typecnt in files that
* have a TZ string and no transitions .
*/
if (!(0 <= leapcnt && leapcnt < TZ_MAX_LEAPS
&& 0 <= typecnt && typecnt < TZ_MAX_TYPES
&& 0 <= timecnt && timecnt < TZ_MAX_TIMES
&& 0 <= charcnt && charcnt < TZ_MAX_CHARS
&& (ttisstdcnt == typecnt || ttisstdcnt == 0 )
&& (ttisutcnt == typecnt || ttisutcnt == 0 )))
return EINVAL;
if (nread
< (tzheadsize /* struct tzhead */
+ timecnt * stored /* ats */
+ timecnt /* types */
+ typecnt * 6 /* ttinfos */
+ charcnt /* chars */
+ leapcnt * (stored + 4 ) /* lsinfos */
+ ttisstdcnt /* ttisstds */
+ ttisutcnt)) /* ttisuts */
return EINVAL;
sp->leapcnt = leapcnt;
sp->timecnt = timecnt;
sp->typecnt = typecnt;
sp->charcnt = charcnt;
/*
* Read transitions , discarding those out of pg_time_t range . But
* pretend the last transition before TIME_T_MIN occurred at
* TIME_T_MIN .
*/
timecnt = 0 ;
for (i = 0 ; i < sp->timecnt; ++i)
{
int64 at
= stored == 4 ? detzcode(p) : detzcode64(p);
sp->types[i] = at <= TIME_T_MAX;
if (sp->types[i])
{
pg_time_t attime
= ((TYPE_SIGNED(pg_time_t) ? at < TIME_T_MIN : at < 0 )
? TIME_T_MIN : at);
if (timecnt && attime <= sp->ats[timecnt - 1 ])
{
if (attime < sp->ats[timecnt - 1 ])
return EINVAL;
sp->types[i - 1 ] = 0 ;
timecnt--;
}
sp->ats[timecnt++] = attime;
}
p += stored;
}
timecnt = 0 ;
for (i = 0 ; i < sp->timecnt; ++i)
{
unsigned char typ = *p++;
if (sp->typecnt <= typ)
return EINVAL;
if (sp->types[i])
sp->types[timecnt++] = typ;
}
sp->timecnt = timecnt;
for (i = 0 ; i < sp->typecnt; ++i)
{
struct ttinfo *ttisp;
unsigned char isdst,
desigidx;
ttisp = &sp->ttis[i];
ttisp->tt_utoff = detzcode(p);
p += 4 ;
isdst = *p++;
if (!(isdst < 2 ))
return EINVAL;
ttisp->tt_isdst = isdst;
desigidx = *p++;
if (!(desigidx < sp->charcnt))
return EINVAL;
ttisp->tt_desigidx = desigidx;
}
for (i = 0 ; i < sp->charcnt; ++i)
sp->chars[i] = *p++;
sp->chars[i] = '\0' ; /* ensure '\0' at end */
/* Read leap seconds, discarding those out of pg_time_t range. */
leapcnt = 0 ;
for (i = 0 ; i < sp->leapcnt; ++i)
{
int64 tr = stored == 4 ? detzcode(p) : detzcode64(p);
int32 corr = detzcode(p + stored);
p += stored + 4 ;
/* Leap seconds cannot occur before the Epoch. */
if (tr < 0 )
return EINVAL;
if (tr <= TIME_T_MAX)
{
/*
* Leap seconds cannot occur more than once per UTC month , and
* UTC months are at least 28 days long ( minus 1 second for a
* negative leap second ) . Each leap second ' s correction must
* differ from the previous one ' s by 1 second .
*/
if (tr - prevtr < 28 * SECSPERDAY - 1
|| (corr != prevcorr - 1 && corr != prevcorr + 1 ))
return EINVAL;
sp->lsis[leapcnt].ls_trans = prevtr = tr;
sp->lsis[leapcnt].ls_corr = prevcorr = corr;
leapcnt++;
}
}
sp->leapcnt = leapcnt;
for (i = 0 ; i < sp->typecnt; ++i)
{
struct ttinfo *ttisp;
ttisp = &sp->ttis[i];
if (ttisstdcnt == 0 )
ttisp->tt_ttisstd = false ;
else
{
if (*p != true && *p != false )
return EINVAL;
ttisp->tt_ttisstd = *p++;
}
}
for (i = 0 ; i < sp->typecnt; ++i)
{
struct ttinfo *ttisp;
ttisp = &sp->ttis[i];
if (ttisutcnt == 0 )
ttisp->tt_ttisut = false ;
else
{
if (*p != true && *p != false )
return EINVAL;
ttisp->tt_ttisut = *p++;
}
}
/*
* If this is an old file , we ' re done .
*/
if (up->tzhead.tzh_version[0 ] == '\0' )
break ;
nread -= p - up->buf;
memmove(up->buf, p, nread);
}
if (doextend && nread > 2 &&
up->buf[0 ] == '\n' && up->buf[nread - 1 ] == '\n' &&
sp->typecnt + 2 <= TZ_MAX_TYPES)
{
struct state *ts = &lsp->u.st;
up->buf[nread - 1 ] = '\0' ;
if (tzparse(&up->buf[1 ], ts, false ))
{
/*
* Attempt to reuse existing abbreviations . Without this ,
* America / Anchorage would be right on the edge after 2037 when
* TZ_MAX_CHARS is 50 , as sp - > charcnt equals 40 ( for LMT AST AWT
* APT AHST AHDT YST AKDT AKST ) and ts - > charcnt equals 10 ( for
* AKST AKDT ) . Reusing means sp - > charcnt can stay 40 in this
* example .
*/
int gotabbr = 0 ;
int charcnt = sp->charcnt;
for (i = 0 ; i < ts->typecnt; i++)
{
char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx;
int j;
for (j = 0 ; j < charcnt; j++)
if (strcmp(sp->chars + j, tsabbr) == 0 )
{
ts->ttis[i].tt_desigidx = j;
gotabbr++;
break ;
}
if (!(j < charcnt))
{
int tsabbrlen = strlen(tsabbr);
if (j + tsabbrlen < TZ_MAX_CHARS)
{
strcpy(sp->chars + j, tsabbr);
charcnt = j + tsabbrlen + 1 ;
ts->ttis[i].tt_desigidx = j;
gotabbr++;
}
}
}
if (gotabbr == ts->typecnt)
{
sp->charcnt = charcnt;
/*
* Ignore any trailing , no - op transitions generated by zic as
* they don ' t help here and can run afoul of bugs in zic 2016 j
* or earlier .
*/
while (1 < sp->timecnt
&& (sp->types[sp->timecnt - 1 ]
== sp->types[sp->timecnt - 2 ]))
sp->timecnt--;
for (i = 0 ; i < ts->timecnt; i++)
if (sp->timecnt == 0
|| (sp->ats[sp->timecnt - 1 ]
< ts->ats[i] + leapcorr(sp, ts->ats[i])))
break ;
while (i < ts->timecnt
&& sp->timecnt < TZ_MAX_TIMES)
{
sp->ats[sp->timecnt]
= ts->ats[i] + leapcorr(sp, ts->ats[i]);
sp->types[sp->timecnt] = (sp->typecnt
+ ts->types[i]);
sp->timecnt++;
i++;
}
for (i = 0 ; i < ts->typecnt; i++)
sp->ttis[sp->typecnt++] = ts->ttis[i];
}
}
}
if (sp->typecnt == 0 )
return EINVAL;
if (sp->timecnt > 1 )
{
for (i = 1 ; i < sp->timecnt; ++i)
if (typesequiv(sp, sp->types[i], sp->types[0 ]) &&
differ_by_repeat(sp->ats[i], sp->ats[0 ]))
{
sp->goback = true ;
break ;
}
for (i = sp->timecnt - 2 ; i >= 0 ; --i)
if (typesequiv(sp, sp->types[sp->timecnt - 1 ],
sp->types[i]) &&
differ_by_repeat(sp->ats[sp->timecnt - 1 ],
sp->ats[i]))
{
sp->goahead = true ;
break ;
}
}
/*
* Infer sp - > defaulttype from the data . Although this default type is
* always zero for data from recent tzdb releases , things are trickier for
* data from tzdb 2018 e or earlier .
*
* The first set of heuristics work around bugs in 32 - bit data generated
* by tzdb 2013 c or earlier . The workaround is for zones like
* Australia / Macquarie where timestamps before the first transition have a
* time type that is not the earliest standard - time type . See :
* https : //mm.icann.org/pipermail/tz/2013-May/019368.html
*/
/*
* If type 0 is unused in transitions , it ' s the type to use for early
* times .
*/
for (i = 0 ; i < sp->timecnt; ++i)
if (sp->types[i] == 0 )
break ;
i = i < sp->timecnt ? -1 : 0 ;
/*
* Absent the above , if there are transition times and the first
* transition is to a daylight time find the standard type less than and
* closest to the type of the first transition .
*/
if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0 ]].tt_isdst)
{
i = sp->types[0 ];
while (--i >= 0 )
if (!sp->ttis[i].tt_isdst)
break ;
}
/*
* The next heuristics are for data generated by tzdb 2018 e or earlier ,
* for zones like EST5EDT where the first transition is to DST .
*/
/*
* If no result yet , find the first standard type . If there is none , punt
* to type zero .
*/
if (i < 0 )
{
i = 0 ;
while (sp->ttis[i].tt_isdst)
if (++i >= sp->typecnt)
{
i = 0 ;
break ;
}
}
/*
* A simple ' sp - > defaulttype = 0 ; ' would suffice here if we didn ' t have to
* worry about 2018 e - or - earlier data . Even simpler would be to remove the
* defaulttype member and just use 0 in its place .
*/
sp->defaulttype = i;
return 0 ;
}
/* Load tz data from the file named NAME into *SP. Read extended
* format if DOEXTEND . Return 0 on success , an errno value on failure .
* PG : If " canonname " is not NULL , then on success the canonical spelling of
* given name is stored there ( the buffer must be > TZ_STRLEN_MAX bytes ! ) .
*/
int
tzload(const char *name, char *canonname, struct state *sp, bool doextend)
{
union local_storage *lsp = malloc(sizeof *lsp);
if (!lsp)
return errno;
else
{
int err = tzloadbody(name, canonname, sp, doextend, lsp);
free(lsp);
return err;
}
}
static bool
typesequiv(const struct state *sp, int a, int b)
{
bool result;
if (sp == NULL ||
a < 0 || a >= sp->typecnt ||
b < 0 || b >= sp->typecnt)
result = false ;
else
{
const struct ttinfo *ap = &sp->ttis[a];
const struct ttinfo *bp = &sp->ttis[b];
result = (ap->tt_utoff == bp->tt_utoff
&& ap->tt_isdst == bp->tt_isdst
&& ap->tt_ttisstd == bp->tt_ttisstd
&& ap->tt_ttisut == bp->tt_ttisut
&& (strcmp(&sp->chars[ap->tt_desigidx],
&sp->chars[bp->tt_desigidx])
== 0 ));
}
return result;
}
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
};
/*
* Given a pointer into a timezone string , scan until a character that is not
* a valid character in a time zone abbreviation is found .
* Return a pointer to that character .
*/
static const char *
getzname(const char *strp)
{
char c;
while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
c != '+' )
++strp;
return strp;
}
/*
* Given a pointer into an extended timezone string , scan until the ending
* delimiter of the time zone abbreviation is located .
* Return a pointer to the delimiter .
*
* As with getzname above , the legal character set is actually quite
* restricted , with other characters producing undefined results .
* We don ' t do any checking here ; checking is done later in common - case code .
*/
static const char *
getqzname(const char *strp, const int delim)
{
int c;
while ((c = *strp) != '\0' && c != delim)
++strp;
return strp;
}
/*
* Given a pointer into a timezone string , extract a number from that string .
* Check that the number is within a specified range ; if it is not , return
* NULL .
* Otherwise , return a pointer to the first character not part of the number .
*/
static const char *
getnum(const char *strp, int *const nump, const int min, const int max)
{
char c;
int num;
if (strp == NULL || !is_digit(c = *strp))
return NULL;
num = 0 ;
do
{
num = num * 10 + (c - '0' );
if (num > max)
return NULL; /* illegal value */
c = *++strp;
} while (is_digit(c));
if (num < min)
return NULL; /* illegal value */
*nump = num;
return strp;
}
/*
* Given a pointer into a timezone string , extract a number of seconds ,
* in hh [ : mm [ : ss ] ] form , from the string .
* If any error occurs , return NULL .
* Otherwise , return a pointer to the first character not part of the number
* of seconds .
*/
static const char *
getsecs(const char *strp, int32 *const secsp)
{
int num;
/*
* ' HOURSPERDAY * DAYSPERWEEK - 1 ' allows quasi - Posix rules like
* " M10 . 4 . 6 / 26 " , which does not conform to Posix , but which specifies the
* equivalent of " 02 : 00 on the first Sunday on or after 23 Oct " .
*/
strp = getnum(strp, &num, 0 , HOURSPERDAY * DAYSPERWEEK - 1 );
if (strp == NULL)
return NULL;
*secsp = num * (int32) SECSPERHOUR;
if (*strp == ':' )
{
++strp;
strp = getnum(strp, &num, 0 , MINSPERHOUR - 1 );
if (strp == NULL)
return NULL;
*secsp += num * SECSPERMIN;
if (*strp == ':' )
{
++strp;
/* 'SECSPERMIN' allows for leap seconds. */
strp = getnum(strp, &num, 0 , SECSPERMIN);
if (strp == NULL)
return NULL;
*secsp += num;
}
}
return strp;
}
/*
* Given a pointer into a timezone string , extract an offset , in
* [ + - ] hh [ : mm [ : ss ] ] form , from the string .
* If any error occurs , return NULL .
* Otherwise , return a pointer to the first character not part of the time .
*/
static const char *
getoffset(const char *strp, int32 *const offsetp)
{
bool neg = false ;
if (*strp == '-' )
{
neg = true ;
++strp;
}
else if (*strp == '+' )
++strp;
strp = getsecs(strp, offsetp);
if (strp == NULL)
return NULL; /* illegal time */
if (neg)
*offsetp = -*offsetp;
return strp;
}
/*
* Given a pointer into a timezone string , extract a rule in the form
* date [ / time ] . See POSIX section 8 for the format of " date " and " time " .
* If a valid rule is not found , return NULL .
* Otherwise , return a pointer to the first character not part of the rule .
*/
static const char *
getrule(const char *strp, struct rule *const rulep)
{
if (*strp == 'J' )
{
/*
* Julian day .
*/
rulep->r_type = JULIAN_DAY;
++strp;
strp = getnum(strp, &rulep->r_day, 1 , DAYSPERNYEAR);
}
else if (*strp == 'M' )
{
/*
* Month , week , day .
*/
rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
++strp;
strp = getnum(strp, &rulep->r_mon, 1 , MONSPERYEAR);
if (strp == NULL)
return NULL;
if (*strp++ != '.' )
return NULL;
strp = getnum(strp, &rulep->r_week, 1 , 5 );
if (strp == NULL)
return NULL;
if (*strp++ != '.' )
return NULL;
strp = getnum(strp, &rulep->r_day, 0 , DAYSPERWEEK - 1 );
}
else if (is_digit(*strp))
{
/*
* Day of year .
*/
rulep->r_type = DAY_OF_YEAR;
strp = getnum(strp, &rulep->r_day, 0 , DAYSPERLYEAR - 1 );
}
else
return NULL; /* invalid format */
if (strp == NULL)
return NULL;
if (*strp == '/' )
{
/*
* Time specified .
*/
++strp;
strp = getoffset(strp, &rulep->r_time);
}
else
rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
return strp;
}
/*
* Given a year , a rule , and the offset from UT at the time that rule takes
* effect , calculate the year - relative time that rule takes effect .
*/
static int32
transtime(const int year, const struct rule *const rulep,
const int32 offset)
{
bool leapyear;
int32 value;
int i;
int d,
m1,
yy0,
yy1,
yy2,
dow;
INITIALIZE(value);
leapyear = isleap(year);
switch (rulep->r_type)
{
case JULIAN_DAY:
/*
* Jn - Julian day , 1 = = January 1 , 60 = = March 1 even in leap
* years . In non - leap years , or if the day number is 59 or less ,
* just add SECSPERDAY times the day number - 1 to the time of
* January 1 , midnight , to get the day .
*/
value = (rulep->r_day - 1 ) * SECSPERDAY;
if (leapyear && rulep->r_day >= 60 )
value += SECSPERDAY;
break ;
case DAY_OF_YEAR:
/*
* n - day of year . Just add SECSPERDAY times the day number to
* the time of January 1 , midnight , to get the day .
*/
value = rulep->r_day * SECSPERDAY;
break ;
case MONTH_NTH_DAY_OF_WEEK:
/*
* Mm . n . d - nth " dth day " of month m .
*/
/*
* Use Zeller ' s Congruence to get day - of - week of first day of
* month .
*/
m1 = (rulep->r_mon + 9 ) % 12 + 1 ;
yy0 = (rulep->r_mon <= 2 ) ? (year - 1 ) : year;
yy1 = yy0 / 100 ;
yy2 = yy0 % 100 ;
dow = ((26 * m1 - 2 ) / 10 +
1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7 ;
if (dow < 0 )
dow += DAYSPERWEEK;
/*
* " dow " is the day - of - week of the first day of the month . Get the
* day - of - month ( zero - origin ) of the first " dow " day of the month .
*/
d = rulep->r_day - dow;
if (d < 0 )
d += DAYSPERWEEK;
for (i = 1 ; i < rulep->r_week; ++i)
{
if (d + DAYSPERWEEK >=
mon_lengths[(int ) leapyear][rulep->r_mon - 1 ])
break ;
d += DAYSPERWEEK;
}
/*
* " d " is the day - of - month ( zero - origin ) of the day we want .
*/
value = d * SECSPERDAY;
for (i = 0 ; i < rulep->r_mon - 1 ; ++i)
value += mon_lengths[(int ) leapyear][i] * SECSPERDAY;
break ;
}
/*
* " value " is the year - relative time of 00 : 00 : 00 UT on the day in
* question . To get the year - relative time of the specified local time on
* that day , add the transition time and the current offset from UT .
*/
return value + rulep->r_time + offset;
}
/*
* Given a POSIX section 8 - style TZ string , fill in the rule tables as
* appropriate .
* Returns true on success , false on failure .
*/
bool
tzparse(const char *name, struct state *sp, bool lastditch)
{
const char *stdname;
const char *dstname = NULL;
size_t stdlen;
size_t dstlen;
size_t charcnt;
int32 stdoffset;
int32 dstoffset;
char *cp;
bool load_ok;
stdname = name;
if (lastditch)
{
/* Unlike IANA, don't assume name is exactly "GMT" */
stdlen = strlen(name); /* length of standard zone name */
name += stdlen;
stdoffset = 0 ;
}
else
{
if (*name == '<' )
{
name++;
stdname = name;
name = getqzname(name, '>' );
if (*name != '>' )
return false ;
stdlen = name - stdname;
name++;
}
else
{
name = getzname(name);
stdlen = name - stdname;
}
if (*name == '\0' ) /* we allow empty STD abbrev, unlike IANA */
return false ;
name = getoffset(name, &stdoffset);
if (name == NULL)
return false ;
}
charcnt = stdlen + 1 ;
if (sizeof sp->chars < charcnt)
return false ;
/*
* The IANA code always tries to tzload ( TZDEFRULES ) here . We do not want
* to do that ; it would be bad news in the lastditch case , where we can ' t
* assume pg_open_tzfile ( ) is sane yet . Moreover , if we did load it and
* it contains leap - second - dependent info , that would cause problems too .
* Finally , IANA has deprecated the TZDEFRULES feature , so it presumably
* will die at some point . Desupporting it now seems like good
* future - proofing .
*/
load_ok = false ;
sp->goback = sp->goahead = false ; /* simulate failed tzload() */
sp->leapcnt = 0 ; /* intentionally assume no leap seconds */
if (*name != '\0' )
{
if (*name == '<' )
{
dstname = ++name;
name = getqzname(name, '>' );
if (*name != '>' )
return false ;
dstlen = name - dstname;
name++;
}
else
{
dstname = name;
name = getzname(name);
dstlen = name - dstname; /* length of DST abbr. */
}
if (!dstlen)
return false ;
charcnt += dstlen + 1 ;
if (sizeof sp->chars < charcnt)
return false ;
if (*name != '\0' && *name != ',' && *name != ';' )
{
name = getoffset(name, &dstoffset);
if (name == NULL)
return false ;
}
else
dstoffset = stdoffset - SECSPERHOUR;
if (*name == '\0' && !load_ok)
name = TZDEFRULESTRING;
if (*name == ',' || *name == ';' )
{
struct rule start;
struct rule end;
int year;
int yearlim;
int timecnt;
pg_time_t janfirst;
int32 janoffset = 0 ;
int yearbeg;
++name;
if ((name = getrule(name, &start)) == NULL)
return false ;
if (*name++ != ',' )
return false ;
if ((name = getrule(name, &end)) == NULL)
return false ;
if (*name != '\0' )
return false ;
sp->typecnt = 2 ; /* standard time and DST */
/*
* Two transitions per year , from EPOCH_YEAR forward .
*/
init_ttinfo(&sp->ttis[0 ], -stdoffset, false , 0 );
init_ttinfo(&sp->ttis[1 ], -dstoffset, true , stdlen + 1 );
sp->defaulttype = 0 ;
timecnt = 0 ;
janfirst = 0 ;
yearbeg = EPOCH_YEAR;
do
{
int32 yearsecs
= year_lengths[isleap(yearbeg - 1 )] * SECSPERDAY;
yearbeg--;
if (increment_overflow_time(&janfirst, -yearsecs))
{
janoffset = -yearsecs;
break ;
}
} while (EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
yearlim = yearbeg + YEARSPERREPEAT + 1 ;
for (year = yearbeg; year < yearlim; year++)
{
int32
starttime = transtime(year, &start, stdoffset),
endtime = transtime(year, &end, dstoffset);
int32
yearsecs = (year_lengths[isleap(year)]
* SECSPERDAY);
bool reversed = endtime < starttime;
if (reversed)
{
int32 swap = starttime;
starttime = endtime;
endtime = swap;
}
if (reversed
|| (starttime < endtime
&& (endtime - starttime
< (yearsecs
+ (stdoffset - dstoffset)))))
{
if (TZ_MAX_TIMES - 2 < timecnt)
break ;
sp->ats[timecnt] = janfirst;
if (!increment_overflow_time
(&sp->ats[timecnt],
janoffset + starttime))
sp->types[timecnt++] = !reversed;
sp->ats[timecnt] = janfirst;
if (!increment_overflow_time
(&sp->ats[timecnt],
janoffset + endtime))
{
sp->types[timecnt++] = reversed;
yearlim = year + YEARSPERREPEAT + 1 ;
}
}
if (increment_overflow_time
(&janfirst, janoffset + yearsecs))
break ;
janoffset = 0 ;
}
sp->timecnt = timecnt;
if (!timecnt)
{
sp->ttis[0 ] = sp->ttis[1 ];
sp->typecnt = 1 ; /* Perpetual DST. */
}
else if (YEARSPERREPEAT < year - yearbeg)
sp->goback = sp->goahead = true ;
}
else
{
int32 theirstdoffset;
int32 theirdstoffset;
int32 theiroffset;
bool isdst;
int i;
int j;
if (*name != '\0' )
return false ;
/*
* Initial values of theirstdoffset and theirdstoffset .
*/
theirstdoffset = 0 ;
for (i = 0 ; i < sp->timecnt; ++i)
{
j = sp->types[i];
if (!sp->ttis[j].tt_isdst)
{
theirstdoffset =
-sp->ttis[j].tt_utoff;
break ;
}
}
theirdstoffset = 0 ;
for (i = 0 ; i < sp->timecnt; ++i)
{
j = sp->types[i];
if (sp->ttis[j].tt_isdst)
{
theirdstoffset =
-sp->ttis[j].tt_utoff;
break ;
}
}
/*
* Initially we ' re assumed to be in standard time .
*/
isdst = false ;
theiroffset = theirstdoffset;
/*
* Now juggle transition times and types tracking offsets as you
* do .
*/
for (i = 0 ; i < sp->timecnt; ++i)
{
j = sp->types[i];
sp->types[i] = sp->ttis[j].tt_isdst;
if (sp->ttis[j].tt_ttisut)
{
/* No adjustment to transition time */
}
else
{
/*
* If daylight saving time is in effect , and the
* transition time was not specified as standard time , add
* the daylight saving time offset to the transition time ;
* otherwise , add the standard time offset to the
* transition time .
*/
/*
* Transitions from DST to DDST will effectively disappear
* since POSIX provides for only one DST offset .
*/
if (isdst && !sp->ttis[j].tt_ttisstd)
{
sp->ats[i] += dstoffset -
theirdstoffset;
}
else
{
sp->ats[i] += stdoffset -
theirstdoffset;
}
}
theiroffset = -sp->ttis[j].tt_utoff;
if (sp->ttis[j].tt_isdst)
theirdstoffset = theiroffset;
else
theirstdoffset = theiroffset;
}
/*
* Finally , fill in ttis .
*/
init_ttinfo(&sp->ttis[0 ], -stdoffset, false , 0 );
init_ttinfo(&sp->ttis[1 ], -dstoffset, true , stdlen + 1 );
sp->typecnt = 2 ;
sp->defaulttype = 0 ;
}
}
else
{
dstlen = 0 ;
sp->typecnt = 1 ; /* only standard time */
sp->timecnt = 0 ;
init_ttinfo(&sp->ttis[0 ], -stdoffset, false , 0 );
sp->defaulttype = 0 ;
}
sp->charcnt = charcnt;
cp = sp->chars;
memcpy(cp, stdname, stdlen);
cp += stdlen;
*cp++ = '\0' ;
if (dstlen != 0 )
{
memcpy(cp, dstname, dstlen);
*(cp + dstlen) = '\0' ;
}
return true ;
}
static void
gmtload(struct state *const sp)
{
if (tzload(gmt, NULL, sp, true ) != 0 )
tzparse(gmt, sp, true );
}
/*
* 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 . )
*/
static struct pg_tm *
localsub(struct state const *sp, pg_time_t const *timep,
struct pg_tm *const tmp)
{
const struct ttinfo *ttisp;
int i;
struct pg_tm *result;
const pg_time_t t = *timep;
if (sp == NULL)
return gmtsub(timep, 0 , tmp);
if ((sp->goback && t < sp->ats[0 ]) ||
(sp->goahead && t > sp->ats[sp->timecnt - 1 ]))
{
pg_time_t newt = t;
pg_time_t seconds;
pg_time_t years;
if (t < sp->ats[0 ])
seconds = sp->ats[0 ] - t;
else
seconds = t - sp->ats[sp->timecnt - 1 ];
--seconds;
years = (seconds / SECSPERREPEAT + 1 ) * YEARSPERREPEAT;
seconds = years * 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(sp, &newt, tmp);
if (result)
{
int64 newy;
newy = result->tm_year;
if (t < sp->ats[0 ])
newy -= years;
else
newy += years;
if (!(INT_MIN <= newy && newy <= INT_MAX))
return NULL;
result->tm_year = newy;
}
return result;
}
if (sp->timecnt == 0 || t < sp->ats[0 ])
{
i = sp->defaulttype;
}
else
{
int lo = 1 ;
int hi = sp->timecnt;
while (lo < hi)
{
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_utoff ;
* timesub ( & t , 0 L , sp , tmp ) ;
*/
result = timesub(&t, ttisp->tt_utoff, sp, tmp);
if (result)
{
result->tm_isdst = ttisp->tt_isdst;
result->tm_zone = unconstify(char *, &sp->chars[ttisp->tt_desigidx]);
}
return result;
}
struct pg_tm *
pg_localtime(const pg_time_t *timep, const pg_tz *tz)
{
return localsub(&tz->state, timep, &tm);
}
/*
* gmtsub is to gmtime as localsub is to localtime .
*
* Except we have a private " struct state " for GMT , so no sp is passed in .
*/
static struct pg_tm *
gmtsub(pg_time_t const *timep, int32 offset,
struct pg_tm *tmp)
{
struct pg_tm *result;
/* GMT timezone state data is kept here */
static struct state *gmtptr = NULL;
if (gmtptr == NULL)
{
/* Allocate on first use */
gmtptr = (struct state *) malloc(sizeof (struct state));
if (gmtptr == NULL)
return NULL; /* errno should be set by malloc */
gmtload(gmtptr);
}
result = timesub(timep, offset, gmtptr, tmp);
/*
* Could get fancy here and deliver something such as " + xx " or " - xx " if
* offset is non - zero , but this is no time for a treasure hunt .
*/
if (offset != 0 )
tmp->tm_zone = wildabbr;
else
tmp->tm_zone = gmtptr->chars;
return result;
}
struct pg_tm *
pg_gmtime(const pg_time_t *timep)
{
return gmtsub(timep, 0 , &tm);
}
/*
* 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_nonneg(int y)
{
return y / 4 - y / 100 + y / 400 ;
}
static int
leaps_thru_end_of(const int y)
{
return (y < 0
? -1 - leaps_thru_end_of_nonneg(-1 - y)
: leaps_thru_end_of_nonneg(y));
}
static struct pg_tm *
timesub(const pg_time_t *timep, int32 offset,
const struct state *sp, struct pg_tm *tmp)
{
const struct lsinfo *lp;
pg_time_t tdays;
int idays; /* unsigned would be so 2003 */
int64 rem;
int y;
const int *ip;
int64 corr;
bool hit;
int i;
corr = 0 ;
hit = false ;
i = (sp == NULL) ? 0 : sp->leapcnt;
while (--i >= 0 )
{
lp = &sp->lsis[i];
if (*timep >= lp->ls_trans)
{
corr = lp->ls_corr;
hit = (*timep == lp->ls_trans
&& (i == 0 ? 0 : lp[-1 ].ls_corr) < corr);
break ;
}
}
y = EPOCH_YEAR;
tdays = *timep / SECSPERDAY;
rem = *timep % SECSPERDAY;
while (tdays < 0 || tdays >= year_lengths[isleap(y)])
{
int newy;
pg_time_t tdelta;
int idelta;
int leapdays;
tdelta = tdays / DAYSPERLYEAR;
if (!((!TYPE_SIGNED(pg_time_t) || INT_MIN <= tdelta)
&& tdelta <= INT_MAX))
goto out_of_range;
idelta = tdelta;
if (idelta == 0 )
idelta = (tdays < 0 ) ? -1 : 1 ;
newy = y;
if (increment_overflow(&newy, idelta))
goto out_of_range;
leapdays = leaps_thru_end_of(newy - 1 ) -
leaps_thru_end_of(y - 1 );
tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR;
tdays -= leapdays;
y = newy;
}
/*
* 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 ))
goto out_of_range;
idays += year_lengths[isleap(y)];
}
while (idays >= year_lengths[isleap(y)])
{
idays -= year_lengths[isleap(y)];
if (increment_overflow(&y, 1 ))
goto out_of_range;
}
tmp->tm_year = y;
if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
goto out_of_range;
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 ;
tmp->tm_gmtoff = offset;
return tmp;
out_of_range:
errno = EOVERFLOW;
return NULL;
}
/*
* Normalize logic courtesy Paul Eggert .
*/
static bool
increment_overflow(int *ip, int j)
{
int const i = *ip;
/*----------
* If i > = 0 there can only be overflow if i + j > INT_MAX
* or if j > INT_MAX - i ; given i > = 0 , INT_MAX - i cannot overflow .
* If i < 0 there can only be overflow if i + j < INT_MIN
* or if j < INT_MIN - i ; given i < 0 , INT_MIN - i cannot overflow .
* - - - - - - - - - -
*/
if ((i >= 0 ) ? (j > INT_MAX - i) : (j < INT_MIN - i))
return true ;
*ip += j;
return false ;
}
static bool
increment_overflow_time(pg_time_t *tp, int32 j)
{
/*----------
* This is like
* ' if ( ! ( TIME_T_MIN < = * tp + j & & * tp + j < = TIME_T_MAX ) ) . . . ' ,
* except that it does the right thing even if * tp + j would overflow .
* - - - - - - - - - -
*/
if (!(j < 0
? (TYPE_SIGNED(pg_time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp)
: *tp <= TIME_T_MAX - j))
return true ;
*tp += j;
return false ;
}
static int64
leapcorr(struct state const *sp, pg_time_t t)
{
struct lsinfo const *lp;
int i;
i = sp->leapcnt;
while (--i >= 0 )
{
lp = &sp->lsis[i];
if (t >= lp->ls_trans)
return lp->ls_corr;
}
return 0 ;
}
/*
* Find the next DST transition time in the given zone after the given time
*
* * timep and * tz are input arguments , the other parameters are output values .
*
* When the function result is 1 , * boundary is set to the pg_time_t
* representation of the next DST transition time after * timep ,
* * before_gmtoff and * before_isdst are set to the GMT offset and isdst
* state prevailing just before that boundary ( in particular , the state
* prevailing at * timep ) , and * after_gmtoff and * after_isdst are set to
* the state prevailing just after that boundary .
*
* When the function result is 0 , there is no known DST transition
* after * timep , but * before_gmtoff and * before_isdst indicate the GMT
* offset and isdst state prevailing at * timep . ( This would occur in
* DST - less time zones , or if a zone has permanently ceased using DST . )
*
* A function result of - 1 indicates failure ( this case does not actually
* occur in our current implementation ) .
*/
int
pg_next_dst_boundary(const pg_time_t *timep,
long int *before_gmtoff,
int *before_isdst,
pg_time_t *boundary,
long int *after_gmtoff,
int *after_isdst,
const pg_tz *tz)
{
const struct state *sp;
const struct ttinfo *ttisp;
int i;
int j;
const pg_time_t t = *timep;
sp = &tz->state;
if (sp->timecnt == 0 )
{
/* non-DST zone, use the defaulttype */
ttisp = &sp->ttis[sp->defaulttype];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
return 0 ;
}
if ((sp->goback && t < sp->ats[0 ]) ||
(sp->goahead && t > sp->ats[sp->timecnt - 1 ]))
{
/* For values outside the transition table, extrapolate */
pg_time_t newt = t;
pg_time_t seconds;
pg_time_t tcycles;
int64 icycles;
int result;
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 -1 ;
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 -1 ; /* "cannot happen" */
result = pg_next_dst_boundary(&newt, before_gmtoff,
before_isdst,
boundary,
after_gmtoff,
after_isdst,
tz);
if (t < sp->ats[0 ])
*boundary -= seconds;
else
*boundary += seconds;
return result;
}
if (t >= sp->ats[sp->timecnt - 1 ])
{
/* No known transition > t, so use last known segment's type */
i = sp->types[sp->timecnt - 1 ];
ttisp = &sp->ttis[i];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
return 0 ;
}
if (t < sp->ats[0 ])
{
/* For "before", use the defaulttype */
ttisp = &sp->ttis[sp->defaulttype];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
*boundary = sp->ats[0 ];
/* And for "after", use the first segment's type */
i = sp->types[0 ];
ttisp = &sp->ttis[i];
*after_gmtoff = ttisp->tt_utoff;
*after_isdst = ttisp->tt_isdst;
return 1 ;
}
/* Else search to find the boundary following t */
{
int lo = 1 ;
int hi = sp->timecnt - 1 ;
while (lo < hi)
{
int mid = (lo + hi) >> 1 ;
if (t < sp->ats[mid])
hi = mid;
else
lo = mid + 1 ;
}
i = lo;
}
j = sp->types[i - 1 ];
ttisp = &sp->ttis[j];
*before_gmtoff = ttisp->tt_utoff;
*before_isdst = ttisp->tt_isdst;
*boundary = sp->ats[i];
j = sp->types[i];
ttisp = &sp->ttis[j];
*after_gmtoff = ttisp->tt_utoff;
*after_isdst = ttisp->tt_isdst;
return 1 ;
}
/*
* Identify a timezone abbreviation ' s meaning in the given zone
*
* Determine the GMT offset and DST flag associated with the abbreviation .
* This is generally used only when the abbreviation has actually changed
* meaning over time ; therefore , we also take a UTC cutoff time , and return
* the meaning in use at or most recently before that time , or the meaning
* in first use after that time if the abbrev was never used before that .
*
* On success , returns true and sets * gmtoff and * isdst . If the abbreviation
* was never used at all in this zone , returns false .
*
* Note : abbrev is matched case - sensitively ; it should be all - upper - case .
*/
bool
pg_interpret_timezone_abbrev(const char *abbrev,
const pg_time_t *timep,
long int *gmtoff,
int *isdst,
const pg_tz *tz)
{
const struct state *sp;
const char *abbrs;
const struct ttinfo *ttisp;
int abbrind;
int cutoff;
int i;
const pg_time_t t = *timep;
sp = &tz->state;
/*
* Locate the abbreviation in the zone ' s abbreviation list . We assume
* there are not duplicates in the list .
*/
abbrs = sp->chars;
abbrind = 0 ;
while (abbrind < sp->charcnt)
{
if (strcmp(abbrev, abbrs + abbrind) == 0 )
break ;
while (abbrs[abbrind] != '\0' )
abbrind++;
abbrind++;
}
if (abbrind >= sp->charcnt)
return false ; /* not there! */
/*
* Unlike pg_next_dst_boundary , we needn ' t sweat about extrapolation
* ( goback / goahead zones ) . Finding the newest or oldest meaning of the
* abbreviation should get us what we want , since extrapolation would just
* be repeating the newest or oldest meanings .
*
* Use binary search to locate the first transition > cutoff time . ( Note
* that sp - > timecnt could be zero , in which case this loop does nothing
* and only the defaulttype entry will be checked . )
*/
{
int lo = 0 ;
int hi = sp->timecnt;
while (lo < hi)
{
int mid = (lo + hi) >> 1 ;
if (t < sp->ats[mid])
hi = mid;
else
lo = mid + 1 ;
}
cutoff = lo;
}
/*
* Scan backwards to find the latest interval using the given abbrev
* before the cutoff time .
*/
for (i = cutoff - 1 ; i >= 0 ; i--)
{
ttisp = &sp->ttis[sp->types[i]];
if (ttisp->tt_desigidx == abbrind)
{
*gmtoff = ttisp->tt_utoff;
*isdst = ttisp->tt_isdst;
return true ;
}
}
/*
* Not found yet ; check the defaulttype , which is notionally the era
* before any of the entries in sp - > types [ ] .
*/
ttisp = &sp->ttis[sp->defaulttype];
if (ttisp->tt_desigidx == abbrind)
{
*gmtoff = ttisp->tt_utoff;
*isdst = ttisp->tt_isdst;
return true ;
}
/*
* Not there , so scan forwards to find the first one after the cutoff .
*/
for (i = cutoff; i < sp->timecnt; i++)
{
ttisp = &sp->ttis[sp->types[i]];
if (ttisp->tt_desigidx == abbrind)
{
*gmtoff = ttisp->tt_utoff;
*isdst = ttisp->tt_isdst;
return true ;
}
}
return false ; /* hm, not actually used in any interval? */
}
/*
* Detect whether a timezone abbreviation is defined within the given zone .
*
* This is similar to pg_interpret_timezone_abbrev ( ) but is not concerned
* with a specific point in time . We want to know if the abbreviation is
* known at all , and if so whether it has one meaning or several .
*
* Returns true if the abbreviation is known , false if not .
* If the abbreviation is known and has a single meaning ( only one value
* of gmtoff / isdst ) , sets * isfixed = true and sets * gmtoff and * isdst .
* If there are multiple meanings , sets * isfixed = false .
*
* Note : abbrev is matched case - sensitively ; it should be all - upper - case .
*/
bool
pg_timezone_abbrev_is_known(const char *abbrev,
bool *isfixed,
long int *gmtoff,
int *isdst,
const pg_tz *tz)
{
bool result = false ;
const struct state *sp = &tz->state;
const char *abbrs;
int abbrind;
/*
* Locate the abbreviation in the zone ' s abbreviation list . We assume
* there are not duplicates in the list .
*/
abbrs = sp->chars;
abbrind = 0 ;
while (abbrind < sp->charcnt)
{
if (strcmp(abbrev, abbrs + abbrind) == 0 )
break ;
while (abbrs[abbrind] != '\0' )
abbrind++;
abbrind++;
}
if (abbrind >= sp->charcnt)
return false ; /* definitely not there */
/*
* Scan the ttinfo array to find uses of the abbreviation .
*/
for (int i = 0 ; i < sp->typecnt; i++)
{
const struct ttinfo *ttisp = &sp->ttis[i];
if (ttisp->tt_desigidx == abbrind)
{
if (!result)
{
/* First usage */
*isfixed = true ; /* for the moment */
*gmtoff = ttisp->tt_utoff;
*isdst = ttisp->tt_isdst;
result = true ;
}
else
{
/* Second or later usage, does it match? */
if (*gmtoff != ttisp->tt_utoff ||
*isdst != ttisp->tt_isdst)
{
*isfixed = false ;
break ; /* no point in looking further */
}
}
}
}
return result;
}
/*
* Iteratively fetch all the abbreviations used in the given time zone .
*
* * indx is a state counter that the caller must initialize to zero
* before the first call , and not touch between calls .
*
* Returns the next known abbreviation , or NULL if there are no more .
*
* Note : the caller typically applies pg_interpret_timezone_abbrev ( )
* to each result . While that nominally results in O ( N ^ 2 ) time spent
* searching the sp - > chars [ ] array , we don ' t expect any zone to have
* enough abbreviations to make that meaningful .
*/
const char *
pg_get_next_timezone_abbrev(int *indx,
const pg_tz *tz)
{
const char *result;
const struct state *sp = &tz->state;
const char *abbrs;
int abbrind;
/* If we're still in range, the result is the current abbrev. */
abbrs = sp->chars;
abbrind = *indx;
if (abbrind < 0 || abbrind >= sp->charcnt)
return NULL;
result = abbrs + abbrind;
/* Advance *indx past this abbrev and its trailing null. */
while (abbrs[abbrind] != '\0' )
abbrind++;
abbrind++;
*indx = abbrind;
return result;
}
/*
* If the given timezone uses only one GMT offset , store that offset
* into * gmtoff and return true , else return false .
*/
bool
pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff)
{
/*
* The zone could have more than one ttinfo , if it ' s historically used
* more than one abbreviation . We return true as long as they all have
* the same gmtoff .
*/
const struct state *sp;
int i;
sp = &tz->state;
for (i = 1 ; i < sp->typecnt; i++)
{
if (sp->ttis[i].tt_utoff != sp->ttis[0 ].tt_utoff)
return false ;
}
*gmtoff = sp->ttis[0 ].tt_utoff;
return true ;
}
/*
* Return the name of the current timezone
*/
const char *
pg_get_timezone_name(pg_tz *tz)
{
if (tz)
return tz->TZname;
return NULL;
}
/*
* Check whether timezone is acceptable .
*
* What we are doing here is checking for leap - second - aware timekeeping .
* We need to reject such TZ settings because they ' ll wreak havoc with our
* date / time arithmetic .
*/
bool
pg_tz_acceptable(pg_tz *tz)
{
struct pg_tm *tt;
pg_time_t time2000;
/*
* To detect leap - second timekeeping , run pg_localtime for what should be
* GMT midnight , 2000 - 01 - 01 . Insist that the tm_sec value be zero ; any
* other result has to be due to leap seconds .
*/
time2000 = (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY;
tt = pg_localtime(&time2000, tz);
if (!tt || tt->tm_sec != 0 )
return false ;
return true ;
}
Messung V0.5 in Prozent C=93 H=86 G=89
¤ Dauer der Verarbeitung: 0.42 Sekunden
(vorverarbeitet am 2026-08-04)
¤
*© Formatika GbR, Deutschland
2026-08-25
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=277311
#Domains=752002