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


82

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

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

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


У меня есть 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);


2
Спасибо, это хорошая идея, но она работает только для одного часового пояса - локальной машины. Нам нужно, чтобы он работал для произвольных часовых
— поясов

51
Это не учитывает переход на летнее время,
— Габриэль Макадамс,

10
Нет! Разница зависит от точной даты. Это зависит от перехода на летнее время.
— usr

3
В моем случае мне просто нужен был 1 часовой пояс, и это отлично сработало! Благодарю.
— M Thelen

10
Это хорошо работает, если вы знаете, что ваш сегодняшний бег не исторически известен, спасибо,
— Дэвид Адлингтон

22

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

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

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


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;
  };
}

+1 Я тоже перешел на DateTimeOffset. Это позволяет избежать ряда проблем с локальными преобразованиями в формате UTC +. Однако по тем же причинам я рекомендую также отправлять значения со смещением по сети (через JSON).
— user2864740 07

В качестве примечания, я делаю то же, что и вы, с точностью до наоборот. Для DateTimes, привязанных к локальному событию, я храню DateTimeOffset. Для DateTime, не привязанного к локальному событию, я сохраняю DateTime в формате UTC. У первого есть две соответствующие точки данных (когда это по местному времени и какое это по местному времени), у последнего только одна (когда)
— Martijn

@Martijn Но часовой пояс не дает вам местоположение, и вы все равно должны хранить его отдельно.
— Tracker1

@ tracker1 Это работает только тогда, когда у местоположения есть способ узнать свой часовой пояс, и даже в этом случае преобразовать его очень сложно.
— Martijn

3

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


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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.