1. ホーム
  2. JavaScript

【JavaScript】Intl.DateTimeFormat の使い方|日付・時刻をロケールに合わせて整形する方法

Share

Date オブジェクトから「2026年9月21日(月)」のような表示を作ろうとすると、getFullYear() や getMonth() で値を取り出し、padStart(2, '0') で0埋めし、曜日は自前の配列から引く……という手順になりがちです。日本語表示だけなら何とかなりますが、英語版も用意するとなると、月名の位置も区切り文字も変わるため、条件分岐が一気に増えていきます。

この面倒をまとめて引き受けてくれるのが Intl.DateTimeFormat です。ロケール(言語と地域)とオプションを渡すだけで、その言語圏で自然な日付・時刻の文字列を作ってくれます。この記事では基本の使い方から、オプションの一覧、和暦やタイムゾーン指定といった実践的な例、そして意図した書式にならないときの確認ポイントまでを解説します。

基本の使い方

new Intl.DateTimeFormat() でフォーマッターを作り、format() に Date オブジェクトを渡します。第1引数がロケール、第2引数がオプションのオブジェクトです。オプションを省略すると、年・月・日だけの短い形式になります。

main.js
const date = new Date('2026-09-21T14:05:09+09:00');

console.log(new Intl.DateTimeFormat('ja-JP').format(date)); // "2026/9/21"
console.log(new Intl.DateTimeFormat('en-US').format(date)); // "9/21/2026"
console.log(new Intl.DateTimeFormat('en-GB').format(date)); // "21/09/2026"
console.log(new Intl.DateTimeFormat('de-DE').format(date)); // "21.9.2026"

同じ日付でも、アメリカ英語は「月/日/年」、イギリス英語は「日/月/年」、ドイツ語はピリオド区切りと、並び順も区切り文字も変わります。自前で組み立てるなら全部書き分けることになる部分です。

なお、上の出力は日本時間で実行した場合のものです。Date は内部的には「1970年1月1日からの経過ミリ秒」という1つの数値でしかなく、それを何時何分として見せるかはタイムゾーン次第です。結果を固定したいときは後述の timeZone を明示してください。

toLocaleDateString との関係

Date.prototype.toLocaleDateString() や toLocaleString() は、引数の形式も結果も Intl.DateTimeFormat と同じです。これは偶然ではなく、これらのメソッドが内部で Intl.DateTimeFormat と同じ仕組みを使うよう仕様で定められているためです。

main.js
const date = new Date('2026-09-21T14:05:09+09:00');

// この2行は同じ結果になる
console.log(date.toLocaleDateString('ja-JP', { dateStyle: 'full' }));
console.log(new Intl.DateTimeFormat('ja-JP', { dateStyle: 'full' }).format(date));
// どちらも "2026年9月21日月曜日"

では使い分けはどこにあるかというと、整形する回数です。フォーマッターを作るときにはロケールデータの読み込みなどのコストがかかりますが、toLocaleDateString() は呼ぶたびに内部でその準備が走ります。一覧表の各行の日付を整形するようなループでは、Intl.DateTimeFormat のインスタンスをループの外で1つ作って使い回すほうが速くなります。

list.js
// フォーマッターは1回だけ作る
const formatter = new Intl.DateTimeFormat('ja-JP', {
  dateStyle: 'medium',
  timeZone: 'Asia/Tokyo',
});

const posts = [
  { title: '記事A', publishedAt: '2026-09-21T14:05:09+09:00' },
  { title: '記事B', publishedAt: '2026-08-03T09:30:00+09:00' },
];

// 何行あっても format() を呼ぶだけ
const rows = posts.map(
  (post) => `${post.title}(${formatter.format(new Date(post.publishedAt))})`
);
console.log(rows);
// [ "記事A(2026/09/21)", "記事B(2026/08/03)" ]

逆に、1箇所で1回だけ表示するなら toLocaleDateString() のほうが短く書けて読みやすいので、そちらで構いません。

指定できるオプション

第2引数には多くのオプションを渡せます。よく使うものをまとめます。

オプション指定できる値説明
dateStylefull / long / medium / short日付部分の詳しさをまとめて指定する
timeStylefull / long / medium / short時刻部分の詳しさをまとめて指定する
yearnumeric / 2-digit年。2-digit は下2桁(26)になる
monthnumeric / 2-digit / long / short / narrow月。long は「9月」「September」のような名称
daynumeric / 2-digit日
weekdaylong / short / narrow曜日。日本語では「月曜日」「月」など
hour / minute / secondnumeric / 2-digit時・分・秒
hour12true / false12時間制にするか。省略時はロケールの既定に従う
timeZoneAsia/Tokyo / UTC などどのタイムゾーンの時刻として表示するか
timeZoneNameshort / long / shortOffset などタイムゾーン名を添える
eralong / short / narrow紀元・元号を表示する(「西暦」「令和」など)
calendargregory / japanese など暦を指定する。japanese で和暦になる
numberingSystemlatn / hanidec など数字の字形。hanidec は漢数字

このうち dateStyle と timeStyle は「まとめて指定する」系、year 〜 second は「個別に指定する」系です。この2系統は混ぜて使えません(詳しくは後半で触れます)。

dateStyle と timeStyle でまとめて指定する

細かいことを考えずにそれらしい表示にしたいなら、まず dateStyle と timeStyle を試すのが早道です。どの程度詳しく出るかは4段階から選びます。

main.js
const date = new Date('2026-09-21T14:05:09+09:00');
const opt = (o) => new Intl.DateTimeFormat('ja-JP', { ...o, timeZone: 'Asia/Tokyo' }).format(date);

console.log(opt({ dateStyle: 'full' }));   // "2026年9月21日月曜日"
console.log(opt({ dateStyle: 'long' }));   // "2026年9月21日"
console.log(opt({ dateStyle: 'medium' })); // "2026/09/21"
console.log(opt({ dateStyle: 'short' }));  // "2026/09/21"

console.log(opt({ timeStyle: 'full' }));   // "14時05分09秒 日本標準時"
console.log(opt({ timeStyle: 'medium' })); // "14:05:09"
console.log(opt({ timeStyle: 'short' }));  // "14:05"

// 日付と時刻をまとめて出す
console.log(opt({ dateStyle: 'full', timeStyle: 'short' }));
// "2026年9月21日月曜日 14:05"

日本語ロケールでは medium と short の日付がほぼ同じ形になりますが、英語ロケールでは Sep 21, 2026 と 9/21/26 のように差が出ます。ロケールを切り替えたときにどう変わるかは、次のデモで動かして確かめてみてください。

<div class="demo">
  <label class="demo__label" for="locale">ロケールを切り替える</label>
  <select id="locale">
    <option value="ja-JP">ja-JP(日本語)</option>
    <option value="ja-JP-u-ca-japanese">ja-JP-u-ca-japanese(和暦)</option>
    <option value="en-US">en-US(アメリカ英語)</option>
    <option value="de-DE">de-DE(ドイツ語)</option>
  </select>

  <table class="demo__table">
    <thead>
      <tr><th>オプション</th><th>表示結果</th></tr>
    </thead>
    <tbody id="output"></tbody>
  </table>

  <p class="demo__note" id="tz"></p>
</div>
body {
  margin: 0;
  padding: 16px;
  font-family: system-ui, sans-serif;
  color: #333;
}

.demo__label {
  display: block;
  margin-bottom: 4px;
  font-size: 14px;
  font-weight: bold;
}

#locale {
  padding: 6px 8px;
  font-size: 14px;
}

.demo__table {
  width: 100%;
  margin-top: 16px;
  border-collapse: collapse;
  font-size: 14px;
}

.demo__table th,
.demo__table td {
  padding: 8px;
  border: 1px solid #ddd;
  text-align: left;
}

.demo__table th {
  background: #f5f5f5;
}

.demo__table td:last-child {
  font-weight: bold;
  color: #007bff;
}

.demo__note {
  margin-top: 12px;
  font-size: 13px;
  color: #666;
}
// 2026年9月21日(月) 14:05:09 (日本時間)
const date = new Date('2026-09-21T14:05:09+09:00');

const patterns = [
  { label: 'dateStyle: "full"', options: { dateStyle: 'full' } },
  { label: 'dateStyle: "long"', options: { dateStyle: 'long' } },
  { label: 'dateStyle: "medium"', options: { dateStyle: 'medium' } },
  { label: 'dateStyle: "short"', options: { dateStyle: 'short' } },
  {
    label: 'dateStyle: "medium", timeStyle: "short"',
    options: { dateStyle: 'medium', timeStyle: 'short' },
  },
  {
    label: 'year / month / day / weekday',
    options: { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short' },
  },
];

const select = document.getElementById('locale');
const output = document.getElementById('output');

function render() {
  const locale = select.value;

  output.innerHTML = patterns
    .map((pattern) => {
      // timeZone を固定して、どの環境でも同じ結果になるようにする
      const formatter = new Intl.DateTimeFormat(locale, {
        ...pattern.options,
        timeZone: 'Asia/Tokyo',
      });
      return '<tr><td>' + pattern.label + '</td><td>' + formatter.format(date) + '</td></tr>';
    })
    .join('');
}

select.addEventListener('change', render);
render();

// 実行環境のタイムゾーンは resolvedOptions() で取得できる
const tz = new Intl.DateTimeFormat().resolvedOptions().timeZone;
document.getElementById('tz').textContent = 'このブラウザのタイムゾーン: ' + tz;
Preview

末尾に出ているのは resolvedOptions().timeZone で取得した、そのブラウザのタイムゾーンです。実際に使われている設定を確認したいときに便利で、ロケールや暦も同じように取り出せます。

項目ごとに細かく指定する

dateStyle の4段階では欲しい形にならないときは、year / month / day などを個別に指定します。指定した項目だけが出力に含まれます。

main.js
const date = new Date('2026-09-21T14:05:09+09:00');
const opt = (o) => new Intl.DateTimeFormat('ja-JP', { ...o, timeZone: 'Asia/Tokyo' }).format(date);

// 年月日 + 曜日
console.log(opt({ year: 'numeric', month: 'long', day: 'numeric', weekday: 'short' }));
// "2026年9月21日(月)"

// 0埋めした年月日
console.log(opt({ year: 'numeric', month: '2-digit', day: '2-digit' }));
// "2026/09/21"

// 月日だけ
console.log(opt({ month: 'numeric', day: 'numeric' })); // "9/21"

// 年月だけ(記事アーカイブの見出しなどに)
console.log(opt({ year: 'numeric', month: 'long' }));   // "2026年9月"

// 時刻だけ
console.log(opt({ hour: '2-digit', minute: '2-digit' })); // "14:05"

// 12時間制にする
console.log(opt({ hour: 'numeric', minute: '2-digit', hour12: true })); // "午後2:05"

「年月だけ」「月日だけ」のように項目を絞れるのが、この書き方の利点です。区切り文字はロケールに合わせて自動で入るので、自分で '/' や '年' を書き足す必要はありません。

和暦で表示する

和暦にするには、ロケール文字列に -u-ca-japanese を付けるか、オプションで calendar: 'japanese' を指定します。-u- の部分は Unicode 拡張と呼ばれるもので、ca が暦(calendar)の指定です。どちらの書き方でも結果は同じです。

wareki.js
const date = new Date('2026-09-21T14:05:09+09:00');

// ロケール文字列に書く方法
console.log(
  new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
    dateStyle: 'long',
    timeZone: 'Asia/Tokyo',
  }).format(date)
); // "令和8年9月21日"

// オプションで書く方法(結果は同じ)
console.log(
  new Intl.DateTimeFormat('ja-JP', {
    calendar: 'japanese',
    dateStyle: 'long',
    timeZone: 'Asia/Tokyo',
  }).format(date)
); // "令和8年9月21日"

// 短い形式では元号がアルファベット1文字になる
console.log(
  new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {
    dateStyle: 'short',
    timeZone: 'Asia/Tokyo',
  }).format(date)
); // "R8/9/21"

元号が変わったときの切り替わりも、ブラウザやランタイムが持つ暦データ側で面倒を見てくれます。「平成31年4月30日」と「令和元年5月1日」の境目を自分で実装せずに済むのは、和暦を扱ううえで大きな安心材料です。

ちなみに西暦のまま「西暦2026年」と出したいときは era を指定します。numberingSystem: 'hanidec' を足せば「二〇二六年九月二一日」のような漢数字表記にもできます。

タイムゾーンを指定する

timeZone には Asia/Tokyo や America/New_York のような IANA タイムゾーン名を渡します。同じ Date でも、指定したタイムゾーンでの時刻として表示されます。

timezone.js
// 日本時間で 2026年9月21日 14:05 の瞬間
const date = new Date('2026-09-21T14:05:09+09:00');

console.log(
  new Intl.DateTimeFormat('ja-JP', {
    dateStyle: 'medium',
    timeStyle: 'short',
    timeZone: 'Asia/Tokyo',
  }).format(date)
); // "2026/09/21 14:05"

console.log(
  new Intl.DateTimeFormat('ja-JP', {
    dateStyle: 'medium',
    timeStyle: 'short',
    timeZone: 'America/New_York',
  }).format(date)
); // "2026/09/21 1:05"(ニューヨークでは同じ日の午前1時5分)

// タイムゾーン名を添える
console.log(
  new Intl.DateTimeFormat('ja-JP', {
    hour: '2-digit',
    minute: '2-digit',
    timeZone: 'America/New_York',
    timeZoneName: 'long',
  }).format(date)
); // "01:05 アメリカ東部夏時間"

夏時間(サマータイム)の適用も自動で判定されます。9月のニューヨークは夏時間の期間なので UTC-4、冬なら UTC-5 として計算されます。この切り替わり日を自分で管理しなくてよいのは、timeZone を使う大きな理由のひとつです。

閲覧者がどのタイムゾーンにいるかを知りたいときは resolvedOptions() を使います。フォーマッターが最終的に採用した設定が返ってくるので、timeZone を指定していなければ実行環境の値が入っています。

timezone.js
const resolved = new Intl.DateTimeFormat().resolvedOptions();

console.log(resolved.timeZone);        // "Asia/Tokyo"(日本での実行例)
console.log(resolved.locale);          // "ja-JP"
console.log(resolved.calendar);        // "gregory"
console.log(resolved.numberingSystem); // "latn"

取得した値はサーバーに送って保存したり、「お住まいの地域の時刻で表示しています」といった案内を出すのに使えます。

formatToParts() で部品に分解する

「年だけ大きく見せたい」「区切りをドットにしたい」など、用意された書式から外れたことをしたい場合は formatToParts() を使います。文字列ではなく、{ type, value } という形の部品の配列が返ってきます。

parts.js
const date = new Date('2026-09-21T14:05:09+09:00');

const formatter = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  weekday: 'short',
  timeZone: 'Asia/Tokyo',
});

console.log(formatter.formatToParts(date));
// [
//   { type: 'year',    value: '2026' },
//   { type: 'literal', value: '/' },
//   { type: 'month',   value: '09' },
//   { type: 'literal', value: '/' },
//   { type: 'day',     value: '21' },
//   { type: 'literal', value: '(' },
//   { type: 'weekday', value: '月' },
//   { type: 'literal', value: ')' }
// ]

// 必要な部品だけ取り出して組み直す
const parts = formatter.formatToParts(date);
const find = (type) => parts.find((part) => part.type === type).value;

console.log(`${find('year')}.${find('month')}.${find('day')}`); // "2026.09.21"

literal は区切り文字(/ や括弧、空白)を表す種別です。自分で組み立て直すときは literal を捨てて、必要な部品だけ使えばよいことになります。下のデモでは、年を強調し、曜日を小さく添えた独自の表示を作っています。

<div class="card">
  <p class="card__label">formatToParts() で組み立てた表示</p>
  <p class="card__result" id="result"></p>
  <p class="card__label">取り出した部品</p>
  <ul class="card__parts" id="parts"></ul>
</div>
body {
  margin: 0;
  padding: 16px;
  font-family: system-ui, sans-serif;
  color: #333;
}

.card__label {
  margin: 0 0 4px;
  font-size: 13px;
  color: #666;
}

.card__result {
  margin: 0 0 16px;
  padding: 12px 16px;
  border-left: 4px solid #007bff;
  background: #f5f9ff;
  font-size: 20px;
  font-weight: bold;
}

.card__result .year {
  color: #007bff;
}

.card__result .weekday {
  margin-left: 4px;
  font-size: 14px;
  color: #d6336c;
}

.card__parts {
  margin: 0;
  padding-left: 20px;
  font-size: 13px;
  line-height: 1.8;
}

.card__parts code {
  background: #eee;
  padding: 1px 4px;
  border-radius: 3px;
}
const date = new Date('2026-09-21T14:05:09+09:00');

const formatter = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
  weekday: 'short',
  timeZone: 'Asia/Tokyo',
});

// 文字列ではなく { type, value } の配列が返る
const parts = formatter.formatToParts(date);

// 区切り文字(literal)を捨てて、自分の好きな形に組み直す
const find = (type) => parts.find((part) => part.type === type).value;

document.getElementById('result').innerHTML =
  '<span class="year">' + find('year') + '</span>.' +
  find('month') + '.' + find('day') +
  '<span class="weekday">' + find('weekday') + '曜日</span>';

document.getElementById('parts').innerHTML = parts
  .map((part) => '<li><code>' + part.type + '</code> → ' + JSON.stringify(part.value) + '</li>')
  .join('');
Preview

ポイントは、数値の取り出し自体はフォーマッターに任せていることです。getMonth() の戻り値が0始まりであることや、0埋めの処理を気にする必要はありませんし、ロケールを変えれば月名も曜日名も自動で切り替わります。

formatRange() で期間を表示する

イベントの開催期間のように、2つの日時をまとめて表示したいときは formatRange() が使えます。2つの引数に開始と終了の Date を渡します。

range.js
const start = new Date('2026-09-21T14:05:00+09:00');
const end = new Date('2026-09-24T18:30:00+09:00');

const dateFormatter = new Intl.DateTimeFormat('ja-JP', {
  dateStyle: 'medium',
  timeZone: 'Asia/Tokyo',
});
console.log(dateFormatter.formatRange(start, end)); // "2026/09/21~2026/09/24"

// 同じ日のなかでの時間帯なら、日付は1回だけになる
const dateTimeFormatter = new Intl.DateTimeFormat('ja-JP', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'Asia/Tokyo',
});
console.log(
  dateTimeFormatter.formatRange(start, new Date('2026-09-21T18:30:00+09:00'))
); // "2026/09/21 14時05分~18時30分"

// 開始と終了が同じなら、期間ではなく単独の日付として出力される
console.log(dateFormatter.formatRange(start, start)); // "2026/09/21"

注目したいのは、共通する部分が自動でまとめられることです。同じ日のなかの時間帯なら日付は1度しか出ませんし、英語ロケールで同じ月の範囲を指定すると Sep 21 – 24, 2026 のように年月がまとめられます。文字列連結で start + '~' + end と書いた場合には得られない挙動です。

部品の配列が欲しいときは formatRangeToParts() もあります。こちらは各部品に、開始側・終了側・共通部分のどれかを示す source プロパティが加わります。

思った書式にならないときに確認すること

dateStyle と year・month の併用はエラーになる

もっとも引っかかりやすいのがこれです。「dateStyle: 'long' をベースにして、年だけ2桁にしたい」というつもりで両方を書くと、整形結果が変になるのではなく TypeError が投げられます。

main.js
// NG: dateStyle と個別指定は同時に使えない
new Intl.DateTimeFormat('ja-JP', {
  dateStyle: 'long',
  year: '2-digit',
});
// TypeError

// OK: 個別指定だけで組み立てる
new Intl.DateTimeFormat('ja-JP', {
  year: '2-digit',
  month: 'long',
  day: 'numeric',
});

timeStyle と hour / minute / second の組み合わせも同様です。dateStyle と timeStyle を一緒に使うのは問題ありませんし、timeZone や hour12 はどちらの系統とも併用できます。「dateStyle 系と項目別指定を混ぜない」と覚えておけば十分です。

出力を文字列として切り貼りしている

format() の結果を split('/') で分解したり、slice() で一部を取り出したりする実装は避けてください。ロケールが変われば区切り文字も項目の順序も変わるため、日本語では動いても英語表示にした途端に崩れます。

整形後の文字列は表示専用と考え、値を取り出したいときは前述の formatToParts() を使うか、元の Date から取り直します。ロケールによっては区切りに、見た目は空白でもコード上は別物の特殊な空白文字が使われることもあるので、文字列処理の前提にするのは危険です。

0埋めされない・されすぎる

month や day の numeric と 2-digit の違いです。numeric は必要最小限の桁数で出すので「9」、2-digit は2桁に揃えるので「09」になります。padStart() の代わりに 2-digit を指定すれば済みます。

ただし、ほかの項目の指定に引きずられることがある点には注意が必要です。たとえば日本語ロケールで { year: 'numeric', month: '2-digit', day: '2-digit' } を指定すると 2026/09/21 という形式が選ばれますが、month を long にすると 2026年9月21日 になり、day に 2-digit を指定していても「21日」と自然な形に落ち着きます。Intl はロケールごとの書式パターンから近いものを選ぶ仕組みなので、1文字単位で完全に制御したい場合は formatToParts() を使ってください。

環境によって表示がずれる

timeZone を省略すると、実行環境の設定が使われます。開発者のパソコンでは日本時間で正しく見えていたのに、海外のユーザーや UTC で動くサーバーでは1日ずれる、という食い違いはここから生まれます。表示したいタイムゾーンが決まっているなら必ず明示するのが安全です。

同じ理由で、ロケールの省略も避けたほうが無難です。また Intl の出力はブラウザが持つロケールデータに基づくため、ブラウザやそのバージョン、OS によって細部が変わることがあります。テストで整形結果を文字列の完全一致で比較するのは、できれば避けたいところです。

RangeError が出る

存在しないタイムゾーン名を渡すと RangeError: Invalid time zone specified になります。Asia/Tokyo を Asia/Tokio と綴り間違える、JST のような略称を渡す、といったケースです。使える名前は Intl.supportedValuesOf('timeZone') で一覧を取得できます。

もうひとつ、new Date('21/09/2026') のように解釈できない文字列から作った Date(Invalid Date)を format() に渡した場合も RangeError: Invalid time value になります。外部から受け取った日付文字列を扱うときは、Number.isNaN(date.getTime()) で妥当性を確認してから渡すと安全です。

まとめ

Intl.DateTimeFormat は、日付と時刻をロケールに合わせて整形する標準機能です。手早く仕上げたいときは dateStyle と timeStyle の4段階から選び、項目を絞りたいときは year / month / day などを個別に指定します。この2系統は併用できず、混ぜると TypeError になる点だけ押さえておけば、あとは組み合わせるだけです。

和暦は ja-JP-u-ca-japanese、タイムゾーンは timeZone、独自の並びにしたいときは formatToParts()、期間表示は formatRange() と、用途ごとに道具がそろっています。表示上の日付がずれるトラブルの多くは timeZone の省略が原因なので、そこを明示することから始めてみてください。何度も整形する場面では、フォーマッターを1つ作って使い回すとパフォーマンスの面でも有利です。

参考ページ