How to Migrate Datetime Values to Datetimeoffset in SQL Server

How to migrate DateTime values to DateTimeOffset in SQL Server?

Daylight saving isn't always an exact science. E.g. there was a rule change in 2007 for the period used in North America. For this reason, would suggest populating a table with the relevant dates. E.g. for 2000-2013:

-- Populate a table with PST daylight saving start/end times
-- Example data for 2000-2013 - sourced from
-- http://www.timeanddate.com/worldclock/timezone.html?n=137
CREATE TABLE dst (start DateTime, [end] DateTime);
INSERT INTO dst (start, [end]) VALUES ('02:00 2 Apr 2000', '02:00 29 Oct 2000');
INSERT INTO dst (start, [end]) VALUES ('02:00 1 Apr 2001', '02:00 28 Oct 2001');
INSERT INTO dst (start, [end]) VALUES ('02:00 7 Apr 2002', '02:00 27 Oct 2002');
INSERT INTO dst (start, [end]) VALUES ('02:00 6 Apr 2003', '02:00 26 Oct 2003');
INSERT INTO dst (start, [end]) VALUES ('02:00 4 Apr 2004', '02:00 31 Oct 2004');
INSERT INTO dst (start, [end]) VALUES ('02:00 3 Apr 2005', '02:00 30 Oct 2005');
INSERT INTO dst (start, [end]) VALUES ('02:00 2 Apr 2006', '02:00 29 Oct 2006');
INSERT INTO dst (start, [end]) VALUES ('02:00 11 Apr 2007', '02:00 4 Oct 2007');
INSERT INTO dst (start, [end]) VALUES ('02:00 9 Apr 2008', '02:00 2 Nov 2008');
INSERT INTO dst (start, [end]) VALUES ('02:00 8 Apr 2009', '02:00 1 Nov 2009');
INSERT INTO dst (start, [end]) VALUES ('02:00 14 Apr 2010', '02:00 7 Nov 2010');
INSERT INTO dst (start, [end]) VALUES ('02:00 13 Apr 2011', '02:00 6 Nov 2011');
INSERT INTO dst (start, [end]) VALUES ('02:00 11 Apr 2012', '02:00 4 Nov 2012');
INSERT INTO dst (start, [end]) VALUES ('02:00 10 Apr 2013', '02:00 3 Nov 2013');

Of course you may need a lot more than this - will leave doing that as an exercise for the reader :-)

OK, so let's say you populated the above table with the whole possible range and you have date/times in field dt of table test. Then you can join to the above table and do the conversions like this:

-- Convert sample dates to PST offset with daylight saving where appropriate
SELECT test.dt,
CAST(CONVERT(VARCHAR(23), test.dt, 126) + -- Convert to ISO8601 format
CASE WHEN dst.start IS NULL
THEN '-08:00' -- No record joined so not within DST period
ELSE '-07:00' -- DST record joined so is within DST period
END AS DateTimeOffset) AS dto
FROM test
LEFT JOIN dst -- Join on daylight savings table to find out whether DST applies
ON test.dt >= dst.start
AND test.dt <= dst.[end]

Here's an SQL fiddle demo.

Migrate SQL Server DateTime column to DateTimeOffset

If you're using a version of SQL Server that knows of the datetimeoffset type, this syntax will work for getting you the local tz offset of the server:

select datepart(tz,sysdatetimeoffset())

The result is in MINUTES.

How to cast datetime to datetimeoffset?

Edit: Updated better answer for SQL Server 2016

SELECT 
ChangeDate, --original datetime value
ChangeDate AT TIME ZONE 'Eastern Standard Time' AS ChangeDateOffset
FROM AuditLog

The AT TIME ZONE takes into account whether daylight savings was in effect at the time of the date being converted. And even though it says "Standard" in "Eastern Standard Time", it will give you daylight times as well:

ChangeDate               ChangeDateOffset
----------------------- ------------------------------
2019-01-21 09:00:00.000 2019-01-21 09:00:00.000 -05:00
2019-02-21 09:00:00.000 2019-02-21 09:00:00.000 -05:00
2019-03-21 09:00:00.000 2019-03-21 09:00:00.000 -04:00 <-- savings time
2019-04-21 09:00:00.000 2019-04-21 09:00:00.000 -04:00 <-- savings time
2019-05-21 09:00:00.000 2019-05-21 09:00:00.000 -04:00 <-- savings time
2019-06-21 09:00:00.000 2019-06-21 09:00:00.000 -04:00 <-- savings time
2019-07-21 09:00:00.000 2019-07-21 09:00:00.000 -04:00 <-- savings time
2019-08-21 09:00:00.000 2019-08-21 09:00:00.000 -04:00 <-- savings time
2019-09-21 09:00:00.000 2019-09-21 09:00:00.000 -04:00 <-- savings time
2019-10-21 09:00:00.000 2019-10-21 09:00:00.000 -04:00 <-- savings time
2019-11-21 09:00:00.000 2019-11-21 09:00:00.000 -05:00
2019-12-21 09:00:00.000 2019-12-21 09:00:00.000 -05:00

As for how do you avoid hard-coding the string Eastern Standard Time, and use the current timezone of the server? You're SOL.

Original pre-SQL Server 2016 answer

i figured it out. The trick is that there is a built-in SQL Server function ToDateTimeOffset, which attaches arbitrary offset information to any supplied datetime.

For example, the identical queries:

SELECT ToDateTimeOffset('2013-07-25 15:35:27', -240)     --  -240 minutes
SELECT ToDateTimeOffset('2013-07-25 15:35:27', '-04:00') -- -4 hours

both return:

2013-07-25 15:35:27.0000000 -04:00

Note: The offset parameter to ToDateTimeOffset can either be:

  • an integer, representing a number of minutes
  • a string, representing a hours and minutes (in {+|-}TZH:THM format)

We need the server's current UTC offset

Next we need the server's current offset from UTC. There are two ways i can have SQL Server return the the integer number of minutes we are from UTC:

DATEPART(TZOFFSET, SYSDATETIMEOFFSET()) 
DATEDIFF(minute, GETUTCDATE(), GETDATE())

both return

-240

Plugging this into the TODATETIMEOFFSET function:

SELECT ToDateTimeOffset(
'2013-07-25 15:35:27',
DATEPART(TZOFFSET, SYSDATETIMEOFFSET()) --e.g. -240
)

returns the datetimeoffset value i want:

2013-07-25 15:35:27.0000000 -04:00

Putting it altogether

Now we can have a better function to convert a datetime into a datetimeoffset:

CREATE FUNCTION dbo.ToDateTimeOffset(@value datetime2)
RETURNS datetimeoffset AS
BEGIN
/*
Converts a date/time without any timezone offset into a datetimeoffset value,
using the server's current offset from UTC.

For this we use the built-in ToDateTimeOffset function;
which attaches timezone offset information with a datetimeoffset value.

The trick is to use DATEDIFF(minutes) between local server time and UTC
to get the offset parameter.

For example:
DATEPART(TZOFFSET, SYSDATETIMEOFFSET())
returns the integer
-240

for people in EDT (Eastern Daylight Time), which is 4 hours (240 minutes) behind UTC.
Pass that value to the SQL Server function:
TODATETIMEOFFSET(@value, -240)
*/

RETURN TODATETIMEOFFSET(@value, DATEPART(TZOFFSET, SYSDATETIMEOFFSET()))
END;

Sample usage

SELECT TOP 5
ChangeDate,
dbo.ToDateTimeOffset(ChangeDate) AS ChangeDateOffset
FROM AuditLog

returns the desired:

ChangeDate               ChangeDateOffset
======================= ==================================
2013-07-25 04:00:03.060 2013-07-25 04:00:03.0600000 -04:00
2013-07-24 04:00:03.073 2013-07-24 04:00:03.0730000 -04:00
2013-07-23 04:00:03.273 2013-07-23 04:00:03.2730000 -04:00
2013-07-20 04:00:02.870 2013-07-20 04:00:02.8700000 -04:00
2013-07-19 04:00:03.780 2013-07-19 04:00:03.7800000 -04:00

It would have been ideal if the built-in function would have just did this:

TODATETIMEOFFSET(value)

rather than having to create an "overload":

dbo.ToDateTimeOffset(value)

Note: Any code is released into the public domain. No attribution required.



Related Topics



Leave a reply



Submit