Merge pull request #10311 from akallabeth/timezone_fixes

Timezone fixes
This commit is contained in:
akallabeth 2024-06-26 21:13:47 +02:00 committed by GitHub
commit 4bf3eac357
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 283 additions and 115 deletions

View File

@ -414,6 +414,8 @@ static BOOL rdp_read_extended_info_packet(rdpRdp* rdp, wStream* s)
if (!Stream_CheckAndLogRequiredLength(TAG, s, 2))
return FALSE;
if (freerdp_settings_get_bool(settings, FreeRDP_SupportDynamicTimeZone))
{
UINT16 cbDynamicDSTTimeZoneKeyName = 0;
@ -443,6 +445,11 @@ static BOOL rdp_read_extended_info_packet(rdpRdp* rdp, wStream* s)
if (!freerdp_settings_set_bool(settings, FreeRDP_DynamicDaylightTimeDisabled,
DynamicDaylightTimeDisabled != 0))
return FALSE;
DEBUG_TIMEZONE("DynamicTimeZone=%s [%s]",
freerdp_settings_get_string(settings, FreeRDP_DynamicDSTTimeZoneKeyName),
freerdp_settings_get_bool(settings, FreeRDP_DynamicDaylightTimeDisabled)
? "no-DST"
: "DST");
}
end:

View File

@ -29,6 +29,101 @@
#include <freerdp/log.h>
#define TAG FREERDP_TAG("core.timezone")
#if !defined(WITH_DEBUG_TIMEZONE)
#define log_timezone(tzif, result)
#else
#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
static const char* weekday2str(WORD wDayOfWeek)
{
switch (wDayOfWeek)
{
case 0:
return "SUNDAY";
case 1:
return "MONDAY";
case 2:
return "TUESDAY";
case 3:
return "WEDNESDAY";
case 4:
return "THURSDAY";
case 5:
return "FRIDAY";
case 6:
return "SATURDAY";
default:
return "DAY-OF-MAGIC";
}
}
static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len)
{
const SYSTEMTIME empty = { 0 };
if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0)
_snprintf(buffer, len, "{ not set }");
else
{
_snprintf(buffer, len,
"{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16 ":%" PRIu16
".%" PRIu16 "}",
t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour, t->wMinute,
t->wSecond, t->wMilliseconds);
}
return buffer;
}
static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line, ...)
{
if (!WLog_IsLevelActive(log, level))
return;
va_list ap;
va_start(ap, line);
WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, ap);
va_end(ap);
}
static void log_timezone_(const TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file,
const char* fkt, size_t line)
{
WINPR_ASSERT(tzif);
char buffer[64] = { 0 };
DWORD level = WLOG_TRACE;
wLog* log = WLog_Get(TIMEZONE_TAG);
log_print(log, level, file, fkt, line, "TIME_ZONE_INFORMATION {");
log_print(log, level, file, fkt, line, " Bias=%" PRIu32, tzif->Bias);
ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
ARRAYSIZE(buffer));
log_print(log, level, file, fkt, line, " StandardName=%s", buffer);
log_print(log, level, file, fkt, line, " StandardDate=%s",
systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer)));
log_print(log, level, file, fkt, line, " StandardBias=%" PRIu32, tzif->StandardBias);
ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
ARRAYSIZE(buffer));
log_print(log, level, file, fkt, line, " DaylightName=%s", buffer);
log_print(log, level, file, fkt, line, " DaylightDate=%s",
systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer)));
log_print(log, level, file, fkt, line, " DaylightBias=%" PRIu32, tzif->DaylightBias);
switch (result)
{
case TIME_ZONE_ID_DAYLIGHT:
log_print(log, level, file, fkt, line, " DaylightDate in use");
break;
case TIME_ZONE_ID_STANDARD:
log_print(log, level, file, fkt, line, " StandardDate in use");
break;
default:
log_print(log, level, file, fkt, line, " UnknownDate in use");
break;
}
log_print(log, level, file, fkt, line, "}");
}
#endif
static BOOL rdp_read_system_time(wStream* s, SYSTEMTIME* system_time);
static BOOL rdp_write_system_time(wStream* s, const SYSTEMTIME* system_time);
@ -78,11 +173,6 @@ BOOL rdp_write_system_time(wStream* s, const SYSTEMTIME* system_time)
Stream_Write_UINT16(s, system_time->wMinute); /* wMinute */
Stream_Write_UINT16(s, system_time->wSecond); /* wSecond */
Stream_Write_UINT16(s, system_time->wMilliseconds); /* wMilliseconds */
DEBUG_TIMEZONE("Time: y=%" PRIu16 ",m=%" PRIu16 ",dow=%" PRIu16 ",d=%" PRIu16 ", %02" PRIu16
":%02" PRIu16 ":%02" PRIu16 ".%03" PRIu16 "",
system_time->wYear, system_time->wMonth, system_time->wDayOfWeek,
system_time->wDay, system_time->wHour, system_time->wMinute,
system_time->wSecond, system_time->wMilliseconds);
return TRUE;
}
@ -121,6 +211,7 @@ BOOL rdp_read_client_time_zone(wStream* s, rdpSettings* settings)
if (!rdp_read_system_time(s, &tz->DaylightDate)) /* DaylightDate */
return FALSE;
Stream_Read_UINT32(s, tz->DaylightBias); /* DaylightBias */
log_timezone(tz, 0);
return TRUE;
}
@ -143,6 +234,7 @@ BOOL rdp_write_client_time_zone(wStream* s, rdpSettings* settings)
if (!tz)
return FALSE;
log_timezone(tz, 0);
if (!Stream_EnsureRemainingCapacity(s, 4ull + sizeof(tz->StandardName)))
return FALSE;
@ -154,21 +246,12 @@ BOOL rdp_write_client_time_zone(wStream* s, rdpSettings* settings)
if (!rdp_write_system_time(s, &tz->StandardDate))
return FALSE;
#ifdef WITH_DEBUG_TIMEZONE
WLog_DBG(TIMEZONE_TAG, "bias=%" PRId32 "", tz->Bias);
WLog_DBG(TIMEZONE_TAG, "StandardName:");
winpr_HexDump(TIMEZONE_TAG, WLOG_DEBUG, (const BYTE*)tz->StandardName,
sizeof(tz->StandardName));
WLog_DBG(TIMEZONE_TAG, "DaylightName:");
winpr_HexDump(TIMEZONE_TAG, WLOG_DEBUG, (const BYTE*)tz->DaylightName,
sizeof(tz->DaylightName));
#endif
/* Note that StandardBias is ignored if no valid standardDate is provided. */
/* StandardBias */
if (!Stream_EnsureRemainingCapacity(s, 4ull + sizeof(tz->DaylightName)))
return FALSE;
Stream_Write_UINT32(s, tz->StandardBias);
DEBUG_TIMEZONE("StandardBias=%" PRId32 "", tz->StandardBias);
/* daylightName (64 bytes) */
Stream_Write(s, tz->DaylightName, sizeof(tz->DaylightName));
/* DaylightDate */
@ -179,6 +262,6 @@ BOOL rdp_write_client_time_zone(wStream* s, rdpSettings* settings)
if (!Stream_EnsureRemainingCapacity(s, 4ull))
return FALSE;
Stream_Write_UINT32(s, tz->DaylightBias);
DEBUG_TIMEZONE("DaylightBias=%" PRId32 "", tz->DaylightBias);
return TRUE;
}

View File

@ -35,13 +35,5 @@
#endif
#define TIMEZONE_TAG FREERDP_TAG("timezone")
#ifdef WITH_DEBUG_TIMEZONE
#define DEBUG_TIMEZONE(...) WLog_DBG(TIMEZONE_TAG, __VA_ARGS__)
#else
#define DEBUG_TIMEZONE(...) \
do \
{ \
} while (0)
#endif
#endif /* FREERDP_LIB_LOCALE_LIB_H */

View File

@ -534,88 +534,132 @@ static LONG get_bias(const struct tm* start, BOOL dstBias)
return 0;
}
static BOOL map_iana_id(const char* iana, LPTIME_ZONE_INFORMATION tz)
static BOOL map_iana_id(const char* iana, LPDYNAMIC_TIME_ZONE_INFORMATION tz)
{
const char* winId = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_ID);
if (!winId)
return FALSE;
const char* winStd = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_STANDARD);
const char* winDst = TimeZoneIanaToWindows(iana, TIME_ZONE_NAME_DAYLIGHT);
ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
if (winStd)
ConvertUtf8ToWChar(winStd, tz->StandardName, ARRAYSIZE(tz->StandardName));
if (winDst)
ConvertUtf8ToWChar(winDst, tz->DaylightName, ARRAYSIZE(tz->DaylightName));
if (winId)
ConvertUtf8ToWChar(winId, tz->TimeZoneKeyName, ARRAYSIZE(tz->TimeZoneKeyName));
return TRUE;
return winId != NULL;
}
static const char* weekday2str(WORD wDayOfWeek)
{
switch (wDayOfWeek)
{
case 0:
return "SUNDAY";
case 1:
return "MONDAY";
case 2:
return "TUESDAY";
case 3:
return "WEDNESDAY";
case 4:
return "THURSDAY";
case 5:
return "FRIDAY";
case 6:
return "SATURDAY";
default:
return "DAY-OF-MAGIC";
}
}
static char* systemtime2str(const SYSTEMTIME* t, char* buffer, size_t len)
{
const SYSTEMTIME empty = { 0 };
if (memcmp(t, &empty, sizeof(SYSTEMTIME)) == 0)
_snprintf(buffer, len, "{ not set }");
else
{
_snprintf(buffer, len,
"{ %" PRIu16 "-%" PRIu16 "-%" PRIu16 " [%s] %" PRIu16 ":%" PRIu16 ":%" PRIu16
".%" PRIu16 "}",
t->wYear, t->wMonth, t->wDay, weekday2str(t->wDayOfWeek), t->wHour, t->wMinute,
t->wSecond, t->wMilliseconds);
}
return buffer;
}
static void log_print(wLog* log, DWORD level, const char* file, const char* fkt, size_t line, ...)
{
if (!WLog_IsLevelActive(log, level))
return;
va_list ap;
va_start(ap, line);
WLog_PrintMessageVA(log, WLOG_MESSAGE_TEXT, level, line, file, fkt, ap);
va_end(ap);
}
#define log_timezone(tzif, result) log_timezone_((tzif), (result), __FILE__, __func__, __LINE__)
static void log_timezone_(const DYNAMIC_TIME_ZONE_INFORMATION* tzif, DWORD result, const char* file,
const char* fkt, size_t line)
{
WINPR_ASSERT(tzif);
char buffer[130] = { 0 };
DWORD level = WLOG_INFO;
wLog* log = WLog_Get(TAG);
log_print(log, level, file, fkt, line, "DYNAMIC_TIME_ZONE_INFORMATION {");
log_print(log, level, file, fkt, line, " Bias=%" PRIu32, tzif->Bias);
ConvertWCharNToUtf8(tzif->StandardName, ARRAYSIZE(tzif->StandardName), buffer,
ARRAYSIZE(buffer));
log_print(log, level, file, fkt, line, " StandardName=%s", buffer);
log_print(log, level, file, fkt, line, " StandardDate=%s",
systemtime2str(&tzif->StandardDate, buffer, sizeof(buffer)));
log_print(log, level, file, fkt, line, " StandardBias=%" PRIu32, tzif->StandardBias);
ConvertWCharNToUtf8(tzif->DaylightName, ARRAYSIZE(tzif->DaylightName), buffer,
ARRAYSIZE(buffer));
log_print(log, level, file, fkt, line, " DaylightName=%s", buffer);
log_print(log, level, file, fkt, line, " DaylightDate=%s",
systemtime2str(&tzif->DaylightDate, buffer, sizeof(buffer)));
log_print(log, level, file, fkt, line, " DaylightBias=%" PRIu32, tzif->DaylightBias);
ConvertWCharNToUtf8(tzif->TimeZoneKeyName, ARRAYSIZE(tzif->TimeZoneKeyName), buffer,
ARRAYSIZE(buffer));
log_print(log, level, file, fkt, line, " TimeZoneKeyName=%s", buffer);
log_print(log, level, file, fkt, line, " DynamicDaylightTimeDisabled=DST-%s",
tzif->DynamicDaylightTimeDisabled ? "disabled" : "enabled");
switch (result)
{
case TIME_ZONE_ID_DAYLIGHT:
log_print(log, level, file, fkt, line, " DaylightDate in use");
break;
case TIME_ZONE_ID_STANDARD:
log_print(log, level, file, fkt, line, " StandardDate in use");
break;
default:
log_print(log, level, file, fkt, line, " UnknownDate in use");
break;
}
log_print(log, level, file, fkt, line, "}");
}
DWORD GetTimeZoneInformation(LPTIME_ZONE_INFORMATION lpTimeZoneInformation)
{
const char** list = NULL;
char* tzid = NULL;
const char* defaultName = "Client Local Time";
DWORD res = TIME_ZONE_ID_UNKNOWN;
const TIME_ZONE_INFORMATION empty = { 0 };
LPTIME_ZONE_INFORMATION tz = lpTimeZoneInformation;
WINPR_ASSERT(tz);
*tz = empty;
ConvertUtf8ToWChar(defaultName, tz->StandardName, ARRAYSIZE(tz->StandardName));
const time_t t = time(NULL);
struct tm tres = { 0 };
struct tm* local_time = localtime_r(&t, &tres);
if (!local_time)
goto out_error;
tz->Bias = get_bias(local_time, FALSE);
if (local_time->tm_isdst >= 0)
{
/* DST bias is the difference between standard time and DST in minutes */
const LONG d = get_bias(local_time, TRUE);
tz->DaylightBias = -1 * labs(tz->Bias - d);
get_transition_date(local_time, FALSE, &tz->StandardDate);
get_transition_date(local_time, TRUE, &tz->DaylightDate);
}
tzid = winpr_guess_time_zone();
if (!map_iana_id(tzid, tz))
{
const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0);
list = calloc(len, sizeof(char*));
if (!list)
goto out_error;
const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
for (size_t x = 0; x < size; x++)
{
const char* id = list[x];
if (map_iana_id(id, tz))
{
res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
break;
}
}
}
else
res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
out_error:
free(tzid);
free(list);
switch (res)
{
case TIME_ZONE_ID_DAYLIGHT:
case TIME_ZONE_ID_STANDARD:
WLog_DBG(TAG, "tz: Bias=%" PRId32 " sn='%s' dln='%s'", tz->Bias, tz->StandardName,
tz->DaylightName);
break;
default:
WLog_DBG(TAG, "tz not found, using computed bias %" PRId32 ".", tz->Bias);
break;
}
return res;
DYNAMIC_TIME_ZONE_INFORMATION dyn = { 0 };
DWORD rc = GetDynamicTimeZoneInformation(&dyn);
lpTimeZoneInformation->Bias = dyn.Bias;
lpTimeZoneInformation->DaylightBias = dyn.DaylightBias;
lpTimeZoneInformation->DaylightDate = dyn.DaylightDate;
lpTimeZoneInformation->StandardBias = dyn.StandardBias;
lpTimeZoneInformation->StandardDate = dyn.StandardDate;
memcpy(lpTimeZoneInformation->StandardName, dyn.StandardName,
sizeof(lpTimeZoneInformation->StandardName));
memcpy(lpTimeZoneInformation->DaylightName, dyn.DaylightName,
sizeof(lpTimeZoneInformation->DaylightName));
return rc;
}
BOOL SetTimeZoneInformation(const TIME_ZONE_INFORMATION* lpTimeZoneInformation)
@ -666,30 +710,72 @@ BOOL TzSpecificLocalTimeToSystemTime(LPTIME_ZONE_INFORMATION lpTimeZoneInformati
(defined(_WIN32) && (defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0600 || \
!defined(NTDDI_WIN8) && _WIN32_WINNT < 0x0501)) /* Windows Vista */
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION pTimeZoneInformation)
DWORD GetDynamicTimeZoneInformation(PDYNAMIC_TIME_ZONE_INFORMATION tz)
{
TIME_ZONE_INFORMATION tz = { 0 };
const DWORD rc = GetTimeZoneInformation(&tz);
BOOL doesNotHaveStandardDate = FALSE;
BOOL doesNotHaveDaylightDate = FALSE;
const char** list = NULL;
char* tzid = NULL;
const char* defaultName = "Client Local Time";
DWORD res = TIME_ZONE_ID_UNKNOWN;
const DYNAMIC_TIME_ZONE_INFORMATION empty = { 0 };
WINPR_ASSERT(pTimeZoneInformation);
pTimeZoneInformation->Bias = tz.Bias;
memcpy(pTimeZoneInformation->StandardName, tz.StandardName,
MIN(sizeof(tz.StandardName), sizeof(pTimeZoneInformation->StandardName)));
pTimeZoneInformation->StandardDate = tz.StandardDate;
pTimeZoneInformation->StandardBias = tz.StandardBias;
WINPR_ASSERT(tz);
memcpy(pTimeZoneInformation->DaylightName, tz.DaylightName,
MIN(sizeof(tz.DaylightName), sizeof(pTimeZoneInformation->DaylightName)));
pTimeZoneInformation->DaylightDate = tz.DaylightDate;
pTimeZoneInformation->DaylightBias = tz.DaylightBias;
*tz = empty;
ConvertUtf8ToWChar(defaultName, tz->StandardName, ARRAYSIZE(tz->StandardName));
memcpy(pTimeZoneInformation->TimeZoneKeyName, tz.StandardName,
MIN(sizeof(tz.StandardName), sizeof(pTimeZoneInformation->TimeZoneKeyName)));
const time_t t = time(NULL);
struct tm tres = { 0 };
struct tm* local_time = localtime_r(&t, &tres);
if (!local_time)
goto out_error;
/* https://learn.microsoft.com/en-us/windows/win32/api/timezoneapi/ns-timezoneapi-dynamic_time_zone_information
*/
pTimeZoneInformation->DynamicDaylightTimeDisabled = FALSE;
return rc;
tz->Bias = get_bias(local_time, FALSE);
if (local_time->tm_isdst >= 0)
{
/* DST bias is the difference between standard time and DST in minutes */
const LONG d = get_bias(local_time, TRUE);
tz->DaylightBias = -1 * labs(tz->Bias - d);
if (!get_transition_date(local_time, FALSE, &tz->StandardDate))
doesNotHaveStandardDate = TRUE;
if (!get_transition_date(local_time, TRUE, &tz->DaylightDate))
doesNotHaveDaylightDate = TRUE;
}
tzid = winpr_guess_time_zone();
if (!map_iana_id(tzid, tz))
{
const size_t len = TimeZoneIanaAbbrevGet(local_time->tm_zone, NULL, 0);
list = calloc(len, sizeof(char*));
if (!list)
goto out_error;
const size_t size = TimeZoneIanaAbbrevGet(local_time->tm_zone, list, len);
for (size_t x = 0; x < size; x++)
{
const char* id = list[x];
if (map_iana_id(id, tz))
{
res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
break;
}
}
}
else
res = (local_time->tm_isdst) ? TIME_ZONE_ID_DAYLIGHT : TIME_ZONE_ID_STANDARD;
if (doesNotHaveDaylightDate)
tz->DaylightBias = 0;
if (doesNotHaveStandardDate)
tz->StandardBias = 0;
out_error:
free(tzid);
free(list);
log_timezone(tz, res);
return res;
}
BOOL SetDynamicTimeZoneInformation(const DYNAMIC_TIME_ZONE_INFORMATION* lpTimeZoneInformation)