¿Qué formatos literales de fecha / hora son seguros para LANGUAGE y DATEFORMAT?

24

Es fácil demostrar que muchos formatos de fecha / hora que no sean los dos siguientes son vulnerables a una mala interpretación debido a SET LANGUAGE, SET DATEFORMAT o al idioma predeterminado de inicio de sesión:

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

Incluso este formato, sin la T, puede parecer un formato ISO 8601 válido, pero falla en varios 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, Nivel 16, Estado 3
Bei der Konvertierung eines varchar-Datentyps in einen datetime-Datentyp liegt der Wert außerhalb des gültigen Bereichs.

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

Msg 242, Nivel 16, Estado 3
La conversión de un tipo de datos variados en un tipo de datos datetime a créé une valeur hors limites.

Ahora, estos fallan como si, en inglés, hubiera transpuesto el mes y el día, para formular un componente de fecha de yyyy-dd-mm:

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

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

Resultado:

Mensaje 242, Nivel 16, Estado 3
La conversión de un tipo de datos varchar a un tipo de datos datetime dio como resultado un valor fuera de rango.

(Esto no es Microsoft Access, que es "agradable" para usted y corrige la transposición por usted. Además, pueden ocurrir errores similares en algunos casos con SET DATEFORMAT ydm;- no es solo una cuestión de lenguaje, ese es solo el escenario más común donde estos las roturas ocurren, y no siempre se notan porque a veces no son errores, es solo que el 7 de agosto se convirtió en 8 de julio y nadie se dio cuenta).

Entonces, la pregunta:

Ahora que sé que hay un montón de formatos inseguros, ¿hay algún otro formato que sea seguro con cualquier combinación de idioma y formato de fecha?

Aaron Bertrand
fuente

Respuestas:

26

En la documentación , se dice muy explícitamente que los únicos formatos seguros son los que mostré al comienzo de la pregunta:

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

Sin embargo, recientemente me llamó la atención que hay un tercer formato que es igualmente inmune a cualquier configuración de idioma o formato de fecha:

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

TL; DR: Esto es cierto. Para datetimey smalldatetime.

Siga leyendo para obtener la versión más larga y la cantidad de pruebas que obtendrá.


Hay una laguna que explica esto: si bien el cuerpo del texto principal no reconoce yyyyMMdd hh:...como un formato seguro del lenguaje transpuesto o las interpretaciones del formato de fecha, hay una pequeña propaganda que dice que la parte de fecha de una cadena de este tipo no se valida según la configuración del formato de fecha :

ingrese la descripción de la imagen aquí

Es bastante diferente a mí tomar la documentación en su palabra, por lo general. Puedes decir que soy un poco escéptico. Y el lenguaje también es ambiguo aquí: solo indica que se trata de la combinación de fecha y hora, no de llamar explícitamente el espacio (eso podría ser un retorno de carro, por lo que sé). También dice que no es multilingüe, lo que significa que podría fallar en ciertos idiomas, pero pronto descubriremos que eso también es incorrecto.

Así que me propuse demostrar que ninguna combinación de lenguaje / formato de fecha podría hacer que este formato específico fallara.

Primero, creé un pequeño bloque de SQL dinámico para cada idioma:

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

Esto produjo 34 filas de salida 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)';    

Copié ese resultado en una nueva ventana de consulta y, sobre él, generé este código, que con suerte trataría de convertir esa misma fecha (el 13 de marzo) al tercer día del mes 13 en al menos un caso:

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

No, todos los idiomas trabajados solo se encuentran en ydm. También probé todos los demás formatos, y también todos los tipos de datos de fecha / hora. 34 conversiones exitosas al 13 de marzo, cada vez.

Entonces, reconozco a @AndriyM y @ErikE que, de hecho, hay un tercer formato seguro. Lo tendré en cuenta para futuras publicaciones, pero he tocado la batería sobre los otros dos en tantos lugares, que no voy a cazarlos y corregirlos ahora.


Por extensión, pensarías que este sería seguro, pero no:

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

Creo que en todos los idiomas, esto producirá el equivalente de:

Msg 241, Nivel 16, Estado 1, La
conversión de la línea 8 falló al convertir la fecha y / o la hora de la cadena de caracteres.


Para completar, hay una cuarta formato seguro, pero sólo es seguro para las conversiones a los tipos de fecha / hora más nuevos ( date, datetime2, datetimeoffset). En estos casos, la configuración del idioma no puede interferir:

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

Sin embargo, recomiendo encarecidamente su uso, ya que solo funciona para los tipos más nuevos, y los antiguos todavía tienen una gran cantidad de uso, en mi experiencia. ¿Por qué tener los guiones allí cuando en cualquier otro lugar (o de hecho en el mismo código, si el tipo de datos cambia) tiene que eliminarlos?

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

Incluso dada la misma cadena fuente, las conversiones arrojaron resultados bastante diferentes:

März    Juli    März

El formato que funciona para datetime ( yyyyMMdd) también siempre funcionará para date y los otros tipos nuevos. Entonces, en mi humilde opinión, siempre usa eso. Y dado el tercer formato para los tipos con fecha / hora ( yyyyMMdd hh:...), esto realmente le permitirá ser más consistente, incluso si el componente de fecha siempre es un poco menos legible.


Ahora solo me llevará unos años, más o menos, acostumbrarme a demostrar los tres formatos seguros cuando estoy hablando de la representación en cadena de fechas.

Aaron Bertrand
fuente
¿No es que el tercer formato puede volverse inseguro cuando se agrega un nuevo idioma a SQL Server en alguna versión futura?
Kuba Wyrostek
@Kuba Estoy bastante seguro de que Microsoft ha aprendido su lección sobre esto. Alguien tomó una decisión bastante mala para que todos estos idiomas interpretaran aaaa-dd-MM, un formato que no creo que nadie en el mundo haya usado a propósito.
Aaron Bertrand