
Hello F&O developers,
Today, I wanted to calculate an offset in minutes based on a timestamp and in a time zone in X++ and came across the DateTimeUtil::getTimeZoneOffset() method. Unfortunately, this seems to have an inaccuracy. Before I report an issue to Microsoft, I wanted to ask you whether I have a logic error or whether I have overlooked something. Unfortunately, the DateTimeUtil::getTimeZoneOffset() method is not well documented.
I also tested the .NET class TimeZoneInfo, which does something similar, but correctly and as I expect. Maybe I've caught your interest ;)
[SysTestMethodAttribute()]
public void timeZoneOffSetTest()
{
// given values
utcdatetime dtBefore;
utcdatetime dtInSwitch;
utcdatetime dtAfter;
TimeZoneId timeZoneId;
System.TimeZoneInfo timeZoneInfo;
// results
int offsetBefore;
int offsetInSwitch;
int offsetAfter;
real offsetBeforeNET;
real offsetInSwitchNET;
real offsetAfterNET;
// ######################################################
// # test with clock change germany (26th october 2025) #
// ######################################################
// visualization for help: https://www.worldtimebuddy.com/?pl=1&lid=100,2950159&h=100&date=10/26/2025%7C3&hf=0
dtBefore = 2025-10-26T00:59:59; // local: 2025-10-26T02:59:59; expected UTC offset: 120
dtInSwitch = 2025-10-26T01:00:00; // local: 2025-10-26T02:00:00; expected UTC offset: 60
dtAfter = 2025-10-26T01:00:01; // local: 2025-10-26T02:00:01; expected UTC offset: 60
// determine offset with X++, it will be wrong
offsetBefore = DateTimeUtil::getTimeZoneOffset(dtBefore, Timezone::GMTPLUS0100_AMSTERDAM_BERLIN_BERN_ROME); // Returns 120; OK
offsetInSwitch = DateTimeUtil::getTimeZoneOffset(dtInSwitch, Timezone::GMTPLUS0100_AMSTERDAM_BERLIN_BERN_ROME); // Returns 120; WRONG
offsetAfter = DateTimeUtil::getTimeZoneOffset(dtAfter, Timezone::GMTPLUS0100_AMSTERDAM_BERLIN_BERN_ROME); // Returns 120; WRONG
// determine offset with .NET, it will be correct
timeZoneId = DateTimeUtil::getTimeZoneId(Timezone::GMTPLUS0100_AMSTERDAM_BERLIN_BERN_ROME);
timeZoneInfo = System.TimeZoneInfo::FindSystemTimeZoneById(timeZoneId);
offsetBeforeNET = timeZoneInfo.GetUtcOffset(dtBefore).TotalMinutes; // Returns 120; OK
offsetInSwitchNET = timeZoneInfo.GetUtcOffset(dtInSwitch).TotalMinutes; // Returns 60; OK
offsetAfterNET = timeZoneInfo.GetUtcOffset(dtAfter).TotalMinutes; // Returns 60; OK
// #################################################################
// # test with clock change pacific standard time (8th march 2026) #
// #################################################################
// visualization for help: https://www.worldtimebuddy.com/?pl=1&lid=100,8&h=100&date=3/8/2026%7C3&hf=0
dtBefore = 2026-03-08T09:59:59; // local: 2026-03-08T01:59:59; expected UTC offset: -480
dtInSwitch = 2026-03-08T10:00:00; // local: 2026-03-08T03:00:00; expected UTC offset: -420
dtAfter = 2026-03-08T10:00:01; // local: 2026-03-08T03:00:01; expected UTC offset: -420
// determine offset with X++, it will be wrong
offsetBefore = DateTimeUtil::getTimeZoneOffset(dtBefore, Timezone::GMTMINUS0800PACIFICTIME); // Returns -420; WRONG
offsetInSwitch = DateTimeUtil::getTimeZoneOffset(dtInSwitch, Timezone::GMTMINUS0800PACIFICTIME); // Returns -420; OK
offsetAfter = DateTimeUtil::getTimeZoneOffset(dtAfter, Timezone::GMTMINUS0800PACIFICTIME); // Returns -420; OK
// determine offset with .NET, it will be correct
timeZoneId = DateTimeUtil::getTimeZoneId(Timezone::GMTMINUS0800PACIFICTIME);
timeZoneInfo = System.TimeZoneInfo::FindSystemTimeZoneById(timeZoneId);
offsetBeforeNET = timeZoneInfo.GetUtcOffset(dtBefore).TotalMinutes; // Returns -480; OK
offsetInSwitchNET = timeZoneInfo.GetUtcOffset(dtInSwitch).TotalMinutes; // Returns -420; OK
offsetAfterNET = timeZoneInfo.GetUtcOffset(dtAfter).TotalMinutes; // Returns -420; OK
}