生の JavaScript で SQL 形式の日時文字列を取得する

生の JavaScript で SQL 形式の日時文字列を取得する

こんにちは、kenzauros です。

昔から JavaScript の Date オブジェクト の扱いは鬼門でした。 2022 年の今もそれは変わらず、悩まされている JS 開発者も多いと思います。

今回は「日時を SQL 形式にフォーマットする」ということを考えます。この記事で SQL 形式とは MySQL 等で特に指定しない場合に採用されるフォーマットで、 2024-01-15 09:00:23 のような形式を指します。

さて、これまでも JS の非力な Date を補うため、さまざまなライブラリーが開発され、消えていきました。今でも残っているのは下記のようなライブラリーでしょうか。

これらのライブラリーは便利ですが、正直なところ日付をフォーマットしたいというだけでライブラリーを導入するのはなんだか大げさな気もします。

また諸般の事情により外部ライブラリーを追加できない場合も考えられます。

ということで今回は生の JavaScript で一番シンプルに SQL 形式の日時文字列を取得する方法を検討してみます。

結論

結論から言って、いまのところ Date.toLocaleString()sv-SE ロケールを指定する下記の書き方が最も短そうです。

new Date().toLocaleString('sv-SE')

タイムゾーンの指定が必要な場合は下記のようにします。

new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Tokyo' })

これで '2022-01-06 14:41:33' のような文字列が得られます。 sv-SE はスウェーデン語 ですが、なぜかイイ感じの SQL 形式になります。

同様の形式にはリトアニアの lt-LT もあります。確認した範囲では、環境によらないのはこの 2 種類のみのようです。 (南アフリカの Zulu 語 zu-ZA は Node.js では 2022-01-06 14:37:31 、 Chrome では 2022/1/6 14:56:31 未対応のようです。)

補足

SQL 形式の日時文字列とは何か

前述のとおり SQL 形式の日時文字列 とは 2024-01-15 09:00:23 のような形式です。

この形式を分解して考えると下記のようになります。

  • 並び順が 年・月・日・時・分・秒
  • 年は 4 桁固定
  • 月~秒は 2 桁固定
  • 日付は - 区切り
  • 時刻は : 区切り
  • 日付と時刻は 半角スペース で結合

このほか、UTC 環境以外はタイムゾーンの考慮が必要です。

エンジニアには見慣れた (?) この形式、機械的にも扱いやすいと思うのですが、世界的に見てもこの形式を採用しているのはごく少数です。

かくいう日本も日付の区切り文字は / のことが多いので、一般のユーザーにも - 形式はあまり馴染みがないでしょう。

各国のロケールで日付を toLocaleString してみた例です。

const locales = ['af-ZA', 'sq-AL', 'ar-AR', 'hy-AM', 'ay-BO', 'az-AZ', 'eu-ES', 'be-BY', 'bn-IN', 'bs-BA', 'bg-BG', 'ca-ES', 'ck-US', 'hr-HR', 'cs-CZ', 'da-DK', 'nl-NL', 'nl-BE', 'en-PI', 'en-GB', 'en-UD', 'en-US', 'eo-EO', 'et-EE', 'fo-FO', 'tl-PH', 'fi-FI', 'fb-FI', 'fr-CA', 'fr-FR', 'gl-ES', 'ka-GE', 'de-DE', 'el-GR', 'gn-PY', 'gu-IN', 'he-IL', 'hi-IN', 'hu-HU', 'is-IS', 'id-ID', 'ga-IE', 'it-IT', 'ja-JP', 'jv-ID', 'kn-IN', 'kk-KZ', 'km-KH', 'tl-ST', 'ko-KR', 'ku-TR', 'la-VA', 'lv-LV', 'fb-LT', 'li-NL', 'lt-LT', 'mk-MK', 'mg-MG', 'ms-MY', 'ml-IN', 'mt-MT', 'mr-IN', 'mn-MN', 'ne-NP', 'se-NO', 'nb-NO', 'nn-NO', 'ps-AF', 'fa-IR', 'pl-PL', 'pt-BR', 'pt-PT', 'pa-IN', 'qu-PE', 'ro-RO', 'rm-CH', 'ru-RU', 'sa-IN', 'sr-RS', 'zh-CN', 'sk-SK', 'sl-SI', 'so-SO', 'es-LA', 'es-CL', 'es-CO', 'es-MX', 'es-ES', 'es-VE', 'sw-KE', 'sv-SE', 'sy-SY', 'tg-TJ', 'ta-IN', 'tt-RU', 'te-IN', 'th-TH', 'zh-HK', 'zh-TW', 'tr-TR', 'uk-UA', 'ur-PK', 'uz-UZ', 'vi-VN', 'cy-GB', 'xh-ZA', 'yi-DE', 'zu-ZA' ]
locales.forEach(x => console.log(x, new Date().toLocaleString(x)))

環境 (Node.js, Chrome, Firefox など) で異なります。下記は Node.js 14 の例です。

af-ZA 2022-01-06 14:37:31
sq-AL 6.1.2022, 2:37:31 e pasdites
ar-AR ??/??/????, ?:??:?? ?
hy-AM 06.01.2022, 14:37:31
ay-BO 1/6/2022, 2:37:31 PM
az-AZ 06.01.2022 14:37:31
eu-ES 2022/1/6 14:37:31
be-BY 6.1.2022, 14:37:31
bn-IN ?/?/???? ?:??:?? PM
bs-BA 6.1.2022. 14:37:31
bg-BG 6.01.2022 г., 14:37:31 ч.
ca-ES 6/1/2022, 14:37:31
ck-US 1/6/2022, 2:37:31 PM
hr-HR 06. 01. 2022. 14:37:31
cs-CZ 6. 1. 2022 14:37:31
da-DK 6.1.2022 14.37.31
nl-NL 6-1-2022 14:37:31
nl-BE 6/1/2022 14:37:31
en-PI 1/6/2022, 2:37:31 PM
en-GB 06/01/2022, 14:37:31
en-UD 1/6/2022, 2:37:31 PM
en-US 1/6/2022, 2:37:31 PM
eo-EO 2022-1-6 14:37:31
et-EE 6.1.2022 14:37:31
fo-FO 06.01.2022, 14:37:31
tl-PH 1/6/2022, 2:37:31 PM
fi-FI 6.1.2022 klo 14.37.31
fb-FI 1/6/2022, 2:37:31 PM
fr-CA 2022-01-06, 14 h 37 min 31 s
fr-FR 06/01/2022, 14:37:31
gl-ES 14:37:31, 6/1/2022
ka-GE 6.1.2022, 14:37:31
de-DE 6.1.2022, 14:37:31
el-GR 6/1/2022, 2:37:31 μ.μ.
gn-PY 1/6/2022, 2:37:31 PM
gu-IN 6/1/2022 2:37:31 PM
he-IL 6.1.2022, 14:37:31
hi-IN 6/1/2022, 2:37:31 pm
hu-HU 2022. 01. 06. 14:37:31
is-IS 6.1.2022, 14:37:31
id-ID 6/1/2022 14.37.31
ga-IE 06/01/2022 14:37:31
it-IT 6/1/2022, 14:37:31
ja-JP 2022/1/6 14:37:31
jv-ID 06-01-2022, 14:37:31
kn-IN 6/1/2022 2:37:31 ???????
kk-KZ 06.01.2022, 14:37:31
km-KH 6/1/2022, 2:37:31 PM
tl-ST 1/6/2022, 2:37:31 PM
ko-KR 2022. 1. 6. ?? 2:37:31
ku-TR 2022-1-6 14:37:31
la-VA 1/6/2022, 2:37:31 PM
lv-LV 2022.01.6. 14:37:31
fb-LT 1/6/2022, 2:37:31 PM
li-NL 1/6/2022, 2:37:31 PM
lt-LT 2022-01-06 14:37:31
mk-MK 6.1.2022, во 14:37:31
mg-MG 2022-01-06 14:37:31
ms-MY 6/1/2022, 2:37:31 PTG
ml-IN 6/1/2022 2:37:31 PM
mt-MT 1/6/2022 14:37:31
mr-IN ?/?/????, ?:??:?? PM
mn-MN 2022.01.06 14:37:31
ne-NP ????-??-??, ??:??:??
se-NO 2022-01-06 14:37:31
nb-NO 1/6/2022, 2:37:31 PM
nn-NO 6.1.2022, 14:37:31
ps-AF AP ????-??-?? ??:??:??
fa-IR ????/??/???? ??:??:??
pl-PL 6.01.2022, 14:37:31
pt-BR 06/01/2022 14:37:31
pt-PT 06/01/2022, 14:37:31
pa-IN 6/1/2022, 2:37:31 ??.??.
qu-PE 06-01-2022 14:37:31
ro-RO 06.01.2022, 14:37:31
rm-CH 06-01-2022 14:37:31
ru-RU 06.01.2022, 14:37:31
sa-IN ?/?/????, ?:??:?? ???????
sr-RS 6.1.2022. 14:37:31
zh-CN 2022/1/6 下午2:37:31
sk-SK 6. 1. 2022, 14:37:31
sl-SI 6. 1. 2022 14:37:31
so-SO 1/6/2022 2:37:31 GD
es-LA 6/1/2022 14:37:31
es-CL 06-01-2022 14:37:31
es-CO 6/1/2022, 2:37:31 p.?m.
es-MX 6/1/2022 14:37:31
es-ES 6/1/2022 14:37:31
es-VE 6/1/2022 2:37:31 p.?m.
sw-KE 6/1/2022 14:37:31
sv-SE 2022-01-06 14:37:31
sy-SY 1/6/2022, 2:37:31 PM
tg-TJ 6/1/2022 14:37:31
ta-IN 6/1/2022, ???????? 2:37:31
tt-RU 06.01.2022, 14:37:31
te-IN 6/1/2022 2:37:31 PM
th-TH 6/1/2565 14:37:31
zh-HK 6/1/2022 下午2:37:31
zh-TW 2022/1/6 下午2:37:31
tr-TR 06.01.2022 14:37:31
uk-UA 06.01.2022, 14:37:31
ur-PK 6/1/2022 2:37:31 PM
uz-UZ 06/01/2022, 14:37:31
vi-VN 14:37:31, 6/1/2022
cy-GB 6/1/2022 14:37:31
xh-ZA 2022-01-06 14:37:31
yi-DE 6-1-2022, 14:37:31
zu-ZA 2022-01-06 14:37:31

toLocaleString(‘sv-SE’) の不安

冒頭に紹介した toLocaleString('sv-SE') はイイ感じの SQL 形式になりましたが、一抹の不安はあります。

  • sv-SE がわかりにくい
  • ブラウザーや Node.js の実装変更で変わってしまうおそれがある

sv-SE ってなんやねん!というツッコミを避けるためには ja-JP を採用し、下記のように書くことでも同様の出力が得られます。 カスタムして使用する場合はこちらのほうが、応用が利きそうです。

new Date().toLocaleString('ja-JP', {
    timeZone: 'Asia/Tokyo',
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
}).replace(/\//g, '-');

実装変更で変わる可能性があることは否定できませんが、ライブラリーが保守されなくなるよりは頻度が低そうな気もします。どちらを採るか、難しいところです。

これ以上を考慮すると、古典的に getなんとか() 関数などで作ることになってしまうので、そのときは大人の事情を乗り越えてライブラリーを導入することにしましょう。

kenzauros