
When working with date-time fields in Dynamics 365 Finance & Operations, it’s critical to store and display values consistently—especially if you’re in a region that observes Daylight Saving Time (DST). This article walks through a common pitfall and shows how to correct “local” date-times written as UTC, ensuring your users always see the right time in the UI.
The Pitfall: Writing Local Time as UTC
Imagine you’re in a GMT+1 region (e.g., Europe/Rome). You take a local time—say 17:15, believing it to be your own time—and write it directly into a utcDateTime field in SQL. Because D365FO interprets that value as UTC, when the form renders, it adds the +1 (or +2 in summer) offset and displays 18:15 (or 19:15), which is obviously wrong.
Saved value in DB: 2025-06-11 17:15 (interpreted as UTC)
Displayed in UI: 2025-06-11 18:15 (UTC+1 applied)
Why a Static “−1 Hour” Isn’t Enough
A naïve fix is to always subtract one hour:
// Subtract 1 hour every time
update_recordset myTable
setting MyUtcField = DateTimeUtil::addHours(MyUtcField, -1)
where MyUtcField != DateTimeUtil::minValue();
While this works in winter (UTC+1), it breaks in summer when the offset is UTC+2—you’d need to subtract 2 hours instead. Hard-coding a fixed offset fails to handle DST transitions.
DST-Aware Solution with DateTimeUtil
D365FO provides built-in support for time zones and DST via DateTimeUtil and the TimeZone enum. The key functions are:
DateTimeUtil::getCompanyTimeZone()
Retrieves the company’s configured time zone (e.g.,Europe/Rome), including DST rules.DateTimeUtil::removeTimeZoneOffset(utcDateTime, tz)
Takes a value that was written “as if UTC” but actually represents local time intz, and subtracts the current offset (1 hour in winter, 2 hours in summer), yielding the true UTC.
Batch-Fixing Your Table
To correct all “snafu” records in one go, use this update_recordset:
// 1. Get the company time zone with DST rules
TimeZone tz = DateTimeUtil::getCompanyTimeZone(); // e.g. Europe/Rome
// 2. Batch-update: remove the right offset per record
ttsBegin;
update_recordset myTable
setting MyUtcField = DateTimeUtil::removeTimeZoneOffset(
myTable.MyUtcField,
tz
)
where myTable.MyUtcField != DateTimeUtil::minValue();
ttsCommit;
Behind the scenes:
- Winter → offset = +1h → subtracts 1h
- Summer → offset = +2h → subtracts 2h
After this, each MyUtcField truly matches UTC. When D365FO displays it, it will re-apply the correct offset (+1/+2), and users will consistently see the intended local time (e.g., 17:15).
Record-by-Record Correction
If you need to fix a single record—perhaps in a form event or batch job—the same principle applies:
TimeZone tz = DateTimeUtil::getCompanyTimeZone();
UtcDateTime localValue = myTable.MyUtcField;
UtcDateTime correctUtc = DateTimeUtil::removeTimeZoneOffset(localValue, tz);
ttsBegin;
myTable.selectForUpdate(true);
myTable.MyUtcField = correctUtc;
myTable.update();
ttsCommit;
This approach ensures that DST is always honored, without manual calculations.
Conclusion
By leveraging getCompanyTimeZone() and removeTimeZoneOffset(), you offload all the complexity of DST management to the platform. Whether in winter or summer, your stored UTC values will be accurate—and your users will see the right local time in every form.
Leave a comment