В пакет SQL Server 2016 включена функция AT TIME ZONE, с помощью которой пользователь может указать целевой часовой пояс. А это в свою очередь дает системе SQL Server возможность динамически рассчитывать смещение часового пояса на основе реализованных в Windows правил преобразования часовых поясов.

Тип данных DATETIMEOFFSET позволяет сохранять значение локальной даты и времени, включая соответствующее смещение часовой зоны относительно универсального глобального времени (UTC). В языке программирования T-SQL имеются вспомогательные функции TODATETIMEOFFSET и SWITCHOFFSET. С их помощью пользователь может приводить не предусматривающее поправку на смещение значение плюс собственно смещение к этому типу, а также переключать смещение допускающего смещение значения к целевому смещению. Однако при работе с обеими функциями пользователь должен указывать статическое смещение для зоны нормативного времени, которое зависит от того, определялось ли нормативное время на базе стандартного или декретного времени. В пакете SQL Server 2016 предусмотрена функция AT TIME ZONE — гибкая альтернатива обоим рассматриваемым средствам. С помощью нового инструмента пользователь может задать имя целевого часового пояса и предоставить системе SQL Server возможность динамически рассчитать соответствующее смещение для часового пояса на основе используемых в Windows правил преобразования. Далее мы подробно рассмотрим эти три функции.

TODATETIMEOFFSET

Функция TODATETIMEOFFSET принимает два входных параметра — определенное без учета смещения значение даты и времени, а также смещение для целевого часового пояса — и просто объединяет эти две величины в значение DATETIMEOFFSET. Входное значение даты и времени представляет собой выражение, которое разрешается в значение DATETIME2. Смещение часового пояса — это либо строка, содержащая смещение в часах и минутах, например '-08:00', либо целое число, выражающее смещение в минутах, например -480. Эту функцию можно представить как вспомогательную, избавляющую пользователя от необходимости возиться с преобразованием входных данных в строки символов соответствующего формата, с их конкатенацией и преобразованием этих данных в тип данных DATETIMEOFFSET. Эту работу за него сделает SQL Server. Рассмотрим пример в листинге 1.

Этот код генерирует следующие выходные данные:

2016-08-12 12:00:00.0000000 -07:00

Рассмотрим одну из ситуаций, в которых эта функция может пригодиться. Допустим, в одном столбце таблицы вы сохранили локальные значения даты и времени, а в другом — значение смещения для часового пояса. Предположим также, что через какое-то время у вас возникла необходимость свести значения двух столбцов в одно значение DATETIMEOFFSET. Чтобы сгенерировать в целевом экземпляре SQL Server локальное значение даты и времени без поправки на смещение, вы используете такие функции, как SYSDATETIME (возвращает значение DATETIME2) или GETDATE (возвращает значение DATETIME). С целью получения смещения для текущего часового пояса вы используете такие функции, как DATENAME (возвращает строку со значением смещения, выраженным в часах и минутах) или DATEPART (возвращает целое число, представляющее выраженное в минутах смещение) с частью TZoffset (сокращенно tz). Пример показан в листинге 2.

Я выполнил этот фрагмент кода на своей системе, в которой в качестве часового пояса указан пояс Pacific Time, взятый в тот период, когда в нем использовалось стандартное время, и получил выходные данные, показанные на экране 1.

 

Выходные данные листинга 2
Экран 1. Выходные данные листинга 2

 

При использовании стандартного времени смещение по часовым поясам в моей системе составляет -08:00 (-480 минут): при использовании летнего времени оно составляет -07:00 (-420 минут).

SWITCHOFFSET

Назначение функции SWITCHOFF­SET отличается от назначения функции DATETIMEOFFSET. Она принимает два параметра — значение DATETIMEOFFSET и смещение по целевому часовому поясу — и переключает смещение исходного значения на целевое. Значение UTC во входных и выходных данных одно и то же; дело в том, что значение смещения во входных данных нужно изменить. Если первоначально они определялись исходным смещением, при котором данные записывались, теперь следует использовать желаемое целевое смещение. В листинге 3 показан пример использования данной функции.

Код листинга 3 генерирует следующие выходные данные:

2016-08-12 12:00:00.0000000 -07:00

Трудность при работе с данной функцией состоит в том, что пользователь должен знать, по какому целевому часовому поясу следует указывать смещение, а также в том, что смещение меняет свое значение в местах, где во время перехода со стандартного времени на летнее и обратно положение стрелок часов меняется. В приведенном выше примере я хотел изменить входное значение на значение, используемое в часовом поясе Pacific Time. Мне нужно было убедиться, что измененное значение приходится на период, когда используется летнее время, и поэтому для смещения в целевом часовом поясе нужно применять значение -07:00. Но, как вы можете заметить, если мы направляем запрос к таблице, в которой входные значения DATETIMEOFFSET хранятся в столбце, часть значений придется переключать на -07:00 (когда в целевом поясе используется летнее время), а часть — на -08:00 (когда в целевом поясе используется стандартное время).

AT TIME ZONE

Функция AT TIME ZONE была впервые реализована в версии SQL Server 2016. Она заменяет две функции: TODATETIMEOFFSET и SWITCHOFFSET. Ее синтаксис таков:

 AT TIME ZONE ''

Когда входное значение для этой функции есть значение даты и времени без смещения, она ведет себя так же, как функция TODATETIMEOFFSET; когда же входное значение — это значение даты и времени со смещением, данная функция ведет себя как функция SWITCHOFFSET. Более того, вам не нужно забивать себе голову вопросами, связанными с переводом стрелок часов. Достаточно просто указать имя стандартного целевого часового пояса (скажем, при использовании тихоокеанского поясного времени всегда пишите Pacific Standard Time), и SQL Server динамически определит смещение для соответствующего часового пояса на основе реализованных в Windows правил преобразования часовых поясов. Чтобы получить полный список поддерживаемых имен стандартных часовых поясов плюс текущее значение смещения от UTC для этих поясов, а также сведения о том, используется ли там в данный момент летнее время, запустите функцию sys.time_zone_info с помощью следующего кода:

SELECT *
   FROM sys.time_zone_info;

Выше я показал, как можно рассчитать смещение текущего часового пояса своей системы от значения UTC с помощью функций DATENAME или DATEPART, примененных к SYSDATETIMEOFFSET с частью TZoffset. К сожалению, такого же простого способа расчета стандартного имени текущего часового пояса системы, которое можно было бы записать с возможностью последующего использования с функцией AT TIME ZONE, не существует.

Один из вариантов достижения этой цели состоит в считывании имени часового пояса непосредственно из реестра с помощью расширенной хранимой процедуры xp_regread. Делается это так, как показано в листинге 4.

Однако данная хранимая процедура не поддерживается, так что данный метод не рекомендуется.

Более чистое поддерживаемое решение предполагает создание пользовательской функции CLR, которая вызывает метод System.TimeZone.CurrentTimeZone.StandardName. С помощью представленного в листинге 5 фрагмента кода на языке C# вы можете определить подобную функцию с именем CurrentTimeZoneStandardName.

Допустим, вы создали с этой сборкой файл dll с именем C:\Temp\TimeZoneFunctions\TimeZoneFunctions\bin\Debug\TimeZoneFunctions.dll. Его можно развернуть в системе SQL Server с помощью кода из листинга 6.

По завершении процесса развертывания проверьте работу функции с помощью следующего кода:

SELECT
   dbo.CurrentTimeZoneStandardName ()
   AS currenttimezonestandardname;

На экране моего компьютера были отображены следующие выходные данные:

Pacific Standard Time

С помощью следующего кода вы сможете получить более подробные сведения о текущем часовом поясе своей системы:

SELECT *
FROM sys.time_zone_info
WHERE name =
   dbo.CurrentTimeZoneStandardName ();

Я получил выходные данные как на экране 2.

 

Подробные сведения о текущем часовом поясе
Экран 2. Подробные сведения о текущем часовом поясе

 

Как уже отмечалось, в случае применения функции AT TIME ZONE к значению даты и времени, не откорректированному с помощью параметра смещения, эта функция ведет себя как динамическая функция TODATETIMEOFFSET. Слово «динамическая» в данном контексте предполагает, что вы указываете имя стандартного часового пояса, а решение вопроса о том, какое смещение для перехода в целевой часовой пояс следует применить, берет на себя SQL Server. В случаях, когда параметры интерпретируются однозначно, местное время в выходных данных берется соответствующим местному времени во входных данных, а смещение для выходных данных определяется в соответствии с тем, используется ли в часовом поясе с указанным именем стандартное или летнее время. Однако существует два периода, в течение которых местное значение и смещение для часового пояса, которое следует использовать, определяются неоднозначно. Для вычисления значений, приходящихся на эти периоды неоднозначности, функция AT TIME ZONE предполагает использование особых правил.

Один из таких периодов — момент перехода от стандартного времени к летнему (в это время стрелки часов обычно переводятся на час вперед); в местном времени образуется «разрыв», продолжительность которого обычно составляет один час. Пример: в 2016 году при переходе от тихоокеанского стандартного времени к тихоокеанскому летнему времени показатели тихоокеанского поясного времени были увеличены на один час в '20160313 02:00:00.0000000' (стрелки часов были переведены с 02:00 на 03:00). Если входное значение приходится на пропущенный час и составляет, скажем, 02:30, функция AT TIME ZONE переводит местное время в выходных данных на один час вперед (на 03:30 в нашем примере) и применяет смещение после изменения времени (PDT: -07:00 в нашем примере).

Другой такой период наступает при переходе от летнего времени к стандартному (стрелки часов обычно переводятся на час назад). При этом в местном времени появляется час, который как бы повторяет себя. Так, в 2016 году при переходе от тихоокеанского летнего времени к тихоокеанскому стандартному времени показатели тихоокеанского поясного времени были уменьшены на один час в '20161106 02:00:00.0000000' (стрелки часов были переведены с 02:00 на 01:00). Если входное значение приходится на повторяющийся час и составляет, к примеру, 01:30, функция AT TIME ZONE приводит местное время в выходных данных в соответствие со временем во входных данных (на 01:30 в нашем примере) и применяет смещение перед изменением времени (PDT: -07:00 в нашем примере).

Код листинга 7 демонстрирует преобразование как однозначных, так и неоднозначных значений.

Этот код генерирует выходные данные, показанные на экране 3.

 

Результат преобразования как однозначных, так и неоднозначных значений
Экран 3. Результат преобразования как однозначных, так и неоднозначных значений

 

В часовых поясах, где стрелки часов не переводятся и используется, к примеру, стандартное горное время US Mountain Standard Time (применяется, скажем, в Аризоне), неоднозначных ситуаций не возникает. Значение местного времени в выходных данных устанавливается в соответствии со значением времени во входных данных и используется применяемое в указанном часовом поясе смещение (-07:00 в данном примере).

AT TIME ZONE как динамическая функция SWITCHOFFSET

Когда входное значение принадлежит к типу DATETIMEOFFSET, функция AT TIME ZONE ведет себя как динамическая функция SWITCHOFFSET. Она изменяет смещение часового пояса входного значения на смещение целевого именованного часового пояса в зависимости от того, приходится ли целевое значение на период использования летнего или стандартного времени. Рассмотрим пример из листинга 8.

Код листинга 8 генерирует выходные данные, показанные на экране 4.

 

Результаты выполнения листинга 8
Экран 4. Результаты выполнения листинга 8

 

Исходные значения были зафиксированы системой, настроенной для работы с тихоокеанским поясным временем, где смещение составляет -08:00 в период использования тихоокеанского стандартного времени Pacific Standard Time (PST) для значений @dt1, @dt2 и @dt6 и где смещение составляет -07:00 в период использования тихоокеанского летнего времени Pacific Daylight Time (PDT) для значений @dt3, @dt4 и @dt5. В этом примере значения входных данных преобразуются в значения восточного времени целевого часового пояса (стандартное наименование «восточное стандартное время», или Eastern Standard Time, EST). В период использования восточного стандартного времени (EST) смещение должно иметь значение -05:00, а в период использования восточного летнего времени (EDT) смещение должно иметь значение -04:00. Следующие коррективы должны применяться к различным входным значениям для перевода их в целевой часовой пояс.

  • @dt1. Как входные, так и выходные значения приходятся на период использования стандартного времени. Локальное значение корректируется путем перевода на 3 часа вперед.
  • @dt2. Входное значение приходится на период использования стандартного времени, а выходное значение — на период использования летнего времени. Локальное значение корректируется путем перевода на 4 часа вперед.
  • @dt3. Как входное, так и выходное значения приходятся на период использования летнего времени. Локальное значение корректируется путем перевода на 3 часа вперед.
  • @dt4. Как входное, так и выходное значения приходятся на период использования летнего времени. Локальное значение корректируется путем перевода на 3 часа вперед.
  • @dt5. Входное значение приходится на период использования летнего времени, а выходное значение — на период использования стандартного времени. Локальное значение корректируется путем перевода на 2 часа вперед.
  • @dt6. Как входное, так и выходное значения приходятся на период использования стандартного времени. Локальное значение корректируется путем перевода на 3 часа вперед.

В часовых поясах, где значение смещения не изменяется и используется, скажем, стандартное горное время (US Mountain Standard Time), локальное время корректируется с помощью различия по смещению между исходным и целевым значениями смещения. Если в рассмотренном выше запросе имя целевого часового пояса заменить на US Mountain Standard Time, мы получим следующий результат.

  • Исходные значения со смещением -08:00 корректируются с использованием смещения -07:00.
  • Исходные значения со смещением 07:00 остаются неизменными и возвращаются со смещением -07:00.

С помощью функции AT TIME ZONE мы можем сформировать обычный запрос, возвращающий текущее значение DATETIMEOFFSET в заданном целевом часовом поясе. Для этого нужно применить функцию AT TIME ZONE к значению, возвращаемому функцией SYSDATETIMEOFFSET с именем целевого часового пояса. Так, текущее значение DATETIMEOFFSET со смещением, отражающим тихо­океанское время, можно представить с помощью кода, показанного в листинге 9.

Запустив данный код на своей системе, я получил следующий результат:

2016-11-13 23:30:26.3248001 -08:00

Множественные преобразования при использовании функции AT TIME ZONE

Бывают случаи, когда при использовании функции AT TIME ZONE возникает необходимость осуществить два преобразования. Рассмотрим ситуацию, когда в системе хранится некорректируемое по смещению значение даты и времени в том или ином часовом поясе и нужно возвратить его в виде значения даты и времени со смещением в целевом часовом поясе. Допустим, у нас зафиксировано некорректируемое по смещению значение даты и времени в часовом поясе UTC и мы хотим возвратить его как значение смещения для часового пояса, где используется тихоокеанское время. Мы выполним первое преобразование от некорректируемого по смещению значения к корректируемому значению, указав, что в нем используется часовой пояс UTC, а затем второе преобразование для перехода к тихоокеанскому времени. Все это показано в листинге 10.

При запуске данного кода генерируется следующий результат:

2016-08-12 05:00:00.0000000 -07:00

При выполнении вычислений с использованием значений DATETIMEOFFSET могут возникать весьма запутанные ситуации. До появления функции AT TIME ZONE двумя основными инструментами администратора были функции TODATETIMEOFFSET и SWITCHOFFSET. В обоих случаях нам приходилось работать со статическим целевым смещением, при этом, если администратор хотел знать, какое смещение следует применить, он должен был иметь четкое представление о том, какое время используется в данный момент, стандартное или летнее. Функция AT TIME ZONE, впервые реализованная в версии SQL Server 2016, заменяет обе старые функции. Теперь мы можем работать со стандартным именем целевого часового пояса и динамически вычислять целевое смещение на основе правил преобразования часовых поясов, реализованных в Windows. Если сейчас чего-то и не хватает, так это возможности рассчитывать имя текущего часового пояса системы. Будет просто замечательно, если такая возможность появится в T-SQL в дальнейшем — скажем, в виде нового свойства в функции SERVERPROPERTY.

Листинг 1. Пример преобразования данных в тип данных DATETIMEOFFSET
DECLARE
  @dt       AS DATETIME2 = '20160812 12:00:00.0000000',
  @tzoffset AS CHAR(6)   = '-07:00'; -- or use the integer -420

SELECT TODATETIMEOFFSET(@dt, @tzoffset);
Листинг 2. Получение смещения для текущего часового пояса
SELECT
  SYSDATETIME() AS curdatetime,
  DATENAME(TZoffset, SYSDATETIMEOFFSET()) AS curtzoffsetstr,
  DATEPART(TZoffset, SYSDATETIMEOFFSET()) AS curtzoffsetint;
Листинг 3. Использование функции SWITCHOFFSET
DECLARE
  @dt       AS DATETIMEOFFSET = '20160812 15:00:00.0000000 -04:00',
  @tzoffset AS CHAR(6)        = '-07:00'; -- or use the integer -420

SELECT SWITCHOFFSET(@dt, @tzoffset);
Листинг 4. Считывание имени часового пояса из реестра с помощью xp_regread
DECLARE @tzname AS sysname;

EXEC master.dbo.xp_regread
  'HKEY_LOCAL_MACHINE',
  'SYSTEM\CurrentControlSet\Control\TimeZoneInformation',
  'TimeZoneKeyName',
  @tzname OUTPUT;

SELECT @tzname;
Листинг 5. Определение функции с именем CurrentTimeZoneStandardName
using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

public partial class TimeZoneFunctions
{
    [SqlFunction(IsDeterministic = false, DataAccess = DataAccessKind.None)]
    public static SqlString CurrentTimeZoneStandardName()
    {
        return TimeZone.CurrentTimeZone.StandardName;
    }
}
Листинг 6. Развертывание файла в системе SQL Server
-- Не забудьте активировать CLR
EXEC sys.sp_configure 'CLR Enabled', 1;
RECONFIGURE WITH OVERRIDE;

IF DB_ID('testtimezone') IS NULL CREATE DATABASE testtimezone;
GO

USE testtimezone; -- замените своей базой данных
DROP FUNCTION IF EXISTS dbo.CurrentTimeZoneStandardName;
DROP ASSEMBLY IF EXISTS TimeZoneFunctions;

CREATE ASSEMBLY TimeZoneFunctions
FROM 'C:\Temp\TimeZoneFunctions\TimeZoneFunctions\bin\Debug\TimeZoneFunctions.dll';
-- или вместо файла .dll file используйте:
/*

CREATE ASSEMBLY TimeZoneFunctions

FROM 0x4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C010300CC192A580000000000000000E00002210B010B000008000000060000000000005E2600000020 000000400000000000100020000000020000040000000000000006000000000000000080000000020000000000000300608500001000001000000000100000100000000000001000000000000000000000000C2600004F00000000400000C802000000000000000000000000000000000000006000000C000000D42400001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E7465787400000064060000002000000008000000020000000000000000000000000000200000602E72737263000000C80200000040000000040000000A0000000000000000000000000000400000402E72656C6F6300000C0000000060000000020000000E00000000000000000000000000004000004200000000000000000000000000000000402600000000000048000000020005007C20000058040000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013300100150000000100001100280500000A6F0600000A280700000A0A2B00062A1E02280800000A2A00000042534A4201000100000000000C00000076342E302E33303331390000000005006C00000038010000237E0000A40100009401000023537472696E67730000000038030000080000002355530040030000100000002347554944000000500300000801000023426C6F620000000000000002000001471402000900000000FA25330016000001000000080000000200000002000000080000000400000001000000010000000200000000000A00010000000000060042003B000A006A0055000600A90096000F00BD0000000600EC00CC0006000C01CC000A0045012A0106005A013B0000000000010000000000010001000100100020000000050001000100502000000000960074000A000100712000000000861890000F000100190090001300290090001900310090000F00390090000F0041006301C20041007701C70011008801CB00090090000F00200023001E002E000B00D6002E001300DF002E001B00E800D10004800000000000000000000000000000000020000000040000000000000000000000010032000000000004000000000000000000000001004900000000000000003C4D6F64756C653E0054696D655A6F6E6546756E6374696F6E732E646C6C0054696D655A6F6E6546756E6374696F6E73006D73636F726C69620053797374656D004F626A6563740053797374656D2E446174610053797374656D2E446174612E53716C54797065730053716C537472696E670043757272656E7454696D655A6F6E655374616E646172644E616D65002E63746F720053797374656D2E446961676E6F73746963730044656275676761626C6541747472696275746500446562756767696E674D6F6465730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300436F6D70696C6174696F6E52656C61786174696F6E734174747269627574650052756E74696D65436F6D7061746962696C697479417474726962757465004D6963726F736F66742E53716C5365727665722E5365727665720053716C46756E6374696F6E4174747269627574650054696D655A6F6E65006765745F43757272656E7454696D655A6F6E65006765745F5374616E646172644E616D65006F705F496D706C6963697400000320000000000019B6CD3E12695E41B750764D1CB79E930008B77A5C561934E089040000110903200001052001011111042001010880A20100020054020F497344657465726D696E6973746963005455794D6963726F736F66742E53716C5365727665722E5365727665722E446174614163636573734B696E642C2053797374656D2E446174612C2056657273696F6E3D342E302E302E302C2043756C747572653D6E65757472616C2C205075626C69634B6579546F6B656E3D623737613563353631393334653038390A446174614163636573730000000004000012210320000E05000111090E04070111090801000701000000000801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F7773010000000000CC192A5800000000020000001C010000F0240000F0060000525344531AA004ED8F58E3438101DE590490937C01000000633A5C54656D705C54696D655A6F6E6546756E6374696F6E735C54696D655A6F6E6546756E6374696F6E735C6F626A5C44656275675C54696D655A6F6E6546756E6374696F6E732E70646200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003426000000000000000000004E26000000200000000000000000000000000000000000000000000040260000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF250020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001001000000018000080000000000000000000000000000001000100000030000080000000000000000000000000000001000000000048000000584000006C02000000000000000000006C0234000000560053005F00560045005200530049004F004E005F0049004E0046004F0000000000BD04EFFE00000100000000000000000000000000000000003F000000000000000400000002000000000000000000000000000000440000000100560061007200460069006C00650049006E0066006F00000000002400040000005400720061006E0073006C006100740069006F006E00000000000000B004CC010000010053007400720069006E006700460069006C00650049006E0066006F000000A801000001003000300030003000300034006200300000002C0002000100460069006C0065004400650073006300720069007000740069006F006E000000000020000000300008000100460069006C006500560065007200730069006F006E000000000030002E0030002E0030002E00300000004C001600010049006E007400650072006E0061006C004E0061006D0065000000540069006D0065005A006F006E006500460075006E006300740069006F006E0073002E0064006C006C0000002800020001004C006500670061006C0043006F0070007900720069006700680074000000200000005400160001004F0072006900670069006E0061006C00460069006C0065006E0061006D0065000000540069006D0065005A006F006E006500460075006E006300740069006F006E0073002E0064006C006C000000340008000100500072006F006400750063007400560065007200730069006F006E00000030002E0030002E0030002E003000000038000800010041007300730065006D0062006C0079002000560065007200730069006F006E00000030002E0030002E0030002E003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000C000000603600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;
*/
GO

CREATE FUNCTION dbo.CurrentTimeZoneStandardName() RETURNS sysname EXTERNAL NAME TimeZoneFunctions.TimeZoneFunctions.CurrentTimeZoneStandardName;
GO
Листинг 7. Преобразование однозначных и неоднозначных значений
DECLARE
  @dt1 AS DATETIME2 = ‘20160212 12:00:00.0000000’,
  @dt2 AS DATETIME2 = ‘20160313 02:30:00.0000000’,
  @dt3 AS DATETIME2 = ‘20160812 12:00:00.0000000’,
  @dt4 AS DATETIME2 = ‘20161106 01:30:00.0000000’,
  @timezone AS sysname   = ‘Pacific Standard Time’;

SELECT *
FROM ( VALUES
  (@dt1, @dt1 AT TIME ZONE @timezone),
  (@dt2, @dt2 AT TIME ZONE @timezone),
  (@dt3, @dt3 AT TIME ZONE @timezone),
  (@dt4, @dt4 AT TIME ZONE @timezone)  ) AS D(dt, dto);
Листинг 8. Пример, в котором AT TIME ZONE ведет себя как SWITCHOFFSET
DECLARE
  @dt1 AS DATETIMEOFFSET = ‘20160212 12:00 -08:00’, -- before PST->PDT
  @dt2 AS DATETIMEOFFSET = ‘20160313 01:30 -08:00’, -- before PST->PDT, after EST->EDT
  @dt3 AS DATETIMEOFFSET = ‘20160313 03:30 -07:00’, -- after PST->PDT
  @dt4 AS DATETIMEOFFSET = ‘20160812 12:00 -07:00’, -- before PDT->PST
  @dt5 AS DATETIMEOFFSET = ‘20161106 01:30 -07:00’, -- before PDT->PST, after EDT->EST
  @dt6 AS DATETIMEOFFSET = ‘20161106 01:30 -08:00’, -- after PDT->PST
  @timezone AS sysname   = ‘Eastern Standard Time’;

SELECT *
FROM ( VALUES
  (@dt1, @dt1 AT TIME ZONE @timezone),
  (@dt2, @dt2 AT TIME ZONE @timezone),
  (@dt3, @dt3 AT TIME ZONE @timezone),
  (@dt4, @dt4 AT TIME ZONE @timezone),
  (@dt5, @dt5 AT TIME ZONE @timezone),
  (@dt6, @dt6 AT TIME ZONE @timezone)  ) AS D(dtosrc, dtotgt);
Листинг 9. Запуск AT TIME ZONE с возвращаемым функцией SYSDATETIMEOFFSET значением
DECLARE @targettimezone AS sysname = ‘Pacific Standard Time’;
SELECT SYSDATETIMEOFFSET() AT TIME ZONE @targettimezone;
Листинг 10. Пример выполнения двух преобразований в AT TIME ZONE
DECLARE
  @dt             AS DATETIME2 = ‘20160812 12:00:00.0000000’,
  @sourcetimezone AS sysname   = ‘UTC’,
  @targettimezone AS sysname   = ‘Pacific Standard Time’;
SELECT @dt AT TIME ZONE @sourcetimezone AT TIME ZONE @targettimezone;