Given a datetime that is "local" (i.e. has no timezone information), e.g.:
| Datetime |
|---------------------|
| 2019-01-21 09:00:00 |
| 2019-02-21 09:00:00 |
| 2019-03-21 09:00:00 |
| 2019-04-21 09:00:00 |
| 2019-05-21 09:00:00 |
| 2019-06-21 09:00:00 |
| 2019-07-21 09:00:00 |
| 2019-08-21 09:00:00 |
| 2019-09-21 09:00:00 |
| 2019-10-21 09:00:00 |
| 2019-11-21 09:00:00 |
| 2019-12-21 09:00:00 |
How can i get that date's offset from UTC? (assuming the machine local timezone information)
For example, my local PC is in the Eastern timezone. And the Eastern timezone is either:
- 300 minutes (5 hours) behind UTC
- 240 minutes (4 hours) behind UTC
depending on whether "daylight saving" was in effect at the time of that datetime.
Which means for the above list:
| Datetime | Offset from UTC (minutes) |
|---------------------|----------------------------|
| 2019-01-21 09:00:00 | -300 (-5 hours) |
| 2019-02-21 09:00:00 | -300 (-5 hours) |
| 2019-03-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-04-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-05-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-06-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-07-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-08-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-09-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-10-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2019-11-21 09:00:00 | -300 (-5 hours) |
| 2019-12-21 09:00:00 | -300 (-5 hours) |
And, of course, those offsets change if the dates are from before 2007, the answer changes:
| Datetime | Offset from UTC (minutes) |
|---------------------|----------------------------|
| 2006-01-21 09:00:00 | -300 (-5 hours) |
| 2006-02-21 09:00:00 | -300 (-5 hours) |
| 2006-03-21 09:00:00 | -240 (-5 hours) |
| 2006-04-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-05-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-06-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-07-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-08-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-09-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 2006-10-21 09:00:00 | -240 (-5 hours) |
| 2006-11-21 09:00:00 | -300 (-5 hours) |
| 2006-12-21 09:00:00 | -300 (-5 hours) |
And the answer would be different again during the 1977 energy crisis, as the country ran on daylight saving year round:
| Datetime | Offset from UTC (minutes) |
|---------------------|----------------------------|
| 1977-01-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-02-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-03-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-04-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-05-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-06-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-07-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-08-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-09-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-10-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-11-21 09:00:00 | -240 (-4 hours) | Daylight savings
| 1977-12-21 09:00:00 | -240 (-4 hours) | Daylight savings
And before 1966 the answers change some more.
Windows knows all these things.
So the question is:
- given a datetime in
FILETIME
format - assumed to be of the timezone of the current PC
- how can i get that datetime's offset from UTC
- at the time of the datetime
In other words:
//Pesudocode. It may look like C#, but i'm using the native Win32 api
Int32 GetDateTimeMinutesOffsetFromUTC(DateTime value)
{
//2006-03-21 09:00:00 ==> -300
//2007-03-21 09:00:00 ==> -240
return -1; //todo
}
or
function GetDateTimeMinutesOffsetFromUtc(Value: TDateTime): Integer;
begin
//2006-03-21 09:00:00 ==> -300
//2007-03-21 09:00:00 ==> -240
Result := -1; //todo
end;
or
int GetDateTimeMinutesOffsetFromUtc(FILETIME value)
{
//2006-03-21 09:00:00 ==> -300
//2007-03-21 09:00:00 ==> -240
Result := -1; //todo
}
And, as a reminder, i'm using the Win32 api.
- This is not C/C++ (i.e. i don't have access to the C standard library)
- This is not C# (i.e. i don't have access to the .NET Framework Class Library)
- This is not Java (i.e. i don't have access to the Java Class Library)
- This is not Python
- This is not Javascript, React, Rust, Django
I'm talking about Windows and the Win32 API.
SQL Server
You can see the above work in SQL Server:
SELECT
EventDate,
DATEDIFF(minute, CAST(EventDate AS datetime) AT TIME ZONE 'Eastern Standard Time', EventDate) AS MinutesOffsetFromUTC
FROM (VALUES
('2019-01-21 09:00:00.000'),
('2019-02-21 09:00:00.000'),
('2019-03-21 09:00:00.000'),
('2019-04-21 09:00:00.000'),
('2019-05-21 09:00:00.000'),
('2019-06-21 09:00:00.000'),
('2019-07-21 09:00:00.000'),
('2019-08-21 09:00:00.000'),
('2019-09-21 09:00:00.000'),
('2019-10-21 09:00:00.000'),
('2019-11-21 09:00:00.000'),
('2019-12-21 09:00:00.000')
) foo(EventDate)
EventDate MinutesOffsetFromUTC
----------------------- --------------------
2019-01-21 09:00:00.000 -300
2019-02-21 09:00:00.000 -300
2019-03-21 09:00:00.000 -240
2019-04-21 09:00:00.000 -240
2019-05-21 09:00:00.000 -240
2019-06-21 09:00:00.000 -240
2019-07-21 09:00:00.000 -240
2019-08-21 09:00:00.000 -240
2019-09-21 09:00:00.000 -240
2019-10-21 09:00:00.000 -240
2019-11-21 09:00:00.000 -300
2019-12-21 09:00:00.000 -300
(12 rows affected)
Research Effort
Most of the Winapi functions for converting "local" to "UTC", and back again, don't take into account the date in question; but instead only use whether daylight savings is in effect right now:
Functions like FileTimeToLocalFileTime apply the current Daylight Savings Time (DST) bias rather than the bias that was in effect at the time in question.
Others take into account the date being converted, but only look at the daylight saving start and end rules as they are now - not what the rules were then.
But TzSpecificLocalTimeToSystemTime is the one function that does understand datetimes and daylight savings:
TzSpecificLocalTimeToSystemTime takes into account whether daylight saving time (DST) is in effect for the local time to be converted.
In reality Windows doesn't know everything. It keeps the database of historical "daylight savings" dates in the registry:
So for me all it really know is
- before the great change of 2007
- and after
But it's good enough for me in my use case. And it's good enough for SQL Server.