diff --git a/include/my_sys.h b/include/my_sys.h index 2852d73d734..25fedb63b8f 100644 --- a/include/my_sys.h +++ b/include/my_sys.h @@ -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); diff --git a/mysys/CMakeLists.txt b/mysys/CMakeLists.txt index 8021a2844c8..5d34cf94b46 100644 --- a/mysys/CMakeLists.txt +++ b/mysys/CMakeLists.txt @@ -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() diff --git a/mysys/my_timezone.cc b/mysys/my_timezone.cc new file mode 100644 index 00000000000..3a62962f225 --- /dev/null +++ b/mysys/my_timezone.cc @@ -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 +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#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 +} diff --git a/sql/CMakeLists.txt b/sql/CMakeLists.txt index ea4d6c9ae9a..2717858716c 100644 --- a/sql/CMakeLists.txt +++ b/sql/CMakeLists.txt @@ -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() diff --git a/sql/gen_win_tzname_data.ps1 b/sql/gen_win_tzname_data.ps1 deleted file mode 100644 index 474ab889d25..00000000000 --- a/sql/gen_win_tzname_data.ps1 +++ /dev/null @@ -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+'"},') -} diff --git a/sql/item_timefunc.cc b/sql/item_timefunc.cc index 757c0a5c5bd..165a28abe6a 100644 --- a/sql/item_timefunc.cc +++ b/sql/item_timefunc.cc @@ -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); diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 63eb33e1516..89342d76191 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -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 diff --git a/sql/tztime.cc b/sql/tztime.cc index 9a1f8c6fc52..fa50fcb87d6 100644 --- a/sql/tztime.cc +++ b/sql/tztime.cc @@ -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); } diff --git a/sql/tztime.h b/sql/tztime.h index 3dabbe8d118..5910e8d0d1c 100644 --- a/sql/tztime.h +++ b/sql/tztime.h @@ -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 diff --git a/sql/win_tzname_data.h b/sql/win_tzname_data.h deleted file mode 100644 index 8a240118ac3..00000000000 --- a/sql/win_tzname_data.h +++ /dev/null @@ -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"}, diff --git a/unittest/mysys/CMakeLists.txt b/unittest/mysys/CMakeLists.txt index 4b947ab780f..d292b7724e2 100644 --- a/unittest/mysys/CMakeLists.txt +++ b/unittest/mysys/CMakeLists.txt @@ -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) diff --git a/unittest/mysys/my_tzinfo-t.c b/unittest/mysys/my_tzinfo-t.c new file mode 100644 index 00000000000..b38ebd37a61 --- /dev/null +++ b/unittest/mysys/my_tzinfo-t.c @@ -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 +#include +#include "tap.h" +#include + +/** + 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(); +}