Дата и время в SQL Server: типы данных и функции для работы с ними

Дата и время в SQL Server: типы данных и функции для работы с ними Полезное

Дата и время в SQL Server — группа типов данных для хранения момента времени: календарной даты, времени суток или того и другого вместе, с точностью от суток (для типа с точностью до дня) до 100 наносекунд (для типов с максимальной точностью). У каждого типа свой диапазон и размер в байтах, а у T-SQL есть отдельные функции для текущего момента, разбора на части, арифметики и перевода между часовыми поясами. Разберем все это по порядку, с проверкой в SQL Server 2016 и новее (актуально и для Azure SQL Database).

Типы данных для даты и времени

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

Тип Диапазон Точность Размер
DATE 0001-01-01 — 9999-12-31 1 день 3 байта
TIME(n) 00:00:00.0000000 — 23:59:59.9999999 до 100 нс, n = 0-7 3-5 байт (зависит от n)
SMALLDATETIME 1900-01-01 — 2079-06-06 1 минута 4 байта
DATETIME 1753-01-01 — 9999-12-31 округление до .000/.003/.007 сек 8 байт
DATETIME2(n) 0001-01-01 — 9999-12-31 до 100 нс, n = 0-7 6-8 байт (зависит от n)
DATETIMEOFFSET(n) 0001-01-01 — 9999-12-31, плюс смещение от UTC -14:00…+14:00 до 100 нс, n = 0-7 8-10 байт (зависит от n)

Для TIME, DATETIME2 и DATETIMEOFFSET размер зависит от заявленной точности (параметр n — число знаков после запятой у секунд): при n от 0 до 2 TIME занимает 3 байта, DATETIME2 — 6, DATETIMEOFFSET — 8; при n от 3 до 4 — соответственно 4, 7 и 9 байт; при n от 5 до 7 (максимум) — 5, 8 и 10 байт. Без явного указания n по умолчанию берется 7.

Если нужна только календарная дата (день рождения, дата документа) — берите DATE, а не DATETIME с обнуленным временем: это и компактнее, и честнее по смыслу поля.

Текущая дата и время: GETDATE, SYSDATETIME и UTC

В T-SQL нет функций NOW() или CURDATE() — это синтаксис MySQL, в SQL Server он не работает. Текущий момент получают так:

SELECT
    GETDATE()          AS local_datetime,       -- тип datetime, округление до .000/.003/.007 сек
    SYSDATETIME()       AS local_datetime2,      -- тип datetime2(7), точность до 100 нс
    GETUTCDATE()         AS utc_datetime,         -- то же самое, но в UTC
    SYSUTCDATETIME()      AS utc_datetime2,        -- UTC с точностью datetime2
    SYSDATETIMEOFFSET()    AS local_with_offset;    -- datetime2 + смещение часового пояса сервера

Все пять функций читают системные часы сервера (или контейнера/ВМ, где он запущен), а не клиента. Если приложение и СУБД стоят в разных часовых поясах, разница будет только в SYSDATETIMEOFFSET — остальные вернут «голое» время без пометки, для какого пояса оно посчитано.

Извлечение частей даты: DATEPART, DATENAME, YEAR/MONTH/DAY

Чтобы достать из даты отдельную часть, в T-SQL есть DATEPART (число) и DATENAME (текст), а для года/месяца/дня — короткие функции-обертки:

DECLARE @d datetime2 = '2026-09-19 14:30:00';

SELECT
    YEAR(@d)                     AS y,     -- 2026
    MONTH(@d)                    AS m,     -- 9
    DAY(@d)                      AS d,     -- 19
    DATEPART(weekday, @d)          AS wd,    -- зависит от DATEFIRST сессии
    DATENAME(month, @d)             AS mname; -- "September" или "Сентябрь" - зависит от языка сессии

DATENAME и текстовое представление DATEPART(weekday, …) зависят от параметров SET LANGUAGE и SET DATEFIRST текущей сессии — один и тот же запрос на разных подключениях может вернуть разный день недели по номеру и разное название месяца.

Арифметика дат: DATEADD и DATEDIFF

DATEADD прибавляет (или вычитает, если число отрицательное) целое число единиц к дате:

SELECT DATEADD(day, 30, '2026-09-19');   -- 2026-10-19
SELECT DATEADD(month, -1, '2026-09-19'); -- 2026-08-19

DATEDIFF считает не «сколько ровно времени прошло», а сколько раз была пересечена граница указанной единицы измерения. Это регулярно дает контринтуитивный результат:

DECLARE @a datetime2 = '2026-09-19 23:59:59';
DECLARE @b datetime2 = '2026-09-20 00:00:01';

SELECT DATEDIFF(minute, @a, @b) AS minutes_wrong; -- вернет 1, хотя прошло всего 2 секунды

Разница всего в 2 секунды, но между значениями лежит граница минуты (и суток), поэтому DATEDIFF(minute, …) засчитывает целую минуту. Чтобы получить точный интервал, нужно спуститься на уровень секунд (или меньше) и уже потом пересчитать:

SELECT DATEDIFF(second, @a, @b) / 60.0 AS minutes_precise; -- 0.033333 - именно 2 секунды в минутах

Для очень больших интервалов (миллисекунды/микросекунды/наносекунды между датами, разнесенными на годы) обычный DATEDIFF может переполнить INT. Начиная с SQL Server 2016 для этого есть DATEDIFF_BIG — та же логика, но результат BIGINT.

Преобразование и форматирование: CONVERT и FORMAT

CONVERT переводит дату в строку по числовому коду стиля — работает быстро и годится для условий WHERE:

SELECT
    CONVERT(varchar(10), GETDATE(), 23)  AS iso_date,      -- 2026-09-19
    CONVERT(varchar(19), GETDATE(), 120) AS odbc_canonical; -- 2026-09-19 14:30:00

FORMAT() (с SQL Server 2012) гибче — принимает .NET-формат и учитывает культуру (FORMAT(GETDATE(), 'dd MMMM yyyy', 'ru-RU')), но заметно медленнее CONVERT и не sargable: индекс по дате при фильтрации FORMAT в WHERE не используется. Для вывода в отчет или интерфейс — нормально, для условий отбора строк — не годится.

DATETIME2 вместо DATETIME: что выбрать

Для нового кода в SQL Server 2008 и новее рекомендуется DATETIME2, а не DATETIME. Причина — точность и диапазон, а не сам факт «более новый тип»:

Критерий DATETIME DATETIME2
Диапазон дат с 1753-01-01 с 0001-01-01
Точность времени округление до .000/.003/.007 сек до 100 нс, настраивается (n)
Минимальный размер 8 байт всегда 6 байт при низкой точности
Соответствие стандарту SQL нет да

DATETIME округляет доли секунды до ближайших .000/.003/.007 — при сравнении значений, вычисленных разными способами, это дает неожиданные несовпадения. У DATETIME2 округления нет, а точность (число знаков после запятой у секунд) задается явно.

Единственная причина остаться на DATETIME — работа со старым приложением или ORM-слоем, который не умеет DATETIME2 или завязан на его диапазон. Для нового кода такого ограничения обычно нет.

Часовые пояса: DATETIMEOFFSET и AT TIME ZONE

DATETIMEOFFSET хранит момент времени плюс числовое смещение от UTC (например, +03:00) — это не то же самое, что часовой пояс с правилами перехода на летнее/зимнее время. Смещение фиксируется в момент записи и не пересчитывается автоматически.

Сменить смещение у уже сохраненного значения, не меняя сам момент времени в UTC, позволяет SWITCHOFFSET:

DECLARE @dto datetimeoffset = '2026-09-19 14:00:00 +03:00';

SELECT SWITCHOFFSET(@dto, '+00:00'); -- 2026-09-19 11:00:00 +00:00, тот же момент времени

Начиная с SQL Server 2016 есть AT TIME ZONE — переводит значение с учетом именованного часового пояса Windows ('Russian Standard Time' и подобные) и его текущих правил DST. Это удобнее ручной арифметики со смещениями, но опирается на таблицу часовых поясов ОС сервера: для дат в прошлом, когда правила перехода отличались от текущих, результат может быть неточным.

Выводы

  • Для календарной даты без времени берите DATE (3 байта), для точного момента — DATETIME2 с нужной точностью, для значений с явным смещением от UTC — DATETIMEOFFSET.
  • DATETIME2 предпочтительнее DATETIME для нового кода: больше диапазон, точнее время, без принудительного округления долей секунды.
  • DATEDIFF считает пересеченные границы единицы измерения, а не точный интервал — при разнице в секунды результат в минутах/часах может быть больше реального.
  • FORMAT() удобен для вывода, но не sargable и медленнее CONVERT — не используйте его в условиях WHERE.
  • DATETIMEOFFSET хранит числовое смещение, а не правила часового пояса; для перевода с учетом летнего/зимнего времени нужен AT TIME ZONE (SQL Server 2016+).

Где применяется / связь с практикой

Освойте тему на практике

Правильный выбор типа даты и времени напрямую влияет на объем БД, точность отчетов и корректность работы с пользователями из разных часовых поясов — это база, с которой сталкивается любой T-SQL-разработчик уже на первых боевых задачах. Разобрать типы данных SQL Server глубже, потренироваться на реальных схемах и разобрать типичные ошибки с преподавателем можно на курсе MS SQL Server Developer.

Освойте тему на практике

Если пока не готовы к полному курсу — загляните на открытые уроки Otus: там разбирают конкретные темы SQL Server и других инструментов бесплатно, в прямом эфире с преподавателем.

FAQ

DATETIME хранится как два целых числа или как число с плавающей точкой?
Микрософт официально не документирует точный битовый формат хранения — это внутренняя деталь движка, на которую нельзя полагаться в коде. Для практики важны задокументированные диапазон, точность и размер из таблицы выше, а не внутреннее устройство.

Можно ли сравнивать значения DATETIME и DATETIME2 напрямую в запросе?
Да, SQL Server неявно приводит DATETIME к DATETIME2 при сравнении, но из-за разной точности и округления в DATETIME результат сравнения «на равенство» может отличаться от ожидаемого — надежнее явно привести оба значения через CONVERT/CAST к одному типу.

Что вернет GETDATE(), если сервер физически находится в другом часовом поясе, чем пользователь?
Локальное время самого сервера (или контейнера), без какой-либо привязки к поясу пользователя. Для многопользовательских приложений с разными поясами обычно хранят время в UTC (GETUTCDATE/SYSUTCDATETIME) и переводят в пояс пользователя уже на уровне приложения или через DATETIMEOFFSET.

OTUS Журнал