Reputation: 659
My user interface features a dropdown list of all of the timezones populated by .Net's TimeZoneInfo.GetSystemTimeZones() (in MVC Razor) that is then recorded against the user.
<select id="TimeZoneAbbreviation" name="TimeZoneAbbreviation">
@foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
{
<!option value="@tz.StandardName" @(tz.StandardName == Model.Project.TimeZone ? "selected='selected'" : "")>@tz.DisplayName</!option>
}
</select>
I have a stored procedure that needs to do some processing and convert a UTC time that something is logged in the database into the current user's local time for presentation on the screen.
In testing, I discovered that a handful (16) timezones cause 'at time zone' to throw an error. These problems occur on both SQL Azure and Sql Server 2016
declare @timezone as varchar(max) = 'Alaskan Standard Time'
select cast(cast(GETUTCDATE() as datetimeoffset) at time zone @timeZone as datetime) -- this works
set @timezone = 'Cabo Verde Standard Time'
select cast(cast(GETUTCDATE() as datetimeoffset) at time zone @timeZone as datetime) -- does not work
set @timezone = 'Coordinated Universal Time'
select cast(cast(GETUTCDATE() as datetimeoffset) at time zone @timeZone as datetime) -- does not work
This results in
(1 row affected)
Msg 9820, Level 16, State 1, Line 411
The time zone parameter 'Cabo Verde Standard Time' provided to AT TIME ZONE clause is invalid.
Msg 9820, Level 16, State 1, Line 414
The time zone parameter 'Coordinated Universal Time' provided to AT TIME ZONE clause is invalid.
So I discover that SQL has its own list that is different to that of .Net. So the immediate thought to not use .Net to get the list. Rather, I should simply ask SQL Server via a query.
select * from sys.time_zone_info
The problem with this approach is that this returns a different and incompatible list to that of .Net. In my .Net code I have some code like this
TimeZoneInfo.FromSerializedString(project.TimeZone)
This will break if it uses an unknown string that SQL Server knows about.
Fun fact #1, the documentation at https://learn.microsoft.com/en-us/sql/t-sql/queries/at-time-zone-transact-sql?view=sql-server-2017 states that the list is sourced from "KEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones". Sure enough, 'Coordinated Universal Time' is not in the list (and it does not work). But 'Cabo Verde Standard Time' IS in the list (but that does not work either).
Fun fact #2, "Cabo Verde Standard" does not work but "Cape Verde Standard Time" does work. These are not typos. These are actual timezones.
(Before someone asks, I converted the GetUTCDate() into a datetimeoffset as the documentation says "If inputdate is provided as a datetimeoffset value, then AT TIME ZONE clause converts it into the target time zone using the time zone conversion rules." which is what I wanted to happen).
So, the question... what am I to do? What have others done? This whole area appears to be a total dog's breakfast.
I guess my problem is that I have timezone processing both in the stored procs and .Net and their timezone processing is incompatible. I probably should somehow just do one and not the other.
Thanks in advance.
Upvotes: 0
Views: 1647
Reputation: 131492
The real problem is that nobody uses Windows timezone names. Everyone uses IANA timezone names making the at time zone
clause a bit ... unhelpful.
In this case you should probably use the ID, not the DisplayName. For example, the display name Cabo Verde Daylight Time
has an ID of Cape Verde Standard Time
. The name Coordinated Universal Time
has an ID of UTC
.
BTW in my case, on SQL Server 2017 on Windows 10, I get 139 timezone names
Update
I forgot to add that Matt Johnson created a SQLCLR UDF that adds support for the IANA timezones through Noda Time. If you're serious about timezones you should probably use it instead of AT TIME ZONE
Upvotes: 3