Skip to content

Commit

Permalink
MDEV-32189 Use icu for timezones on windows
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
vaintroub committed Nov 21, 2023
1 parent bb8e1bf commit 3424ed7
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 266 deletions.
12 changes: 12 additions & 0 deletions include/my_sys.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions mysys/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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()
Expand Down
219 changes: 219 additions & 0 deletions mysys/my_timezone.cc
Original file line number Diff line number Diff line change
@@ -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
}
2 changes: 1 addition & 1 deletion sql/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
12 changes: 0 additions & 12 deletions sql/gen_win_tzname_data.ps1

This file was deleted.

4 changes: 2 additions & 2 deletions sql/item_timefunc.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
51 changes: 2 additions & 49 deletions sql/mysqld.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 3424ed7

Please sign in to comment.