mariadb/unittest/mysys/my_tzinfo-t.c
Vladislav Vaintroub 3424ed7d42 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.
2023-11-21 21:35:02 +01:00

197 lines
6.2 KiB
C

/*
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();
}