哪些日期/时间文字格式是 LANGUAGE 和 DATEFORMAT 安全的?

Aar*_*and 24 sql-server t-sql date-format datetime string-representation

这是很容易证明,许多日期/时间格式比下面的两个很容易受到由于设置语言,SET DATEFORMAT,或登录的默认语言误解:

yyyyMMdd                 -- unseparated, date only
yyyy-MM-ddThh:mm:ss.fff  -- date dash separated, date/time separated by T 
Run Code Online (Sandbox Code Playgroud)

即使这种没有 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);
Run Code Online (Sandbox Code Playgroud)

结果:

Die Spracheneinstellung wurde auf Deutsch geändert。

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

现在,这些失败好像,在英语中,我已经调换了月份和日期,以制定一个日期组件yyyy-dd-mm

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

SET LANGUAGE us_english;
SELECT CONVERT(datetime, @d);
Run Code Online (Sandbox Code Playgroud)

结果:

消息 242,级别 16,状态 3
将 varchar 数据类型转换为日期时间数据类型导致值超出范围。

(这不是 Microsoft Access,它对您“很好”并为您修复了换位。此外,在某些情况下可能会发生类似的错误SET DATEFORMAT ydm;- 这不仅仅是语言问题,这只是更常见的情况破损发生 - 并不总是被注意到,因为有时它们不是错误,只是 8 月 7 日变成了 7 月 8 日,没有人注意到。)

所以,问题:

既然我知道有一堆不安全的格式,那么考虑到任何语言和日期格式的组合,还有其他任何格式是安全的吗?

Aar*_*and 27

文档中,非常明确地指出,唯一安全的格式是我在问题开始时演示的格式:

yyyyMMdd                 -- unseparated, date only
yyyy-MM-ddThh:mm:ss.fff  -- date dash separated, date/time separated by T 
Run Code Online (Sandbox Code Playgroud)

然而,最近我注意到有第三种格式同样不受任何语言或日期格式设置的影响:

yyyyMMdd hh:mm:ss.fff    -- unseparated date, no T separator
Run Code Online (Sandbox Code Playgroud)

TL;DR:这是真的。对于datetimesmalldatetime

继续阅读更长的版本,以及尽可能多的证据。


有一个漏洞可以解释这一点 - 虽然主要文本正文未能将yyyyMMdd hh:...格式确认为对转置语言或日期格式解释安全的格式,但有一点说明此类字符串的日期部分未根据日期格式设置进行验证:

在此处输入图片说明

通常情况下,只听从文档的说法与我不同。你可以说我有点怀疑。而且这里的语言也是模棱两可的——它只是说明这是关于日期和时间的组合,而不是明确地标出空格(据我所知,这可能是回车)。它还说它不是多语言的,这意味着它可能会在某些语言中失败,但我们很快就会发现这也是不正确的。

所以我开始证明没有任何语言/日期格式的组合可以使这种特定格式失败。

首先,我为每种语言创建了一小块动态 SQL:

EXEC sys.sp_executesql @sql, N'@lang sysname', N'us_english';
Run Code Online (Sandbox Code Playgroud)

这产生了 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)';    
Run Code Online (Sandbox Code Playgroud)

我将该输出复制到一个新的查询窗口,并在其上方生成了此代码,希望至少在一种情况下尝试将同一日期(3 月 13 日)转换为第 13 个月的第 3 天:

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

不,每种语言在ydm. 我也尝试了所有其他格式,以及每种日期/时间数据类型。34 次成功转换到 3 月 13 日,每次。

所以,我确实向 @AndriyM 和 @ErikE 承认,确实存在第三种安全格式。我会在以后的帖子中牢记这一点,但是我已经在很多地方对另外两个进行了抨击,我现在不会将它们全部追捕并纠正它们。


通过扩展,您会认为这个是安全的,但不是:

yyyyMMddThh:mm:ss.fff    -- unseparated date, T separator
Run Code Online (Sandbox Code Playgroud)

我认为在每种语言中,这将产生等价于:

消息 241,级别 16,状态 1,第 8 行
从字符串转换日期和/或时间时转换失败。


为完整起见,还有第四种安全格式,但它仅对转换为较新的日期/时间类型 ( date, datetime2, datetimeoffset) 是安全的。在这些情况下,语言设置不会干扰:

yyyy-MM-dd hh:mm:...
Run Code Online (Sandbox Code Playgroud)

但是,我强烈建议不要使用它,因为它适用于较新的类型,而根据我的经验,旧类型仍在大量使用。为什么在其他任何地方(或者实际上在相同的代码中,如果数据类型发生变化)都必须删除破折号?

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);
Run Code Online (Sandbox Code Playgroud)

即使给定相同的源字符串,转换也会产生完全不同的结果:

März    Juli    März
Run Code Online (Sandbox Code Playgroud)

对于日期时间(作品格式yyyyMMdd)将始终日期和其他新类型的工作。所以,恕我直言,请始终使用它。考虑到日期/时间 ( yyyyMMdd hh:...)类型的第三种格式,这实际上会让你更加一致——即使日期组件的可读性总是有点低。


现在,当我谈论日期的字符串表示时,我只需要几年时间就可以养成展示三种安全格式的习惯。