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

24

Il est facile de démontrer que de nombreux formats de date / heure autres que les deux suivants sont susceptibles d'être mal interprétés en raison de SET LANGUAGE, SET DATEFORMAT ou de la langue par défaut d'une connexion:

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 entraîné une valeur hors plage.

(Ce n'est pas Microsoft Access, 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 seulement une question de langue, c'est juste le scénario le plus courant où ces des ruptures se produisent - et ne sont pas toujours remarquées parce que parfois 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 existe un tas de formats dangereux, existe-t-il d'autres formats qui seront sûrs compte tenu de toute combinaison de langue et de format de date?

Aaron Bertrand
la source

Réponses:

26

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 été récemment porté à mon attention qu'il existe un troisième format qui est également immunisé contre tous les 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 datetimeet 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 - bien que le corps du texte principal ne reconnaisse pas yyyyMMdd hh:...un format à l'abri des interprétations de langue transposée ou de format de date, il y a un petit texte expliquant que la partie date d'une telle chaîne n'est pas validée en fonction des paramètres de format de date:

entrez la description de l'image ici

C'est assez différent de moi de prendre la documentation au mot, 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 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 types de date / heure les plus récents ( date,datetime2 , datetimeoffset). Dans ces cas, les paramètres de langue ne peuvent pas interférer:

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

Cependant, je recommande fortement contre son utilisation car il ne fonctionne que pour les nouveaux types, et les anciens sont encore très utilisés, selon 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 compte tenu de la même chaîne source, les conversions ont donné des résultats assez différents:

März    Juli    März

Le format qui fonctionne pour datetime ( yyyyMMdd) sera également 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 juste quelques années, à donner ou à prendre, pour prendre l'habitude de démontrer les trois formats sûrs lorsque je parle de la représentation sous forme de chaîne des dates.

Aaron Bertrand
la source
N'est-ce pas que le troisième format peut devenir dangereux quand une nouvelle langue est ajoutée à SQL Server dans une version future?
Kuba Wyrostek
@Kuba Je suis assez confiant que Microsoft a appris leur leçon à ce sujet. Quelqu'un a pris une assez mauvaise décision pour que toutes ces langues interprètent aaaa-jj-MM, un format que personne sur terre n'a jamais utilisé à dessein.
Aaron Bertrand