web-dev-qa-db-fra.com

Quels formats littéraux de date / heure sont LANGUAGE et DATEFORMAT sûrs?

Il est facile de démontrer que de nombreux formats de date/heure autres que les deux suivants sont vulnérables à une mauvaise interprétation en raison de SET LANGUAGE, SET DATEFORMAT ou de la valeur par défaut d'une connexion Langue:

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

Même ce format, sans le T, peut ressembler à un format ISO 8601 valide, mais il échoue dans plusieurs langues:

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

Résultats:

Die Spracheneinstellung wurde auf Deutsch geändert.

Msg 242, niveau 16, état 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, niveau 16, état 3
La conversion d'un type de données varchar en type de données datetime a créé une valeur hors limites.

Maintenant, ceux-ci échouent comme si, en anglais, j'avais transposé le mois et le jour, pour formuler une composante date de yyyy-dd-mm:

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

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

Résultat:

Msg 242, niveau 16, état 3
La conversion d'un type de données varchar en un type de données datetime a donné lieu à une valeur hors plage.

(Ce n'est pas Microsoft Access, ce qui est "sympa" pour vous et corrige la transposition pour vous. De plus, des erreurs similaires peuvent se produire dans certains cas avec SET DATEFORMAT ydm; - ce n'est pas juste une chose de langage, c'est juste le scénario le plus courant où ces ruptures se produisent - et ne sont pas toujours remarquées parce que parfois elles ce ne sont pas des erreurs, c'est juste que le 7 août est devenu le 8 juillet et personne ne l'a remarqué.)

Donc, la question:

Maintenant que je sais qu'il y a un tas de formats dangereux, y a-t-il d'autres formats qui seront sûrs étant donné toute combinaison de langue et de format de date?

23
Aaron Bertrand

Dans la documentation , il est très explicitement indiqué que les seuls formats sûrs sont ceux que j'ai montrés au tout début de la question:

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

Cependant, il a récemment été porté à mon attention qu'il existe un troisième format qui est également à l'abri des paramètres de langue ou de format de date:

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

TL; DR: C'est vrai. Pour datetime et smalldatetime.

Lisez la suite pour la version plus longue, et pour autant de preuves que vous allez en avoir.


Il y a une faille qui explique cela - alors que le corps du texte principal ne reconnaît pas yyyyMMdd hh:... en tant que format à l'abri des interprétations de format de langue ou de date transposées, il y a un petit texte indiquant que la partie date d'une telle chaîne n'est pas validée en fonction des paramètres de format de date:

enter image description here

C'est assez différent de moi de prendre simplement la documentation sur Word, généralement. Vous pouvez dire que je suis un peu sceptique. Et le langage est ambigu ici aussi - il indique simplement qu'il s'agit de la combinaison de la date et de l'heure, sans appeler explicitement l'espace (cela pourrait être un retour chariot, pour autant que je sache). Il indique également qu'il n'est pas multilingue, ce qui signifie qu'il pourrait échouer dans certaines langues, mais nous découvrirons sous peu que c'est également incorrect.

J'ai donc voulu prouver qu'aucune combinaison langue/format de date ne pouvait faire échouer ce format spécifique.

Tout d'abord, j'ai créé un petit bloc de SQL dynamique pour chaque langue:

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

Cela a produit 34 lignes de sortie comme ceci:

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

J'ai copié cette sortie dans une nouvelle fenêtre de requête, et au-dessus, j'ai généré ce code, qui devrait, espérons-le, essayer de convertir cette même date (le 13 mars) au 3ème jour du 13ème mois dans au moins un cas:

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

Non, toutes les langues travaillées se trouvent juste dans ydm. J'ai également essayé tous les autres formats, ainsi que tous les types de données date/heure. 34 conversions réussies au 13 mars, à chaque fois.

Donc, je concède à @AndriyM et @ErikE qu'il existe en effet un troisième format sûr. Je garderai cela à l'esprit pour les prochains articles, mais j'ai pilonné les tambours des deux autres à tellement d'endroits, je ne vais pas tous les traquer et les corriger maintenant.


Par extension, vous penseriez que celui-ci serait sûr, mais non:

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

Je pense que dans toutes les langues, cela donnera l'équivalent de:

Msg 241, niveau 16, état 1, ligne 8
La conversion a échoué lors de la conversion de la date et/ou de l'heure à partir d'une chaîne de caractères.


Pour être complet, il existe un quatrième format sûr, mais il n'est sûr que pour les conversions vers les nouveaux types de date/heure (date, datetime2, datetimeoffset). Dans ces cas, les paramètres de langue ne peuvent pas interférer:

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

Cependant, je déconseille fortement son utilisation car il seulement fonctionne pour les types les plus récents, et les anciens sont encore très utilisés, dans mon expérience. Pourquoi avoir les tirets là où partout ailleurs (ou en fait dans ce même code, si le type de données change) vous devez les supprimer?

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ême avec la chaîne source même, les conversions ont donné des résultats assez différents:

März    Juli    März

Le format qui fonctionne pour datetime (yyyyMMdd) fonctionnera aussi fonctionnera toujours pour la date et les autres nouveaux types. Donc, à mon humble avis, utilisez toujours cela. Et étant donné le troisième format pour les types avec date/heure (yyyyMMdd hh:...), cela vous permettra en fait d'être plus cohérent - même si le composant date est toujours un peu moins lisible.


Maintenant, il me faudra quelques années, à donner ou à prendre, pour prendre l'habitude de démontrer les trois formats sûrs quand je parle de chaîne représentation des dates.

26
Aaron Bertrand