TSQL: Как перевести местное время в UTC? (SQL Server 2008)

82

Мы имеем дело с приложением, которое должно обрабатывать глобальные данные о времени из разных часовых поясов и настройки перехода на летнее время. Идея состоит в том, чтобы хранить все в формате UTC внутри и конвертировать только туда и обратно для локализованных пользовательских интерфейсов. Предлагает ли SQL Server какие-либо механизмы для работы с переводами с учетом времени, страны и часового пояса?

Это должно быть распространенная проблема, поэтому я удивлен, что Google не нашел ничего полезного.

Есть указатели?

BuschnicK
источник
У меня есть mssql-сервер, связанный с mysql-сервером. Интересно, можно ли запускать mysql CONVERT_TZ (time, srczone, dstzone) по запросам :-) Странно, что эта функция отсутствует; он встроен в Linux.
Лейф Неланд
1
@BuschnicK См. Мой ответ ниже. На самом деле, я думаю, вы можете принять это, даже если другим будет легче найти.
Петр Овсяк
Краткий ответ: до SQL Server 2016 встроенного способа сделать это не существует, поэтому для более ранних версий потребуется специальный код.
Лехистер
Средний ответ: все функции смещения времени до SQL Server 2016 работали только с абсолютными смещениями, без поддержки переменных смещений, которые происходят в большинстве часовых поясов из-за перехода на летнее время. Запросы не имеют никакого способа получить доступ к смещениям времени, за исключением того, что является текущим смещением местного времени сервера, что бесполезно для попытки автоматизировать преобразование.
Лехистер

Ответы:

49

Прошло 7 лет, и ... на
самом деле появилась новая функция SQL Server 2016, которая делает именно то, что вам нужно.
Он называется AT TIME ZONE и преобразует дату в указанный часовой пояс с учетом изменений летнего времени (DST).
Подробнее здесь: https://msdn.microsoft.com/en-us/library/mt612795.aspx

Петр Овсяк
источник
19
Больше не работаю над этим - ни в этом проекте, ни в SQL Server, ни в той же компании, ни даже в той же стране ;-) Так что это мне не поможет, но я буду поддерживать его, если люди найдут этот вопрос в настоящее время.
BuschnicK
2
@BuschnicK да, я полагаю, но я пришел сюда в поисках решения той же проблемы, что и у вас, поэтому я решил опубликовать ответ сейчас, когда есть реальное решение: D
Петр Овсяк
5
Вопрос для SQL Server 2008. Обновите вопрос, если вы принимаете этот ответ. Спасибо
Роберт
1
Чтобы преобразовать в UTC, вы можете сделать «В ЧАСОВОЙ ПОЯС» UTC.
Krzyserious
1
@Piotr Owsiak: Да, но предполагается, что время ввода - UTC, что бессмысленно, если вы хотите преобразовать местное время в UTC ... Итак, прошло 7 лет, а они все еще не думают, что стоит обрабатывать это должным образом. ..
Stefan Steiger
65

Это работает для дат, которые в настоящее время имеют такое же смещение в формате UTC, что и хост SQL Server; он не учитывает изменения летнего времени. YOUR_DATEДля преобразования замените местную дату.

SELECT DATEADD(second, DATEDIFF(second, GETDATE(), GETUTCDATE()), YOUR_DATE);

Shaig
источник
2
Спасибо, это хорошая идея, но она работает только для одного часового пояса - локальной машины. Нам нужно, чтобы он работал для произвольных часовых
поясов
51
Это не учитывает переход на летнее время,
Габриэль Макадамс,
10
Нет! Разница зависит от точной даты. Это зависит от перехода на летнее время.
usr
3
В моем случае мне просто нужен был 1 часовой пояс, и это отлично сработало! Благодарю.
M Thelen
10
Это хорошо работает, если вы знаете, что ваш сегодняшний бег не исторически известен, спасибо,
Дэвид Адлингтон
22

Хотя некоторые из этих ответов дадут вам приблизительную оценку, вы не можете делать то, что пытаетесь сделать с произвольными датами для SqlServer 2005 и ранее, из-за перехода на летнее время. Использование разницы между текущим локальным и текущим UTC даст мне смещение, которое существует сегодня. Я не нашел способа определить, каким было бы смещение для рассматриваемой даты.

Тем не менее, я знаю, что SqlServer 2008 предоставляет некоторые новые функции даты, которые могут решить эту проблему, но люди, использующие более раннюю версию, должны знать об ограничениях.

Наш подход состоит в том, чтобы сохранять UTC и выполнять преобразование на стороне клиента, где у нас есть больший контроль над точностью преобразования.

user890155
источник
16

Для SQL Server 2016 и новее, а также для базы данных SQL Azure используйте встроенный AT TIME ZONEоператор .

Для более старых выпусков SQL Server вы можете использовать мой проект поддержки часовых поясов SQL Server для преобразования между стандартными часовыми поясами IANA, как указано здесь .

UTC в Local выглядит так:

SELECT Tzdb.UtcToLocal('2015-07-01 00:00:00', 'America/Los_Angeles')

Локально для UTC выглядит так:

SELECT Tzdb.LocalToUtc('2015-07-01 00:00:00', 'America/Los_Angeles', 1, 1)

Числовые параметры - это флаг для управления поведением, когда на значения местного времени влияет переход на летнее время. Они подробно описаны в документации по проекту.

Мэтт Джонсон-Пинт
источник
1
Как человек ... этот проект Мэтта великолепен и не требует CLR. Мэтт заслуживает большой похвалы за это +100
бакли
14

SQL Server 2008 имеет тип, называемый datetimeoffset. Это действительно полезно для такого рода вещей.

http://msdn.microsoft.com/en-us/library/bb630289.aspx

Затем вы можете использовать эту функцию, SWITCHOFFSETчтобы переместить его из одного часового пояса в другой, сохраняя при этом то же значение UTC.

http://msdn.microsoft.com/en-us/library/bb677244.aspx

Роб

Роб Фарли
источник
6
SWITCHOFFSET не учитывает летнее время, поэтому полезен только в некоторых ситуациях.
robocat
2
Нет. Но вопрос был в том, сможет ли переключиться на любой часовой пояс, который был запрошен.
Роб Фарли
Из вопроса "разные часовые пояса и настройки летнего времени". Мы тоже ищем решение для местного времени. Ваше предложение не решает проблему перехода на летнее время?
robocat
Каждый раз, когда вам нужно определить часовой пояс на клиенте, а затем получить время возврата базы данных в указанном часовом поясе, очень полезна функция SWITCHOFFSET.
Роб Фарли
3
@RobFarley Определение часового пояса на клиенте и использование SWITCHOFFSET все равно может привести к ошибкам. Вам нужно знать, применено ли к дате и времени, которое вы конвертируете, летнее время или нет. Простое определение часового пояса и применение текущего смещения к всемирному координированному времени может привести к отсрочке на час - и это в простом случае, когда все ваши преобразования происходят в одной стране. Не все страны переключаются на летнее время в одни и те же дни. SWITCHOFFSET отлично работает, если вы храните местное время и знаете разницу между исходной и целевой зоной в одной стране.
JamieSee
12

Вот код для преобразования одной зоны DateTimeв другую зонуDateTime

DECLARE @UTCDateTime DATETIME = GETUTCDATE();
DECLARE @ConvertedZoneDateTime DATETIME;

-- 'UTC' to 'India Standard Time' DATETIME
SET @ConvertedZoneDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE 'India Standard Time'
SELECT @UTCDateTime AS UTCDATE,@ConvertedZoneDateTime AS IndiaStandardTime

-- 'India Standard Time' to 'UTC' DATETIME
SET @UTCDateTime = @ConvertedZoneDateTime AT TIME ZONE 'India Standard Time' AT TIME ZONE 'UTC'
SELECT @ConvertedZoneDateTime AS IndiaStandardTime,@UTCDateTime AS UTCDATE

Примечание : AT TIME ZONE работает только на SQL Server 2016+, и преимущество состоит в том, что он автоматически учитывает дневной свет при преобразовании в определенный часовой пояс.

Картикеян
источник
2
Мне это нравится, хотя бы по той причине, что он показывает, что вы можете AT TIME ZONEобъединить несколько вызовов (фраз?) Вместе! Просто элегантно. Ранее я сказал, что stackoverflow.com/a/44579178/112764 отвечает моим потребностям, но это даже лучше. Большая честь.
NateJ
DECLARE @UTCDateTime DATETIME = GETUTCDATE(); DECLARE @ConvertedZoneDateTime DATETIME; -- 'UTC' to 'India Standard Time' to 'Eastern Standard Time' DATETIME SET @ConvertedZoneDateTime = @UTCDateTime AT TIME ZONE 'UTC' AT TIME ZONE 'India Standard Time' AT TIME ZONE 'Eastern Standard Time' SELECT @UTCDateTime AS UTCDATE,@ConvertedZoneDateTime AS EasternStandardTime Да, вы можете объединить несколько AT TIME ZONEвызовов в цепочку , но From и To достаточно для любой конверсии, и большинство из них нам нужно
KarthikeyanMlp
4

Я склоняюсь к использованию DateTimeOffset для всего хранения даты и времени, которое не связано с местным событием (например, встреча / вечеринка и т. Д., С 12 до 15 в музее).

Чтобы получить текущий DTO как UTC:

DECLARE @utcNow DATETIMEOFFSET = CONVERT(DATETIMEOFFSET, SYSUTCDATETIME())
DECLARE @utcToday DATE = CONVERT(DATE, @utcNow);
DECLARE @utcTomorrow DATE = DATEADD(D, 1, @utcNow);
SELECT  @utcToday [today]
        ,@utcTomorrow [tomorrow]
        ,@utcNow [utcNow]

ПРИМЕЧАНИЕ. Я всегда буду использовать UTC при отправке по сети ... JS на стороне клиента может легко добраться до / из местного UTC. Смотрите: new Date().toJSON()...

Следующий JS будет обрабатывать синтаксический анализ даты UTC / GMT в формате ISO8601 в локальную дату и время.

if (typeof Date.fromISOString != 'function') {
  //method to handle conversion from an ISO-8601 style string to a Date object
  //  Date.fromISOString("2009-07-03T16:09:45Z")
  //    Fri Jul 03 2009 09:09:45 GMT-0700
  Date.fromISOString = function(input) {
    var date = new Date(input); //EcmaScript5 includes ISO-8601 style parsing
    if (!isNaN(date)) return date;

    //early shorting of invalid input
    if (typeof input !== "string" || input.length < 10 || input.length > 40) return null;

    var iso8601Format = /^(\d{4})-(\d{2})-(\d{2})((([T ](\d{2}):(\d{2})(:(\d{2})(\.(\d{1,12}))?)?)?)?)?([Zz]|([-+])(\d{2})\:?(\d{2}))?$/;

    //normalize input
    var input = input.toString().replace(/^\s+/,'').replace(/\s+$/,'');

    if (!iso8601Format.test(input))
      return null; //invalid format

    var d = input.match(iso8601Format);
    var offset = 0;

    date = new Date(+d[1], +d[2]-1, +d[3], +d[7] || 0, +d[8] || 0, +d[10] || 0, Math.round(+("0." + (d[12] || 0)) * 1000));

    //use specified offset
    if (d[13] == 'Z') offset = 0-date.getTimezoneOffset();
    else if (d[13]) offset = ((parseInt(d[15],10) * 60) + (parseInt(d[16],10)) * ((d[14] == '-') ? 1 : -1)) - date.getTimezoneOffset();

    date.setTime(date.getTime() + (offset * 60000));

    if (date.getTime() <= new Date(-62135571600000).getTime()) // CLR DateTime.MinValue
      return null;

    return date;
  };
}
Tracker1
источник
+1 Я тоже перешел на DateTimeOffset. Это позволяет избежать ряда проблем с локальными преобразованиями в формате UTC +. Однако по тем же причинам я рекомендую также отправлять значения со смещением по сети (через JSON).
user2864740 07
В качестве примечания, я делаю то же, что и вы, с точностью до наоборот. Для DateTimes, привязанных к локальному событию, я храню DateTimeOffset. Для DateTime, не привязанного к локальному событию, я сохраняю DateTime в формате UTC. У первого есть две соответствующие точки данных (когда это по местному времени и какое это по местному времени), у последнего только одна (когда)
Martijn
@Martijn Но часовой пояс не дает вам местоположение, и вы все равно должны хранить его отдельно.
Tracker1
@ tracker1 Это работает только тогда, когда у местоположения есть способ узнать свой часовой пояс, и даже в этом случае преобразовать его очень сложно.
Martijn
3

Да, в некоторой степени, как описано здесь .
Подход, который я использовал (до 2008 г.), - это преобразование в бизнес-логику .NET перед вставкой в ​​БД.

AdaTheDev
источник
1

Вы можете использовать функцию GETUTCDATE (), чтобы получить дату и время в формате UTC. Возможно, вы можете выбрать разницу между GETUTCDATE () и GETDATE () и использовать эту разницу, чтобы скорректировать свои даты по UTC.

Но я согласен с предыдущим сообщением, что гораздо проще контролировать правильное datetime на бизнес-уровне (например, в .NET).

Богдан_Ч
источник
12
Нет! Разница зависит от точной даты. Это зависит от перехода на летнее время.
usr
3
Не учитывает летнее время. Некоторое время я использовал подобные решения, и это вызвало серьезные проблемы. Вы должны определить, находится ли дата, с которой вы сравниваете, летнее время.
Джефф Дэвис
-1

Пример использования:

SELECT
    Getdate=GETDATE()
    ,SysDateTimeOffset=SYSDATETIMEOFFSET()
    ,SWITCHOFFSET=SWITCHOFFSET(SYSDATETIMEOFFSET(),0)
    ,GetutcDate=GETUTCDATE()
GO

Возврат:

Getdate SysDateTimeOffset   SWITCHOFFSET    GetutcDate
2013-12-06 15:54:55.373 2013-12-06 15:54:55.3765498 -08:00  2013-12-06 23:54:55.3765498 +00:00  2013-12-06 23:54:55.373
Роберт Кантор
источник