Format literal tanggal / waktu apa yang LANGUAGE dan DATEFORMAT aman?

24

Sangat mudah untuk menunjukkan bahwa banyak tanggal / format waktu lain selain dua berikut rentan terhadap salah tafsir karena SET BAHASA, SET dateformat, atau bahasa default login ini:

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

Bahkan format ini, tanpa T, mungkin terlihat seperti format ISO 8601 yang valid, tetapi gagal dalam beberapa bahasa:

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

Hasil:

Die Spracheneinstellung wurde auf Deutsch geändert.

Msg 242, Level 16, Negara 3
Bei der Konvertierung menggunakan varchar-Datentyps dalam setiap waktu-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 dan type de données datetime a créé une valeur hors limites.

Sekarang, ini gagal seolah-olah, dalam bahasa Inggris, saya telah mengubah bulan dan hari, untuk merumuskan komponen tanggal yyyy-dd-mm:

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

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

Hasil:

Msg 242, Level 16, Negara 3
Konversi tipe data varchar ke tipe data datetime menghasilkan nilai di luar kisaran.

(Ini bukan Microsoft Access, yang "baik" untuk Anda dan memperbaiki transposisi untuk Anda. Juga, kesalahan serupa dapat terjadi dalam beberapa kasus dengan SET DATEFORMAT ydm;- itu bukan hanya masalah bahasa, itu hanya skenario yang lebih umum di mana ini kerusakan terjadi - dan tidak selalu diperhatikan karena kadang-kadang itu bukan kesalahan, hanya saja 7 Agustus menjadi 8 Juli dan tidak ada yang memperhatikan.)

Jadi, pertanyaannya:

Sekarang saya tahu ada banyak format tidak aman, apakah ada format lain yang akan aman diberikan kombinasi bahasa dan format tanggal?

Aaron Bertrand
sumber

Jawaban:

26

Dalam dokumentasi , secara eksplisit dinyatakan bahwa satu-satunya format aman adalah yang saya contohkan di awal pertanyaan:

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

Namun, baru-baru ini menarik perhatian saya bahwa ada format ketiga yang sama-sama kebal terhadap pengaturan bahasa atau format tanggal:

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

TL; DR: Ini benar. Untuk datetimedan smalldatetime.

Baca terus untuk versi yang lebih panjang, dan tentang sebanyak mungkin bukti yang akan Anda dapatkan.


Ada celah yang menjelaskan hal ini - sementara badan teks utama gagal untuk mengakui yyyyMMdd hh:...sebagai format aman dari terjemahan bahasa atau interpretasi format tanggal, ada uraian kecil yang mengatakan bagian tanggal dari string semacam itu tidak divalidasi tergantung pada pengaturan format tanggal :

masukkan deskripsi gambar di sini

Ini agak tidak seperti saya untuk hanya mengambil dokumentasi sesuai kata-katanya, biasanya. Bisa dibilang saya agak skeptis. Dan bahasanya di sini juga ambigu - hanya menyatakan bahwa ini adalah tentang kombinasi tanggal dan waktu, tidak menyebut ruang secara eksplisit (yang bisa menjadi carriage return, untuk semua yang saya tahu). Ia juga mengatakan bahwa itu bukan multi-bahasa, yang berarti bisa gagal dalam bahasa tertentu, tetapi kami akan segera mengetahui bahwa itu juga salah.

Jadi saya berangkat untuk membuktikan bahwa tidak ada kombinasi bahasa / format tanggal yang dapat membuat format spesifik ini gagal.

Pertama, saya membuat blok kecil SQL dinamis untuk setiap bahasa:

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

Ini menghasilkan 34 baris output seperti ini:

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

Saya menyalin output itu ke jendela permintaan baru, dan di atasnya, saya membuat kode ini, yang diharapkan akan mencoba untuk mengkonversi tanggal yang sama (tanggal 13 Maret) ke hari ke-3 dari bulan ke-13 dalam setidaknya satu kasus:

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

Tidak, setiap bahasa bekerja hanya menemukan di ydm. Saya mencoba setiap format lainnya juga, dan juga setiap tipe data tanggal / waktu. 34 konversi sukses hingga 13 Maret, setiap saat.

Jadi, saya mengakui ke @AndriyM dan @ErikE bahwa, memang, ada format aman ke-3. Saya akan mengingat hal ini untuk posting selanjutnya, tetapi saya telah memukul drum tentang dua lainnya di begitu banyak tempat, saya tidak akan memburu mereka semua dan memperbaikinya sekarang.


Dengan ekstensi, Anda akan berpikir yang ini akan aman, tetapi tidak:

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

Saya pikir dalam setiap bahasa, ini akan menghasilkan yang setara dengan:

Msg 241, Level 16, State 1, Line 8
Conversion gagal ketika mengubah tanggal dan / atau waktu dari string karakter.


Untuk kelengkapan, ada format yang aman keempat, tetapi hanya aman untuk konversi ke jenis tanggal / waktu yang lebih baru ( date, datetime2, datetimeoffset). Dalam kasus ini pengaturan bahasa tidak dapat mengganggu:

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

Namun, saya sangat menyarankan untuk tidak menggunakannya karena hanya berfungsi untuk jenis yang lebih baru, dan yang lama masih banyak digunakan, menurut pengalaman saya. Mengapa ada tanda hubung di tempat lain (atau bahkan dalam kode yang sama, jika tipe data berubah) Anda harus menghapusnya?

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

Meskipun diberi string sumber yang sama , konversi menghasilkan hasil yang sangat berbeda:

März    Juli    März

Format yang berfungsi untuk datetime ( yyyyMMdd) juga akan selalu bekerja untuk tanggal dan tipe baru lainnya. Jadi, IMHO, selalu gunakan itu. Dan mengingat format ketiga untuk tipe dengan tanggal / waktu ( yyyyMMdd hh:...), ini sebenarnya akan memungkinkan Anda untuk lebih konsisten - bahkan jika komponen tanggal selalu sedikit kurang dapat dibaca.


Sekarang saya hanya perlu beberapa tahun, memberi atau menerima, untuk membiasakan mendemonstrasikan tiga format aman ketika saya berbicara tentang representasi string dari tanggal.

Aaron Bertrand
sumber
Bukankah itu format ketiga mungkin menjadi tidak aman ketika bahasa baru ditambahkan ke SQL Server di beberapa rilis di masa depan?
Kuba Wyrostek
@Kuba Saya cukup yakin bahwa Microsoft telah belajar pelajaran tentang ini. Seseorang membuat keputusan yang sangat buruk untuk meminta semua bahasa ini menerjemahkan yyyy-dd-MM, sebuah format yang saya pikir tidak seorang pun di dunia ini pernah menggunakannya dengan sengaja.
Aaron Bertrand