TSQL: Як перевести місцевий час у UTC? (SQL Server 2008)


82

Ми маємо справу з додатком, який повинен обробляти глобальні часові дані з різних часових поясів та налаштування переходу на літній час. Ідея полягає в тому, щоб все зберігати у форматі UTC внутрішньо і конвертувати лише вперед і назад для локалізованих користувацьких інтерфейсів. Чи пропонує SQL Server якісь механізми роботи з перекладами, заданими часом, країною та часовим поясом?

Це, мабуть, поширена проблема, тому я здивований, що Google не виявить нічого корисного.

Будь-які вказівники?


У мене mssql-сервер пов’язаний з mysql-сервером. Цікаво, чи можна запустити mysql CONVERT_TZ (time, srczone, dstzone) за запитами :-) Дивно, ця функція відсутня; він вбудований в Linux.
Лейф Неланд

1
@BuschnicK Дивіться мою відповідь нижче. Насправді я думаю, що ви можете прийняти це, навіть для того, щоб іншим було легше знайти.
Piotr Owsiak

Коротка відповідь: немає вбудованого способу зробити це до SQL Server 2016, тому для попередніх версій потрібен спеціальний код.
lehiester

Середня відповідь: всі функції часового зсуву до SQL Server 2016 працювали лише з абсолютними зсувами, не підтримуючи змінних зсувів, які відбуваються в більшості часових поясів через перехід на літній час. Запити не мають жодного способу доступу до зсувів часу, за винятком того, що є поточним зміщенням місцевого часу сервера, що марно для спроб автоматизувати перетворення.
lehiester

Відповіді:


49

Минуло 7 років, і ...
насправді є нова функція SQL Server 2016, яка робить саме те, що вам потрібно.
Він називається AT TIME ZONE, і він перетворює дату в заданий часовий пояс з урахуванням змін літнього часу (літнього часу).
Більше інформації тут: https://msdn.microsoft.com/en-us/library/mt612795.aspx


19
Більше не працюю над цим - ні на тому проекті, ні на SQL Server, ні в тій самій компанії, ні навіть в тій самій країні ;-) Так що це мені не допоможе, але я підтримую для людей, які знайдуть це питання зараз.
BuschnicK

2
@BuschnicK так, я думаю, але я прийшов сюди, шукаючи рішення тієї самої проблеми, що і у вас, тому я вирішив опублікувати відповідача зараз, коли є реальне рішення: D
Piotr Owsiak,

5
Питання стосується SQL Server 2008. Будь ласка, оновіть питання, якщо ви приймаєте цю відповідь. Thx
Роберт

1
Для конвертації в UTC ви можете зробити 'AT TIME ZONE' UTC '.
Krzyserious 21.03.18

1
@Piotr Owsiak: Так, але він передбачає, що введеним часом є UTC, що безглуздо, якщо ви хочете перевести місцевий час у UTC ... Отже, минуло 7 років, і вони все ще не вважають, що варто робити це належним чином. ..
Стефан Штайгер

65

Це працює для дат, які в даний час мають такий самий зсув UTC, як і хост SQL Server; це не враховує зміни переходу на літній час. Замініть YOUR_DATEмісцевою датою для перетворення.

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


2
Дякую, це гарна ідея, але вона працює лише для одного часового поясу - локальної машини. Нам це потрібно для роботи на довільних часових поясах ...
BuschnicK

51
Це не враховує перехід на літній час
Габріель МакАдамс

10
Ні! Різниця залежить від точної дати. Це залежить від переходу на літній час.
usr

3
У моєму випадку мені просто знадобився часовий пояс 1, і це чудово працювало! Дякую.
M Thelen

10
Це добре працює, якщо ви знаєте, що ваш біг сьогодні не історично, дякую
Девід Адлінгтон,

22

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

Тим не менш, я знаю, що SqlServer 2008 надає деякі нові функції дати, які можуть вирішити цю проблему, але люди, які використовують попередню версію, повинні знати про обмеження.

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


16

Для SQL Server 2016 та новіших версій та Azure SQL Database використовуйте вбудований AT TIME ZONEоператор .

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

UTC до місцевого має такий вигляд:

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 25.03.13

Будь-коли, коли вам потрібно виявити часовий пояс у клієнта, а потім мати час повернення бази даних у зазначеному часовому поясі, функція SWITCHOFFSET дуже корисна.
Роб Фарлі

3
@RobFarley Виявлення часового поясу у клієнта та використання SWITCHOFFSET все одно може призвести до помилок. Потрібно знати, чи застосовується дата та час конвертації літнього часу чи ні. Просто виявлення часового поясу та застосування поточного зміщення до UTC може затримати годину перерви - і це в простому випадку, коли всі ваші перерахунки знаходяться в одній країні. Не кожна країна переходить на / з переходу на літній час на ті самі дати. SWITCHOFFSET працює чудово, якщо ви зберігаєте місцевий час і знаєте різницю між вихідною та цільовою зонами в одній країні.
Джеймі Дивіться

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+, і перевага полягає в тому, що він автоматично враховує Daylight при перетворенні в певний часовий пояс


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:00 до 15:00 у музеї).

Щоб отримати поточний 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

Як примітка, я роблю те, що ви робите , навпаки. Для DateTimes, прив’язаних до локальної події, я зберігаю DateTimeOffset. Для DateTime, який не прив’язаний до локальної події, я зберігаю DateTime в UTC. Перший має дві відповідні точки даних (коли це за місцевим часом, і який це місцевий час), другий лише один (коли це)
Мартін,

@Martijn Але часовий пояс не дає вам місцезнаходження, і ви все одно повинні зберігати окремо.
Tracker1

@ tracker1 Це працює лише тоді, коли місцеположення має спосіб дізнатися свій часовий пояс, і навіть тоді це цілком боляче перетворити.
Martijn

3

Так, певною мірою, як тут докладно .
Підхід, який я використовував (до 2008 р.), Полягає у здійсненні перетворення в бізнес-логіці .NET перед вставкою в БД.


1

Ви можете скористатися функцією GETUTCDATE (), щоб отримати дату UTC за часом. Можливо, ви можете вибрати різницю між GETUTCDATE () та GETDATE () і скористатися цією різницею, щоб налаштувати свої дати на UTC

Але я погоджуюся з попереднім повідомленням, що набагато простіше контролювати правильний час і час на бізнес-рівні (наприклад, у .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.