Quais formatos literais de data / hora são seguros para LANGUAGE e DATEFORMAT?

24

É fácil demonstrar que muitos formatos de data / hora outros do que os dois seguintes são vulneráveis a erros de interpretação devido ao SET LANGUAGE, SET DATEFORMAT, ou idioma padrão de um login:

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

Mesmo esse formato, sem o T, pode parecer um formato ISO 8601 válido, mas falha em vários idiomas:

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

Resultados:

Die Spracheneinstellung wurde auf Deutsch geändert.

Msg 242, Nível 16, Estado 3
Bei der Konvertierung eines varchar-Datentyps in ain datetime-Datentyp liegt der Wert außerhalb des gültigen Bereichs.

O parâmetro de idioma é passado para francês.

Msg 242, Level 16, State 3
A conversão de um tipo de dados varchar em um tipo de dados datetime para criar um valor hors limites.

Agora, eles falham como se, em inglês, eu tivesse transposto o mês e o dia, para formular um componente de data de yyyy-dd-mm:

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

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

Resultado:

Msg 242, Nível 16, Estado 3
A conversão de um tipo de dados varchar em um tipo de dados datetime resultou em um valor fora do intervalo.

(Isso não é o Microsoft Access, que é "legal" para você e corrige a transposição para você. Além disso, erros semelhantes podem ocorrer em alguns casos com SET DATEFORMAT ydm; - não é apenas uma questão de linguagem, é apenas o cenário mais comum em que esses quebras acontecem - e nem sempre são notadas porque às vezes não são erros, é que 7 de agosto se tornou 8 de julho e ninguém notou.)

Então, a pergunta:

Agora que eu sei que existem vários formatos não seguros, existem outros formatos que serão seguros, dada qualquer combinação de idioma e formato de data?

Aaron Bertrand
fonte

Respostas:

26

Na documentação , é afirmado explicitamente que os únicos formatos seguros são os que demonstrei no início da pergunta:

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

No entanto, recentemente, soube que existe um terceiro formato igualmente imune a qualquer configuração de idioma ou formato de data:

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

TL; DR: Isso é verdade. Para datetimee smalldatetime.

Continue lendo para a versão mais longa e com todas as provas que conseguir.


Existe uma brecha que explica isso - embora o corpo do texto principal não reconheça yyyyMMdd hh:...como um formato protegido de interpretações de idiomas ou formatos de data transpostos, há um pequeno resumo que diz que a parte da data de uma string não é validada, dependendo das configurações do formato da data:

insira a descrição da imagem aqui

É bastante diferente de mim apenas pegar a documentação em sua palavra, normalmente. Você pode dizer que sou um pouco cético. E a linguagem também é ambígua aqui - apenas afirma que se trata da combinação de data e hora, não chamando o espaço explicitamente (isso poderia ser um retorno de carro, pelo que sei). Ele também diz que não é multilíngue, o que significa que pode falhar em alguns idiomas, mas descobriremos em breve que isso também está incorreto.

Então, decidi provar que nenhuma combinação de idioma / formato de data poderia fazer esse formato específico falhar.

Primeiro, criei um pequeno bloco de SQL dinâmico para cada idioma:

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

Isso produziu 34 linhas de saída como esta:

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)';    

Copiei essa saída para uma nova janela de consulta e, acima dela, gerei esse código, que esperançosamente tentaria converter a mesma data (13 de março) para o 3º dia do 13º mês em pelo menos um caso:

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

Não, todas as línguas trabalhadas são encontradas ydm. Também tentei todos os outros formatos e também todos os tipos de dados de data / hora. 34 conversões bem-sucedidas para 13 de março, todas as vezes.

Portanto, concordo com @AndriyM e @ErikE que, de fato, existe um terceiro formato seguro. Vou manter isso em mente para posts futuros, mas já toquei a bateria dos outros dois em tantos lugares que não vou caçar todos e corrigi-los agora.


Por extensão, você pensaria que este seria seguro, mas não:

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

Eu acho que em todas as línguas, isso renderá o equivalente a:

Mensagem 241, nível 16, estado 1, linha 8
Falha na conversão ao converter data e / ou hora da cadeia de caracteres.


Para completar, há uma quarta formato seguro, mas só é seguro para as conversões para os tipos de data / hora mais recentes ( date, datetime2, datetimeoffset). Nesses casos, as configurações de idioma não podem interferir:

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

No entanto, eu recomendo contra o seu uso, porque ele funciona apenas para os tipos mais recentes, e os antigos ainda estão em uso massivo, na minha experiência. Por que os traços estão lá quando em qualquer outro lugar (ou de fato nesse mesmo código, se o tipo de dados mudar) você precisa removê-los?

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

Mesmo com a mesma sequência de origem, as conversões renderam resultados bastante diferentes:

März    Juli    März

O formato que funciona para datetime ( yyyyMMdd) também sempre funcionará para data e outros tipos novos. Então, IMHO, sempre use isso. E dado o terceiro formato para tipos com data / hora ( yyyyMMdd hh:...), isso permitirá que você seja mais consistente - mesmo que o componente de data seja sempre um pouco menos legível.


Agora, levarei apenas alguns anos, mais ou menos, para adquirir o hábito de demonstrar os três formatos seguros quando estou falando sobre a representação de datas das strings.

Aaron Bertrand
fonte
Não é possível que o terceiro formato se torne inseguro quando um novo idioma é adicionado ao SQL Server em alguma versão futura?
Kuba Wyrostek
@ Kuba Estou bastante confiante de que a Microsoft aprendeu a lição sobre isso. Alguém tomou uma péssima decisão de interpretar todas essas linguagens aaaa-dd-MM, um formato que acho que ninguém na terra jamais usou de propósito.
Aaron Bertrand