1/*-------------------------------------------------------------------------
4 * Timezone Library Integration Functions
6 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
11 *-------------------------------------------------------------------------
27/* Current session timezone (controlled by TimeZone GUC) */
30/* Current log timezone (controlled by log_timezone GUC) */
35 const char *fname,
int fnamelen,
36 char *canonname,
int canonnamelen);
40 * Return full pathname of timezone data directory
46 /* normal case: timezone stuff is under our share dir */
47 static bool done_tzdir =
false;
59 /* we're configured to use system's timezone database */
66 * Given a timezone name, open() the timezone data file. Return the
67 * file descriptor if successful, -1 if not.
69 * The input name is searched for case-insensitively (we assume that the
70 * timezone database does not contain case-equivalent names).
72 * If "canonname" is not NULL, then on success the canonical spelling of the
73 * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!).
83 /* Initialize fullname with base name of tzdata directory */
85 orignamelen = fullnamelen = strlen(fullname);
88 return -1;
/* not gonna fit */
91 * If the caller doesn't need the canonical spelling, first just try to
92 * open the name as-is. This can be expected to succeed if the given name
93 * is already case-correct, or if the filesystem is case-insensitive; and
94 * we don't need to distinguish those situations if we aren't tasked with
95 * reporting the canonical spelling.
97 if (canonname == NULL)
101 fullname[fullnamelen] =
'/';
102 /* test above ensured this will fit: */
103 strcpy(fullname + fullnamelen + 1,
name);
104 result = open(fullname, O_RDONLY |
PG_BINARY, 0);
107 /* If that didn't work, fall through to do it the hard way */
108 fullname[fullnamelen] =
'0円';
112 * Loop to split the given name into directory levels; for each level,
113 * search using scan_directory_ci().
118 const char *slashptr;
121 slashptr = strchr(fname,
'/');
123 fnamelen = slashptr - fname;
125 fnamelen = strlen(fname);
127 fullname + fullnamelen + 1,
130 fullname[fullnamelen++] =
'/';
131 fullnamelen += strlen(fullname + fullnamelen);
133 fname = slashptr + 1;
141 return open(fullname, O_RDONLY |
PG_BINARY, 0);
146 * Scan specified directory for a case-insensitive match to fname
147 * (of length fnamelen --- fname may not be null terminated!). If found,
148 * copy the actual filename into canonname and return true.
152 char *canonname,
int canonnamelen)
163 * Ignore . and .., plus any other "hidden" files. This is a security
164 * measure to prevent access to files outside the timezone directory.
166 if (direntry->
d_name[0] ==
'.')
169 if (strlen(direntry->
d_name) == fnamelen &&
172 /* Found our match */
186 * We keep loaded timezones in a hashtable so we don't have to
187 * load and parse the TZ definition file every time one is selected.
188 * Because we want timezone names to be found case-insensitively,
189 * the hash key is the uppercased name of the zone.
193 /* tznameupper contains the all-upper-case name of the timezone */
220 * Load a timezone from file or from cache.
221 * Does not verify that the timezone is acceptable!
223 * "GMT" is always interpreted as the tzparse() definition, without attempting
224 * to load a definition from the filesystem. This has a number of benefits:
225 * 1. It's guaranteed to succeed, so we don't have the failure mode wherein
226 * the bootstrap default timezone setting doesn't work (as could happen if
227 * the OS attempts to supply a leap-second-aware version of "GMT").
228 * 2. Because we aren't accessing the filesystem, we can safely initialize
229 * the "GMT" zone definition before my_exec_path is known.
230 * 3. It's quick enough that we don't waste much time when the bootstrap
231 * default timezone setting is later overridden from postgresql.conf.
237 struct state tzstate;
243 return NULL;
/* not going to fit */
250 * Upcase the given name to perform a case-insensitive hashtable search.
251 * (We could alternatively downcase it, but we prefer upcase so that we
252 * can get consistently upcased results from tzparse() in case the name is
253 * a POSIX-style timezone spec.)
266 /* Timezone found in cache, nothing more to do */
271 * "GMT" is always sent to tzparse(), as per discussion above.
273 if (strcmp(uppername,
"GMT") == 0)
275 if (!
tzparse(uppername, &tzstate,
true))
277 /* This really, really should not happen ... */
278 elog(
ERROR,
"could not initialize GMT time zone");
280 /* Use uppercase name as canonical */
281 strcpy(canonname, uppername);
283 else if (
tzload(uppername, canonname, &tzstate,
true) != 0)
285 if (uppername[0] ==
':' || !
tzparse(uppername, &tzstate,
false))
287 /* Unknown timezone. Fail our call instead of loading GMT! */
290 /* For POSIX timezone specs, use uppercase name as canonical */
291 strcpy(canonname, uppername);
294 /* Save timezone in the cache */
300 /* hash_search already copied uppername into the hash key */
302 memcpy(&tzp->
tz.
state, &tzstate,
sizeof(tzstate));
308 * Load a fixed-GMT-offset timezone.
309 * This is used for SQL-spec SET TIME ZONE INTERVAL 'foo' cases.
310 * It's otherwise equivalent to pg_tzset().
312 * The GMT offset is specified in seconds, positive values meaning west of
313 * Greenwich (ie, POSIX not ISO sign convention). However, we use ISO
314 * sign convention in the displayable abbreviation for the zone.
316 * Caution: this can fail (return NULL) if the specified offset is outside
317 * the range allowed by the zic library.
322 long absoffset = (gmtoffset < 0) ? -gmtoffset : gmtoffset;
326 snprintf(offsetstr,
sizeof(offsetstr),
331 snprintf(offsetstr + strlen(offsetstr),
332 sizeof(offsetstr) - strlen(offsetstr),
336 snprintf(offsetstr + strlen(offsetstr),
337 sizeof(offsetstr) - strlen(offsetstr),
338 ":%02ld", absoffset);
341 snprintf(tzname,
sizeof(tzname),
"<-%s>+%s",
342 offsetstr, offsetstr);
344 snprintf(tzname,
sizeof(tzname),
"<+%s>-%s",
345 offsetstr, offsetstr);
352 * Initialize timezone library
354 * This is called before GUC variable initialization begins. Its purpose
355 * is to ensure that log_timezone has a valid value before any logging GUC
356 * variables could become set to values that require elog.c to provide
357 * timestamps (e.g., log_line_prefix). We may as well initialize
358 * session_timezone to something valid, too.
364 * We may not yet know where PGSHAREDIR is (in particular this is true in
365 * an EXEC_BACKEND subprocess). So use "GMT", which pg_tzset forces to be
366 * interpreted without reference to the filesystem. This corresponds to
367 * the bootstrap default for these variables in guc_parameters.dat,
368 * although in principle it could be different.
376 * Functions to enumerate available timezones
378 * Note that pg_tzenumerate_next() will return a pointer into the pg_tzenum
379 * structure, so the data is only valid up to the next call.
381 * All data is allocated using palloc in the current context.
383 #define MAX_TZDIR_DEPTH 10
394/* typedef pg_tzenum is declared in pgtime.h */
402 ret->
baselen = strlen(startdir) + 1;
409 errmsg(
"could not open directory \"%s\": %m", startdir)));
416 while (dir->
depth >= 0)
428 while (dir->
depth >= 0)
437 /* End of this directory */
444 if (direntry->
d_name[0] ==
'.')
447 snprintf(fullname,
sizeof(fullname),
"%s/%s",
452 /* Step into the subdirectory */
462 errmsg(
"could not open directory \"%s\": %m",
465 /* Start over reading in the new directory */
470 * Load this timezone using tzload() not pg_tzset(), so we don't fill
471 * the cache. Also, don't ask for the canonical spelling: we already
472 * know it, and pg_open_tzfile's way of finding it out is pretty
477 /* Zone could not be loaded, ignore it */
483 /* Ignore leap-second zones */
487 /* OK, return the canonical zone name spelling. */
491 /* Timezone loaded OK. */
495 /* Nothing more found */
void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr)
HTAB * hash_create(const char *tabname, int64 nelem, const HASHCTL *info, int flags)
int errmsg_internal(const char *fmt,...)
int errcode_for_file_access(void)
int errmsg(const char *fmt,...)
#define ereport(elevel,...)
struct dirent * ReadDirExtended(DIR *dir, const char *dirname, int elevel)
DIR * AllocateDir(const char *dirname)
struct dirent * ReadDir(DIR *dir, const char *dirname)
PGFileType get_dirent_type(const char *path, const struct dirent *de, bool look_through_symlinks, int elevel)
char my_exec_path[MAXPGPATH]
int tzload(const char *name, char *canonname, struct state *sp, bool doextend)
bool tzparse(const char *name, struct state *sp, bool lastditch)
char * pstrdup(const char *in)
void pfree(void *pointer)
void * palloc0(Size size)
bool pg_tz_acceptable(pg_tz *tz)
pg_tz * pg_tzset_offset(long gmtoffset)
static HTAB * timezone_cache
pg_tz * pg_tzenumerate_next(pg_tzenum *dir)
void pg_timezone_initialize(void)
pg_tz * pg_tzset(const char *tzname)
static const char * pg_TZDIR(void)
int pg_open_tzfile(const char *name, char *canonname)
static bool scan_directory_ci(const char *dirname, const char *fname, int fnamelen, char *canonname, int canonnamelen)
static bool init_timezone_hashtable(void)
void pg_tzenumerate_end(pg_tzenum *dir)
pg_tzenum * pg_tzenumerate_start(void)
void get_share_path(const char *my_exec_path, char *ret_path)
unsigned char pg_toupper(unsigned char ch)
size_t strlcpy(char *dst, const char *src, size_t siz)
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
char TZname[TZ_STRLEN_MAX+1]
char * dirname[MAX_TZDIR_DEPTH]
DIR * dirdesc[MAX_TZDIR_DEPTH]