MDEV-32189 Use icu for timezones on windows

Use ICU to work with timezones, to retrieve current timezone name,
abbreviation, and offset from GMT. However in case TZ environment variable
is used to set timezone, and ICU does not have corresponding one,
C runtime functions will be used.

Moved some of timezone handling to mysys.
Added unit tests.
This commit is contained in:
Vladislav Vaintroub 2023-09-29 12:37:10 +02:00
parent bb8e1bf7a2
commit 3424ed7d42
12 changed files with 454 additions and 266 deletions

View file

@ -1076,6 +1076,18 @@ static inline void my_uuid2str(const uchar *guid, char *s, int with_separators)
const char *my_dlerror(const char *dlpath);
/* System timezone handling*/
void my_tzset();
void my_tzname(char *sys_timezone, size_t size);
struct my_tz
{
long seconds_offset;
char abbreviation[64];
};
void my_tzinfo(time_t t, struct my_tz*);
/* character sets */
extern void my_charset_loader_init_mysys(MY_CHARSET_LOADER *loader);
extern uint get_charset_number(const char *cs_name, uint cs_flags, myf flags);

View file

@ -45,7 +45,8 @@ SET(MYSYS_SOURCES array.c charset-def.c charset.c my_default.c
my_uuid.c wqueue.c waiting_threads.c ma_dyncol.c ../sql-common/my_time.c
my_rdtsc.c psi_noop.c
my_atomic_writes.c my_cpu.c my_likely.c my_largepage.c
file_logger.c my_dlerror.c crc32/crc32c.cc)
file_logger.c my_dlerror.c crc32/crc32c.cc
my_timezone.cc)
IF (WIN32)
SET (MYSYS_SOURCES ${MYSYS_SOURCES}
@ -170,7 +171,9 @@ MAYBE_DISABLE_IPO(mysys)
TARGET_LINK_LIBRARIES(mysys dbug strings ${ZLIB_LIBRARY}
${LIBNSL} ${LIBM} ${LIBRT} ${CMAKE_DL_LIBS} ${LIBSOCKET} ${LIBEXECINFO})
DTRACE_INSTRUMENT(mysys)
IF(WIN32)
TARGET_LINK_LIBRARIES(mysys icuuc icuin)
ENDIF()
IF (HAVE_GCC_C11_ATOMICS_WITH_LIBATOMIC)
TARGET_LINK_LIBRARIES(mysys atomic)
ENDIF()

219
mysys/my_timezone.cc Normal file
View file

@ -0,0 +1,219 @@
/*
Copyright (c) 2023 MariaDB
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; version 2 of the License.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
#include <config.h>
#include <my_global.h>
#include <my_sys.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
#include <icu.h>
#include <objbase.h>
#define MAX_TZ_ABBR 64
static bool use_icu_for_tzinfo;
/*
Retrieve GMT offset and timezone abbreviation using ICU.
*/
static void icu_get_tzinfo(time_t t, my_tz* tz)
{
UErrorCode status= U_ZERO_ERROR;
const char *locale= nullptr;
UCalendar* cal= ucal_open(nullptr, -1, locale, UCAL_GREGORIAN, &status);
ucal_setMillis(cal, 1000.0 * t, &status);
int32_t zone_offset= ucal_get(cal, UCAL_ZONE_OFFSET, &status);
int32_t dst_offset= ucal_get(cal, UCAL_DST_OFFSET, &status);
tz->seconds_offset= (zone_offset + dst_offset) / 1000;
UChar u_tz_abbr[MAX_TZ_ABBR];
ucal_getTimeZoneDisplayName(cal,
dst_offset ? UCAL_SHORT_DST : UCAL_SHORT_STANDARD,
locale, u_tz_abbr, MAX_TZ_ABBR, &status);
ucal_close(cal);
size_t num;
wcstombs_s(&num, tz->abbreviation, sizeof(tz->abbreviation),
(wchar_t *) u_tz_abbr, sizeof(u_tz_abbr));
}
/*
Return GMT offset and TZ abbreviation using Windows C runtime.
Only used if TZ environment variable is set, and there is no
corresponding timezone in ICU data.
*/
static void win_get_tzinfo(time_t t, my_tz* tz)
{
struct tm local_time;
localtime_r(&t, &local_time);
int is_dst= local_time.tm_isdst?1:0;
tz->seconds_offset= (long)(_mkgmtime(&local_time) - t);
snprintf(tz->abbreviation, sizeof(tz->abbreviation), "%s", _tzname[is_dst]);
}
#define MAX_TIMEZONE_LEN 128
/*
Synchronizes C runtime timezone with ICU timezone.
Must be called after tzset().
If TZ environment variable is set, tries to find ICU
timezone matching the variable value.If such timezone
is found, it is set as default timezone for ICU.
@return 0 success
-1 otherwise
*/
static int sync_icu_timezone()
{
const char *tz_env= getenv("TZ");
UErrorCode ec= U_ZERO_ERROR;
UEnumeration *en= nullptr;
int ret= -1;
if (!tz_env)
{
/* TZ environment variable not set - use default timezone*/
return 0;
}
int timezone_offset_ms = -1000 * _timezone;
int dst_offset_ms = -1000 * _dstbias *(_daylight != 0);
/*
Find ICU timezone with the same UTC and DST offsets
and name as C runtime.
*/
en= ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_ANY, nullptr,
&timezone_offset_ms, &ec);
if (U_FAILURE(ec))
return -1;
for (;;)
{
int32_t len;
const char *tzid= uenum_next(en, &len, &ec);
if (U_FAILURE(ec))
break;
if (!tzid)
break;
UChar u_tzid[MAX_TIMEZONE_LEN];
u_uastrncpy(u_tzid, tzid, MAX_TIMEZONE_LEN);
int32_t dst_savings= ucal_getDSTSavings(u_tzid, &ec);
if (U_FAILURE(ec))
break;
if (dst_savings == dst_offset_ms)
{
if (tz_env && !strcmp(tzid, tz_env))
{
/*
Found timezone ID that matches TZ env.var
exactly, e.g PST8PDT
*/
UChar u_tzid[MAX_TIMEZONE_LEN];
u_uastrncpy(u_tzid, tzid, MAX_TIMEZONE_LEN);
ucal_setDefaultTimeZone(u_tzid, &ec);
ret= 0;
break;
}
}
}
uenum_close(en);
return ret;
}
#endif /* _WIN32 */
/**
Initialize time conversion information
@param[out] sys_timezone name of the current timezone
@param[in] sys_timezone_len length of the 'sys_timezone' buffer
*/
extern "C" void my_tzset()
{
tzset();
#ifdef _WIN32
/*
CoInitializeEx is needed by ICU on older Windows 10, until
version 1903.
*/
(void) CoInitializeEx(NULL, COINITBASE_MULTITHREADED);
use_icu_for_tzinfo= !sync_icu_timezone();
#endif
}
/**
Retrieve current timezone name
@param[out] sys_timezone - buffer to receive timezone name
@param[in] size - size of sys_timezone buffer
*/
extern "C" void my_tzname(char* sys_timezone, size_t size)
{
#ifdef _WIN32
if (use_icu_for_tzinfo)
{
/* TZ environment variable not set - return default timezone name*/
UChar default_tzname[MAX_TIMEZONE_LEN];
UErrorCode ec;
int32_t len=
ucal_getDefaultTimeZone(default_tzname, MAX_TIMEZONE_LEN, &ec);
if (U_SUCCESS(ec))
{
u_austrncpy(sys_timezone, default_tzname, (int32_t) size);
return;
}
use_icu_for_tzinfo= false;
}
#endif
struct tm tm;
time_t t;
tzset();
t= time(NULL);
localtime_r(&t, &tm);
const char *tz_name= tzname[tm.tm_isdst != 0 ? 1 : 0];
snprintf(sys_timezone, size, "%s", tz_name);
}
/**
Return timezone information (GMT offset, timezone abbreviation)
corresponding to specific timestamp.
@param[in] t timestamp (seconds since Unix epoch)
@param[out] gmt_offset offset from GMT, in seconds
@param[out] abbr buffer to receive time zone abbreviation
@param[in] abbr_size size of the abbr buffer
*/
void my_tzinfo(time_t t, struct my_tz* tz)
{
#ifdef _WIN32
if (use_icu_for_tzinfo)
icu_get_tzinfo(t, tz);
else
win_get_tzinfo(t, tz);
#else
struct tm tm_local_time;
localtime_r(&t, &tm_local_time);
snprintf(tz->abbreviation, sizeof(tz->abbreviation), "%s", tm_local_time.tm_zone);
tz->seconds_offset= tm_local_time.tm_gmtoff;
#endif
}

View file

@ -229,7 +229,6 @@ TARGET_LINK_LIBRARIES(sql
${LIBWRAP} ${LIBCRYPT} ${CMAKE_DL_LIBS} ${CMAKE_THREAD_LIBS_INIT}
${SSL_LIBRARIES}
${LIBSYSTEMD})
IF(TARGET pcre2)
ADD_DEPENDENCIES(sql pcre2)
ENDIF()
@ -293,6 +292,7 @@ IF(MSVC OR CMAKE_SYSTEM_NAME MATCHES AIX)
sql_builtins
)
IF(MSVC)
TARGET_LINK_LIBRARIES(server PRIVATE icuuc icuin)
IF(NOT WITHOUT_DYNAMIC_PLUGINS)
SET_TARGET_PROPERTIES(server PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
ENDIF()

View file

@ -1,12 +0,0 @@
# Generates a header file for converting between Windows timezone names to tzdb names
# using CLDR data.
# Usage: powershell -File gen_win_tzname_data.ps1 > win_tzname_data.h
write-output "/* This file was generated using gen_win_tzname_data.ps1 */"
$xdoc = new-object System.Xml.XmlDocument
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$xdoc.load("https://raw.githubusercontent.com/unicode-org/cldr/master/common/supplemental/windowsZones.xml")
$nodes = $xdoc.SelectNodes("//mapZone[@territory='001']") # use default territory (001)
foreach ($node in $nodes) {
write-output ('{L"'+ $node.other + '","'+ $node.type+'"},')
}

View file

@ -482,7 +482,7 @@ static bool make_date_time(THD *thd, const String *format,
uint weekday;
ulong length;
const char *ptr, *end;
struct tz curr_tz;
struct my_tz curr_tz;
Time_zone* curr_timezone= 0;
str->length(0);
@ -725,7 +725,7 @@ static bool make_date_time(THD *thd, const String *format,
curr_timezone= thd->variables.time_zone;
curr_timezone->get_timezone_information(&curr_tz, l_time);
}
str->append(curr_tz.abbrevation, strlen(curr_tz.abbrevation));
str->append(curr_tz.abbreviation, strlen(curr_tz.abbreviation));
break;
default:
str->append(*ptr);

View file

@ -3867,40 +3867,6 @@ static int init_early_variables()
return 0;
}
#ifdef _WIN32
static void get_win_tzname(char* buf, size_t size)
{
static struct
{
const wchar_t* windows_name;
const char* tzdb_name;
}
tz_data[] =
{
#include "win_tzname_data.h"
{0,0}
};
DYNAMIC_TIME_ZONE_INFORMATION tzinfo;
if (GetDynamicTimeZoneInformation(&tzinfo) == TIME_ZONE_ID_INVALID)
{
strncpy(buf, "unknown", size);
return;
}
for (size_t i= 0; tz_data[i].windows_name; i++)
{
if (wcscmp(tzinfo.TimeZoneKeyName, tz_data[i].windows_name) == 0)
{
strncpy(buf, tz_data[i].tzdb_name, size);
return;
}
}
wcstombs(buf, tzinfo.TimeZoneKeyName, size);
buf[size-1]= 0;
return;
}
#endif
static int init_common_variables()
{
umask(((~my_umask) & 0666));
@ -3958,21 +3924,8 @@ static int init_common_variables()
struct tm tm_tmp;
localtime_r(&server_start_time, &tm_tmp);
#ifdef HAVE_TZNAME
#ifdef _WIN32
/*
If env.variable TZ is set, derive timezone name from it.
Otherwise, use IANA tz name from get_win_tzname.
*/
if (!getenv("TZ"))
get_win_tzname(system_time_zone, sizeof(system_time_zone));
else
#endif
{
const char *tz_name= tzname[tm_tmp.tm_isdst != 0 ? 1 : 0];
strmake_buf(system_time_zone, tz_name);
}
#endif
my_tzset();
my_tzname(system_time_zone, sizeof(system_time_zone));
/*
We set SYSTEM time zone as reasonable default and

View file

@ -1007,7 +1007,7 @@ public:
virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
virtual const String * get_name() const;
virtual void get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const;
virtual void get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const;
};
@ -1074,48 +1074,13 @@ Time_zone_system::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
adjust_leap_second(tmp);
}
void
Time_zone_system::get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const
Time_zone_system::get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const
{
#ifndef _WIN32
uint error;
struct tm tm_local_time;
time_t time_sec= TIME_to_gmt_sec(local_TIME, &error);
localtime_r(&time_sec, &tm_local_time);
strmake_buf(curr_tz->abbrevation, tm_local_time.tm_zone);
curr_tz->seconds_offset= tm_local_time.tm_gmtoff;
#else
#define mdHMS(mon,day,h,m,s) (((((mon)*100+(day))*100+(h))*100+(m))*100+(s))
TIME_ZONE_INFORMATION tzi;
bool use_dst= false;
GetTimeZoneInformationForYear(local_TIME->year, NULL, &tzi);
if (tzi.StandardDate.wMonth)
{
ulonglong std= mdHMS(tzi.StandardDate.wMonth, tzi.StandardDate.wDay,
tzi.StandardDate.wHour, tzi.StandardDate.wMinute,
tzi.StandardDate.wSecond);
ulonglong dst= mdHMS(tzi.DaylightDate.wMonth, tzi.DaylightDate.wDay,
tzi.DaylightDate.wHour, tzi.DaylightDate.wMinute,
tzi.DaylightDate.wSecond);
ulonglong cur= mdHMS(local_TIME->month, local_TIME->day, local_TIME->hour,
local_TIME->minute, local_TIME->second);
use_dst= std < dst ? cur < std || cur >= dst : cur >= dst && cur < std;
}
if (use_dst)
{
size_t len;
curr_tz->seconds_offset= -60 * (tzi.Bias + tzi.DaylightBias);
wcstombs_s(&len, curr_tz->abbrevation, sizeof(curr_tz->abbrevation),
tzi.DaylightName, _TRUNCATE);
}
else
{
size_t len;
curr_tz->seconds_offset= -60 * (tzi.Bias + tzi.StandardBias);
wcstombs_s(&len, curr_tz->abbrevation, sizeof(curr_tz->abbrevation),
tzi.StandardName, _TRUNCATE);
}
#endif
time_t time_sec= TIME_to_gmt_sec(local_TIME, &error);
my_tzinfo(time_sec, curr_tz);
}
@ -1149,7 +1114,7 @@ public:
uint *error_code) const;
virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
virtual const String * get_name() const;
virtual void get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const;
virtual void get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const;
};
@ -1198,9 +1163,9 @@ Time_zone_utc::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
}
void
Time_zone_utc::get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const
Time_zone_utc::get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const
{
strmake_buf(curr_tz->abbrevation, "UTC");
strmake_buf(curr_tz->abbreviation, "UTC");
curr_tz->seconds_offset= 0;
}
@ -1239,7 +1204,7 @@ public:
virtual my_time_t TIME_to_gmt_sec(const MYSQL_TIME *t, uint *error_code) const;
virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
virtual const String * get_name() const;
virtual void get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const;
virtual void get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const;
private:
TIME_ZONE_INFO *tz_info;
const String *tz_name;
@ -1311,7 +1276,7 @@ Time_zone_db::gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const
}
void
Time_zone_db::get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const
Time_zone_db::get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const
{
uint error;
my_time_t sec_in_utc;
@ -1323,7 +1288,7 @@ Time_zone_db::get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *loc
ttisp= find_transition_type(sec_in_utc, tz_info);
curr_tz->seconds_offset= ttisp->tt_gmtoff;
strmake_buf(curr_tz->abbrevation, &(tz_info->chars[ttisp->tt_abbrind]));
strmake_buf(curr_tz->abbreviation, &(tz_info->chars[ttisp->tt_abbrind]));
}
@ -1355,7 +1320,7 @@ public:
uint *error_code) const;
virtual void gmt_sec_to_TIME(MYSQL_TIME *tmp, my_time_t t) const;
virtual const String * get_name() const;
virtual void get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const;
virtual void get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const;
/*
This have to be public because we want to be able to access it from
my_offset_tzs_get_key() function
@ -1482,11 +1447,11 @@ Time_zone_offset::get_name() const
}
void
Time_zone_offset::get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const
Time_zone_offset::get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const
{
curr_tz->seconds_offset= offset;
const char *name= get_name()->ptr();
strmake_buf(curr_tz->abbrevation, name);
strmake_buf(curr_tz->abbreviation, name);
}

View file

@ -32,15 +32,6 @@ class THD;
class THD;
/*
Has only offset from UTC and abbrevation.
*/
struct tz
{
long seconds_offset;
char abbrevation[64];
};
/**
This class represents abstract time zone and provides
basic interface for MYSQL_TIME <-> my_time_t conversion.
@ -72,7 +63,7 @@ public:
*/
virtual const String * get_name() const = 0;
virtual void get_timezone_information(struct tz* curr_tz, const MYSQL_TIME *local_TIME) const = 0;
virtual void get_timezone_information(struct my_tz* curr_tz, const MYSQL_TIME *local_TIME) const = 0;
/**
We need this only for surpressing warnings, objects of this type are

View file

@ -1,140 +0,0 @@
/* This file was generated using gen_win_tzname_data.ps1 */
{L"Dateline Standard Time","Etc/GMT+12"},
{L"UTC-11","Etc/GMT+11"},
{L"Aleutian Standard Time","America/Adak"},
{L"Hawaiian Standard Time","Pacific/Honolulu"},
{L"Marquesas Standard Time","Pacific/Marquesas"},
{L"Alaskan Standard Time","America/Anchorage"},
{L"UTC-09","Etc/GMT+9"},
{L"Pacific Standard Time (Mexico)","America/Tijuana"},
{L"UTC-08","Etc/GMT+8"},
{L"Pacific Standard Time","America/Los_Angeles"},
{L"US Mountain Standard Time","America/Phoenix"},
{L"Mountain Standard Time (Mexico)","America/Chihuahua"},
{L"Mountain Standard Time","America/Denver"},
{L"Yukon Standard Time","America/Whitehorse"},
{L"Central America Standard Time","America/Guatemala"},
{L"Central Standard Time","America/Chicago"},
{L"Easter Island Standard Time","Pacific/Easter"},
{L"Central Standard Time (Mexico)","America/Mexico_City"},
{L"Canada Central Standard Time","America/Regina"},
{L"SA Pacific Standard Time","America/Bogota"},
{L"Eastern Standard Time (Mexico)","America/Cancun"},
{L"Eastern Standard Time","America/New_York"},
{L"Haiti Standard Time","America/Port-au-Prince"},
{L"Cuba Standard Time","America/Havana"},
{L"US Eastern Standard Time","America/Indianapolis"},
{L"Turks And Caicos Standard Time","America/Grand_Turk"},
{L"Paraguay Standard Time","America/Asuncion"},
{L"Atlantic Standard Time","America/Halifax"},
{L"Venezuela Standard Time","America/Caracas"},
{L"Central Brazilian Standard Time","America/Cuiaba"},
{L"SA Western Standard Time","America/La_Paz"},
{L"Pacific SA Standard Time","America/Santiago"},
{L"Newfoundland Standard Time","America/St_Johns"},
{L"Tocantins Standard Time","America/Araguaina"},
{L"E. South America Standard Time","America/Sao_Paulo"},
{L"SA Eastern Standard Time","America/Cayenne"},
{L"Argentina Standard Time","America/Buenos_Aires"},
{L"Greenland Standard Time","America/Godthab"},
{L"Montevideo Standard Time","America/Montevideo"},
{L"Magallanes Standard Time","America/Punta_Arenas"},
{L"Saint Pierre Standard Time","America/Miquelon"},
{L"Bahia Standard Time","America/Bahia"},
{L"UTC-02","Etc/GMT+2"},
{L"Azores Standard Time","Atlantic/Azores"},
{L"Cape Verde Standard Time","Atlantic/Cape_Verde"},
{L"UTC","Etc/UTC"},
{L"GMT Standard Time","Europe/London"},
{L"Greenwich Standard Time","Atlantic/Reykjavik"},
{L"Sao Tome Standard Time","Africa/Sao_Tome"},
{L"Morocco Standard Time","Africa/Casablanca"},
{L"W. Europe Standard Time","Europe/Berlin"},
{L"Central Europe Standard Time","Europe/Budapest"},
{L"Romance Standard Time","Europe/Paris"},
{L"Central European Standard Time","Europe/Warsaw"},
{L"W. Central Africa Standard Time","Africa/Lagos"},
{L"Jordan Standard Time","Asia/Amman"},
{L"GTB Standard Time","Europe/Bucharest"},
{L"Middle East Standard Time","Asia/Beirut"},
{L"Egypt Standard Time","Africa/Cairo"},
{L"E. Europe Standard Time","Europe/Chisinau"},
{L"Syria Standard Time","Asia/Damascus"},
{L"West Bank Standard Time","Asia/Hebron"},
{L"South Africa Standard Time","Africa/Johannesburg"},
{L"FLE Standard Time","Europe/Kiev"},
{L"Israel Standard Time","Asia/Jerusalem"},
{L"South Sudan Standard Time","Africa/Juba"},
{L"Kaliningrad Standard Time","Europe/Kaliningrad"},
{L"Sudan Standard Time","Africa/Khartoum"},
{L"Libya Standard Time","Africa/Tripoli"},
{L"Namibia Standard Time","Africa/Windhoek"},
{L"Arabic Standard Time","Asia/Baghdad"},
{L"Turkey Standard Time","Europe/Istanbul"},
{L"Arab Standard Time","Asia/Riyadh"},
{L"Belarus Standard Time","Europe/Minsk"},
{L"Russian Standard Time","Europe/Moscow"},
{L"E. Africa Standard Time","Africa/Nairobi"},
{L"Iran Standard Time","Asia/Tehran"},
{L"Arabian Standard Time","Asia/Dubai"},
{L"Astrakhan Standard Time","Europe/Astrakhan"},
{L"Azerbaijan Standard Time","Asia/Baku"},
{L"Russia Time Zone 3","Europe/Samara"},
{L"Mauritius Standard Time","Indian/Mauritius"},
{L"Saratov Standard Time","Europe/Saratov"},
{L"Georgian Standard Time","Asia/Tbilisi"},
{L"Volgograd Standard Time","Europe/Volgograd"},
{L"Caucasus Standard Time","Asia/Yerevan"},
{L"Afghanistan Standard Time","Asia/Kabul"},
{L"West Asia Standard Time","Asia/Tashkent"},
{L"Ekaterinburg Standard Time","Asia/Yekaterinburg"},
{L"Pakistan Standard Time","Asia/Karachi"},
{L"Qyzylorda Standard Time","Asia/Qyzylorda"},
{L"India Standard Time","Asia/Calcutta"},
{L"Sri Lanka Standard Time","Asia/Colombo"},
{L"Nepal Standard Time","Asia/Katmandu"},
{L"Central Asia Standard Time","Asia/Almaty"},
{L"Bangladesh Standard Time","Asia/Dhaka"},
{L"Omsk Standard Time","Asia/Omsk"},
{L"Myanmar Standard Time","Asia/Rangoon"},
{L"SE Asia Standard Time","Asia/Bangkok"},
{L"Altai Standard Time","Asia/Barnaul"},
{L"W. Mongolia Standard Time","Asia/Hovd"},
{L"North Asia Standard Time","Asia/Krasnoyarsk"},
{L"N. Central Asia Standard Time","Asia/Novosibirsk"},
{L"Tomsk Standard Time","Asia/Tomsk"},
{L"China Standard Time","Asia/Shanghai"},
{L"North Asia East Standard Time","Asia/Irkutsk"},
{L"Singapore Standard Time","Asia/Singapore"},
{L"W. Australia Standard Time","Australia/Perth"},
{L"Taipei Standard Time","Asia/Taipei"},
{L"Ulaanbaatar Standard Time","Asia/Ulaanbaatar"},
{L"Aus Central W. Standard Time","Australia/Eucla"},
{L"Transbaikal Standard Time","Asia/Chita"},
{L"Tokyo Standard Time","Asia/Tokyo"},
{L"North Korea Standard Time","Asia/Pyongyang"},
{L"Korea Standard Time","Asia/Seoul"},
{L"Yakutsk Standard Time","Asia/Yakutsk"},
{L"Cen. Australia Standard Time","Australia/Adelaide"},
{L"AUS Central Standard Time","Australia/Darwin"},
{L"E. Australia Standard Time","Australia/Brisbane"},
{L"AUS Eastern Standard Time","Australia/Sydney"},
{L"West Pacific Standard Time","Pacific/Port_Moresby"},
{L"Tasmania Standard Time","Australia/Hobart"},
{L"Vladivostok Standard Time","Asia/Vladivostok"},
{L"Lord Howe Standard Time","Australia/Lord_Howe"},
{L"Bougainville Standard Time","Pacific/Bougainville"},
{L"Russia Time Zone 10","Asia/Srednekolymsk"},
{L"Magadan Standard Time","Asia/Magadan"},
{L"Norfolk Standard Time","Pacific/Norfolk"},
{L"Sakhalin Standard Time","Asia/Sakhalin"},
{L"Central Pacific Standard Time","Pacific/Guadalcanal"},
{L"Russia Time Zone 11","Asia/Kamchatka"},
{L"New Zealand Standard Time","Pacific/Auckland"},
{L"UTC+12","Etc/GMT-12"},
{L"Fiji Standard Time","Pacific/Fiji"},
{L"Chatham Islands Standard Time","Pacific/Chatham"},
{L"UTC+13","Etc/GMT-13"},
{L"Tonga Standard Time","Pacific/Tongatapu"},
{L"Samoa Standard Time","Pacific/Apia"},
{L"Line Islands Standard Time","Pacific/Kiritimati"},

View file

@ -14,7 +14,7 @@
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA
MY_ADD_TESTS(bitmap base64 my_atomic my_rdtsc lf my_malloc my_getopt dynstring
byte_order
byte_order my_tzinfo
queues stacktrace crc32 LINK_LIBRARIES mysys)
MY_ADD_TESTS(my_vsnprintf LINK_LIBRARIES strings mysys)
MY_ADD_TESTS(aes LINK_LIBRARIES mysys mysys_ssl)

View file

@ -0,0 +1,197 @@
/*
Copyright (c) 2023, MariaDB
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; version 2 of the License.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
*/
/*
This file tests my_tzset/my_get_tzinfo APIs
*/
#include <my_global.h>
#include <my_sys.h>
#include "tap.h"
#include <stdlib.h>
/**
Seconds since epoch used for "summer" timestamp
Corresponds to Jul 22 2023 04:26:40 GMT
Used to test timezone daylight savings UTC offset and DST abbreviation
*/
#define SUMMER_TIMESTAMP 1690000000
/**
Seconds since epoch used for "winter" timestamp
Corresponds to Nov 14 2023 22:13:20 GMT+
Used to test standard (no daylight savings) UTC offset and abbreviation
*/
#define WINTER_TIMESTAMP 1700000000
#ifdef _WIN32
#define timegm _mkgmtime
#endif
/**
Check expected offset from UTC, corresponding to specific
timestamp.
On Windows, it is possible that my_get_tzinfo() is using
ICU to calculate offset.This function rechecks that value is the
same when using C runtime's _mkgmtime().
Elsewhere, my_get_tzinfo is taking this value from non-standard glibc
extension struct tm::tm_gmtoff. This function rechecks that the value
is the same if calculated with timegm().
*/
static void check_utc_offset(time_t t, long expected, const char *comment)
{
#if defined _WIN32 || defined __linux__
struct tm local_time;
long offset;
localtime_r(&t, &local_time);
offset= (long) (timegm(&local_time) - t);
ok(offset == expected, "%s: Offset for timestamp %lld is %ld/%ld", comment,
(long long) t,expected, offset);
#else
skip(1, "no utc offset check");
#endif
}
/**
Test my_tzset/my_get_tzinfo functions for a single named timezone.
@param[in] tz_env. Timezone name, as used for TZ env.variable
@param[in] expected_tznames. Expected "sytem_timezone" names.
For example, expected names for timezone PST8PDT can
be PST8PDT, PST or PDT
@param[in] summer_gmt_off. Expected UTC offset, for SUMMER_TIMESTAMP
For timezones with DST savings on northern hemisphere
it is the expected DST offset from UTC
@param[in] summer_time_abbr . Expected standard abbreviation
corresponding to SUMMER_TIMESTAMP. For example, it is
"PDT" for timezone PST8PDT
@param[in] winter_gmt_off. Expected UTC offset, for WINTER_TIMESTAMP
@param[in] winter_time_abbr . Expected standard abbreviation
corresponding to WINTER_TIMESTAMP. For example, it is
"PST" for timezone PST8PDT.
*/
void test_timezone(const char *tz_env, const char **expected_tznames,
long summer_gmt_off, const char *summer_time_abbr,
long winter_gmt_off, const char *winter_time_abbr)
{
char timezone_name[64];
int found;
struct my_tz tz;
setenv("TZ", tz_env, 1);
my_tzset();
my_tzname(timezone_name, sizeof(timezone_name));
/* Check expected timezone names. */
found= 0;
for (int i= 0; expected_tznames[i]; i++)
{
if (!strcmp(expected_tznames[i], timezone_name))
{
found= 1;
break;
}
}
ok(found, "%s: timezone_name = %s", tz_env, timezone_name);
my_tzinfo(SUMMER_TIMESTAMP, &tz);
ok(summer_gmt_off == tz.seconds_offset, "%s: Summer GMT offset %ld", tz_env, tz.seconds_offset);
check_utc_offset(SUMMER_TIMESTAMP,tz.seconds_offset, tz_env);
ok(!strcmp(summer_time_abbr, tz.abbreviation), "%s: Summer time abbreviation %s",
tz_env, tz.abbreviation);
my_tzinfo(WINTER_TIMESTAMP, &tz);
ok(winter_gmt_off == tz.seconds_offset, "%s: Winter GMT offset %ld", tz_env, tz.seconds_offset);
check_utc_offset(WINTER_TIMESTAMP, tz.seconds_offset, tz_env);
ok(!strcmp(winter_time_abbr, tz.abbreviation), "%s: Winter time abbreviation %s",
tz_env, tz.abbreviation);
}
/* Check default timezone.*/
static void test_default_timezone()
{
char timezone_name[64];
time_t timestamps[]= {SUMMER_TIMESTAMP, WINTER_TIMESTAMP, time(NULL)};
size_t i;
struct my_tz tz;
#ifdef _WIN32
(void) putenv("TZ=");
#else
unsetenv("TZ");
#endif
my_tzset();
my_tzname(timezone_name, sizeof(timezone_name));
#ifdef _WIN32
/* Expect timezone name like Europe/Berlin */
ok(strstr(timezone_name, "/") != NULL, "Default timezone name %s",
timezone_name);
#else
skip(1, "no test for default timezone name %s", timezone_name);
#endif
for (i = 0; i < array_elements(timestamps); i++)
{
my_tzinfo(timestamps[i], &tz);
ok(tz.seconds_offset % 60 == 0,
"GMT offset is whole number of minutes %ld", tz.seconds_offset);
check_utc_offset(timestamps[i], tz.seconds_offset, timezone_name);
ok(strlen(tz.abbreviation) < 8, "tz abbreviation %s", tz.abbreviation);
}
}
int main(int argc __attribute__((unused)), char *argv[])
{
const char *PST8PDT_names[]= {"PST", "PDT", "PST8PDT", NULL};
const char *GMT_names[]= {"GMT", "Etc/UTC", NULL};
const char *GST_minus1GDT_names[]= {"GST", "GDT", NULL};
const char *IST_names[]= {"IST",NULL};
MY_INIT(argv[0]);
plan(38);
test_default_timezone();
/*
Test PST8PDT timezone
Standard timezone, supported everywhere. Note - this one is supported by
ICU, so it would be using ICU for calculation on Windows
*/
test_timezone("PST8PDT", PST8PDT_names, -25200, "PDT", -28800, "PST");
/*
Test GMT. Supported by ICU, would be using ICU for calculations
*/
test_timezone("GMT", GMT_names, 0, "GMT", 0, "GMT");
/*
Non-standard "Germany" timezone, taken from Windows tzset() documentation
example. Unsupported by ICU, will be using C runtime on Windows for
abbreviations, and offset calculations.
*/
test_timezone("GST-1GDT", GST_minus1GDT_names, 7200, "GDT", 3600, "GST");
/* India */
test_timezone("IST-5:30", IST_names, 19800, "IST", 19800, "IST");
my_end(0);
return exit_status();
}