Какие форматы даты / времени являются безопасными для ЯЗЫКА и DATEFORMAT?

24

Легко показать , что многие даты / времени форматов других , чем следующие два уязвимы для неверной интерпретации из - за установки языка, SET DATEFORMAT или язык для входа в систему по умолчанию:

yyyyMMdd                 -- unseparated, date only
yyyy-MM-ddThh:mm:ss.fff  -- date dash separated, date/time separated by T 

Даже этот формат без T может выглядеть как действительный формат ISO 8601, но он не работает на нескольких языках:

DECLARE @d varchar(32) = '2017-03-13 23:22:21.020';

SET LANGUAGE Deutsch;
SELECT CONVERT(datetime, @d);

SET LANGUAGE Français;
SELECT CONVERT(datetime, @d);

Полученные результаты:

Die Spracheneinstellung wurde auf Deutsch geändert.

Msg 242, Уровень 16, Состояние 3
Bei der Konvertierung eines varchar-Datentyps in einen datetime-Datentyp liegt der Wert außerhalb des gültigen Bereichs.

Le paramètre langue est passé à Français.

Msg 242, Уровень 16, Государство 3
Преобразование в День Святого Духа в Тип и День Дня, время и время.

Теперь они терпят неудачу, как будто на английском языке я перенес месяц и день, чтобы сформулировать компонент даты yyyy-dd-mm:

DECLARE @d varchar(32) = '2017-13-03 23:22:21.020';

SET LANGUAGE us_english;
SELECT CONVERT(datetime, @d);

Результат:

Сообщение 242, уровень 16, состояние 3
Преобразование типа данных varchar в тип данных datetime привело к значению вне допустимого диапазона.

(Это не Microsoft Access, который «хорош» для вас и исправляет транспонирование для вас. Кроме того, в некоторых случаях могут возникать похожие ошибки SET DATEFORMAT ydm;- это не просто язык, это просто более распространенный сценарий, когда случаются поломки - и не всегда замечаются, потому что иногда они не являются ошибками, просто 7 августа стало 8 июля, и никто не заметил.)

Итак, вопрос:

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

Аарон Бертран
источник

Ответы:

26

В документации очень четко указано, что единственными безопасными форматами являются те, которые я продемонстрировал в самом начале вопроса:

yyyyMMdd                 -- unseparated, date only
yyyy-MM-ddThh:mm:ss.fff  -- date dash separated, date/time separated by T 

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

yyyyMMdd hh:mm:ss.fff    -- unseparated date, no T separator

TL; DR: это правда. Для datetimeи smalldatetime.

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


Существует лазейка, которая объясняет это - хотя основное тело текста не может быть признано yyyyMMdd hh:...форматом, безопасным для транспонированных языков или интерпретаций формата даты, есть небольшая реклама, которая говорит, что часть даты в такой строке не проверена в зависимости от настроек dateformat:

введите описание изображения здесь

Обычно я совсем не согласен с документацией. Вы можете сказать, что я немного скептически. И язык здесь тоже неоднозначен - он просто говорит о том, что речь идет о комбинации даты и времени, а не о явном вызове пробела (насколько я знаю, это может быть возврат каретки). В нем также говорится, что это не мультиязычный язык, что означает, что он может не работать на определенных языках, но вскоре мы выясним, что это также неверно.

Поэтому я решил доказать, что ни одна комбинация языка / даты не сможет сделать этот конкретный формат неудачным.

Сначала я создал небольшой блок динамического SQL для каждого языка:

EXEC sys.sp_executesql @sql, N'@lang sysname', N'us_english';

Это произвело 34 ряда вывода как это:

EXEC sys.sp_executesql @sql, N'@lang sysname', N'us_english';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'Deutsch';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'Français';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'日本語';
...
EXEC sys.sp_executesql @sql, N'@lang sysname', N'简体中文';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'Arabic';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'ไทย';
EXEC sys.sp_executesql @sql, N'@lang sysname', N'norsk (bokmål)';    

Я скопировал этот вывод в новое окно запроса и над ним сгенерировал этот код, который, мы надеемся, попытается преобразовать эту же дату (13 марта) в 3-й день 13-го месяца хотя бы в одном случае:

DECLARE @sql nvarchar(max) = N'
SET LANGUAGE @lang;
SET DATEFORMAT ydm;
SELECT @@LANGUAGE, CONVERT(datetime, ''20170313 23:22:21.020'');';

Нет, каждый язык работал просто найти в ydm. Я попробовал любой другой формат, а также каждый тип данных даты / времени. 34 успешных преобразования в 13 марта, каждый раз.

Итак, я согласен с @AndriyM и @ErikE, что действительно существует третий безопасный формат. Я буду помнить это для будущих постов, но я разбил барабаны о двух других во многих местах, я не собираюсь выслеживать их все и исправлять их сейчас.


По сути, вы думаете, что это будет безопасно, но нет:

yyyyMMddThh:mm:ss.fff    -- unseparated date, T separator

Я думаю, что на каждом языке это даст эквивалент:

Сообщение 241, Уровень 16, Состояние 1, Строка 8
Преобразование не выполнено при преобразовании даты и / или времени из символьной строки.


Для полноты картины , есть четвертый безопасный формат, но это безопасно только для преобразования в более новых типов даты / времени ( date, datetime2, datetimeoffset). В этих случаях языковые настройки не могут мешать:

yyyy-MM-dd hh:mm:...

Тем не менее, я настоятельно рекомендую против его использования, потому что он работает только для новых типов, а старые, по моему опыту, все еще широко используются. Зачем черточки там, когда везде (или на самом деле в том же коде, если тип данных меняется) вы должны их удалить?

SET LANGUAGE Deutsch;
DECLARE @dashes char(10) = '2017-03-07 03:34';
DECLARE @d date = @dashes, @dt datetime = @dashes, @dt2 datetime2 = @dashes;

SELECT DATENAME(MONTH,@d), DATENAME(MONTH,@dt), DATENAME(MONTH,@dt2);

Даже при одной и той же исходной строке преобразования дали совершенно разные результаты:

März    Juli    März

Формат, который работает для datetime ( yyyyMMdd), также всегда будет работать для даты и других новых типов. Так что, имхо, просто всегда этим пользуюсь. А учитывая третий формат для типов с date / time ( yyyyMMdd hh:...), это фактически позволит вам быть более последовательным, даже если компонент даты всегда немного менее читабелен.


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

Аарон Бертран
источник
Не правда ли, что третий формат может стать небезопасным при добавлении нового языка в SQL Server в каком-либо будущем выпуске?
Куба Уиростек
@Kuba Я довольно уверен, что Microsoft извлекла урок об этом. Кто-то принял довольно плохое решение, чтобы все эти языки интерпретировали yyyy-dd-MM, формат, который, я думаю, никто на земле никогда не использовал специально.
Аарон Бертран