【JavaScript】日付のフォーマットを変換する完全ガイド|ゼロ埋め・ISO 8601・時刻・タイムゾーン・Temporal APIまで解説

JavaScriptの Date オブジェクトが返す文字列は "Mon Apr 06 2026 09:00:00 GMT+0900" のような長い形式です。実務では「YYYY/MM/DD」「YYYY-MM-DD(ISO 8601)」「YYYY年MM月DD日(曜日)」など用途ごとに異なるフォーマットが求められ、そのたびに変換処理が必要になります。

この記事では外部ライブラリなしのバニラJSで任意のフォーマットへ変換するコアテクニック、再利用できる formatDate() ユーティリティ関数、時刻・タイムゾーン対応、そして文字列パース時に起きがちな「9時間ずれ」の落とし穴まで体系的に解説します。

スポンサーリンク

ゼロ埋め(ゼロパディング)を理解する

日付フォーマットの多くは月・日を必ず2桁にします(例: 4月 → 04)。JavaScriptの getMonth() などは1桁の整数を返すため、ゼロ埋めが必要です。

getMonth() は0始まりに注意:
date.getMonth()0〜11 を返します。1月=0、12月=11 です。フォーマット時は必ず getMonth() + 1 として月の実数値に直してください。うっかり getMonth() をそのまま使うと、4月なのに 03 が出力されます。
padStart() でゼロ埋めする基本
const date = new Date('2026-04-06');

const year  = date.getFullYear();                          // 2026
const month = String(date.getMonth() + 1).padStart(2, '0'); // '04'(0始まり補正+ゼロ埋め)
const day   = String(date.getDate()).padStart(2, '0');      // '06'

console.log(`${year}/${month}/${day}`);  // "2026/04/06"
console.log(`${year}-${month}-${day}`);  // "2026-04-06"
console.log(`${year}年${month}月${day}日`); // "2026年04月06日"
padStart(2, ‘0’) の意味:
String(n).padStart(2, '0') は「文字列を最低2文字にし、足りない桁を先頭の ‘0’ で埋める」という処理です。例: String(4).padStart(2, '0')'04'String(12).padStart(2, '0')'12'(変化なし)。

よく使われるフォーマット一覧と変換例

実務でよく登場する形式をまとめます。すべてバニラJSで実装できます。

YYYY/MM/DD・YYYY-MM-DD・日本語表記
const d = new Date('2026-04-06T09:30:00');

const Y  = d.getFullYear();
const M  = String(d.getMonth() + 1).padStart(2, '0');
const D  = String(d.getDate()).padStart(2, '0');
const h  = String(d.getHours()).padStart(2, '0');
const mi = String(d.getMinutes()).padStart(2, '0');
const s  = String(d.getSeconds()).padStart(2, '0');

// 日付のみ
console.log(`${Y}/${M}/${D}`);            // "2026/04/06"
console.log(`${Y}-${M}-${D}`);            // "2026-04-06" ← ISO 8601
console.log(`${Y}年${M}月${D}日`);        // "2026年04月06日"

// 日付+時刻
console.log(`${Y}/${M}/${D} ${h}:${mi}:${s}`);   // "2026/04/06 09:30:00"
console.log(`${Y}-${M}-${D}T${h}:${mi}:${s}`);   // "2026-04-06T09:30:00" ← ISO 8601拡張
曜日を含むフォーマット
const d = new Date('2026-04-06');
const DAY_JA  = ['日', '月', '火', '水', '木', '金', '土'];
const DAY_EN  = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

const Y  = d.getFullYear();
const M  = String(d.getMonth() + 1).padStart(2, '0');
const D  = String(d.getDate()).padStart(2, '0');
const wj = DAY_JA[d.getDay()];  // getDay() は 0=日曜〜6=土曜
const we = DAY_EN[d.getDay()];

console.log(`${Y}年${M}月${D}日(${wj})`);  // "2026年04月06日(月)"
console.log(`${Y}/${M}/${D}(${we})`);          // "2026/04/06(Mon)"
12時間表記(AM/PM)
const d = new Date('2026-04-06T14:30:00');

const hours24 = d.getHours();     // 14
const ampm    = hours24 < 12 ? 'AM' : 'PM';
const hours12 = hours24 % 12 || 12; // 0時 → 12 に変換
const mi      = String(d.getMinutes()).padStart(2, '0');

console.log(`${hours12}:${mi} ${ampm}`);  // "2:30 PM"
console.log(`${String(hours12).padStart(2,'0')}:${mi} ${ampm}`); // "02:30 PM"

再利用できる formatDate() ユーティリティ関数

毎回同じ処理を書かなくてすむよう、フォーマット文字列("YYYY/MM/DD HH:mm:ss")を渡すだけで変換できるユーティリティ関数を作成します。

formatDate() 実装と使用例
/**
 * Date オブジェクトを指定パターンの文字列に変換する
 * @param {Date|string|number} date - 変換元の日付
 * @param {string} pattern         - 変換パターン
 *   YYYY: 4桁年, MM: 2桁月, DD: 2桁日, HH: 2桁時(24h),
 *   mm: 2桁分, ss: 2桁秒, ddd: 曜日略称(Mon etc.)
 * @returns {string}
 */
function formatDate(date, pattern = 'YYYY/MM/DD') {
  const d = date instanceof Date ? date : new Date(date);

  if (isNaN(d.getTime())) {
    throw new RangeError(`Invalid date: ${date}`);
  }

  const DAY_EN = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  const DAY_JA = ['日', '月', '火', '水', '木', '金', '土'];

  const map = {
    'YYYY': String(d.getFullYear()),
    'YY'  : String(d.getFullYear()).slice(-2),
    'MM'  : String(d.getMonth() + 1).padStart(2, '0'),
    'M'   : String(d.getMonth() + 1),
    'DD'  : String(d.getDate()).padStart(2, '0'),
    'D'   : String(d.getDate()),
    'HH'  : String(d.getHours()).padStart(2, '0'),
    'H'   : String(d.getHours()),
    'mm'  : String(d.getMinutes()).padStart(2, '0'),
    'ss'  : String(d.getSeconds()).padStart(2, '0'),
    'ddd' : DAY_EN[d.getDay()],
    'dd'  : DAY_JA[d.getDay()],
  };

  // 長いトークンを先にマッチさせるため長さ降順でソート
  const pattern_keys = Object.keys(map).sort((a, b) => b.length - a.length);
  const regex = new RegExp(pattern_keys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'g');

  return pattern.replace(regex, token => map[token] ?? token);
}

// ── 使用例 ──────────────────────────────
const d = new Date('2026-04-06T09:30:05');

console.log(formatDate(d, 'YYYY/MM/DD'));          // "2026/04/06"
console.log(formatDate(d, 'YYYY-MM-DD'));          // "2026-04-06"
console.log(formatDate(d, 'YYYY年MM月DD日(ddd)')); // "2026年04月06日(Mon)"
console.log(formatDate(d, 'YYYY/MM/DD HH:mm:ss')); // "2026/04/06 09:30:05"
console.log(formatDate(d, 'MM/DD/YYYY'));           // "04/06/2026"

// 文字列や数値(Unixタイムスタンプ)も渡せる
console.log(formatDate('2026-01-01', 'YYYY年MM月DD日'));  // "2026年01月01日"
console.log(formatDate(1743897600000, 'YYYY/MM/DD'));     // タイムスタンプも可
ライブラリ不要で実装するメリット:

  • バンドルサイズへの影響ゼロ(date-fns は tree-shake しても数KB〜十数KB増える)
  • Node.js・ブラウザ・Deno どこでも動く
  • 型注釈を加えれば TypeScript でもそのまま利用可能

文字列パースの落とし穴:「9時間ずれ」問題

日付文字列を new Date() に渡すとき、区切り文字の違いで解釈されるタイムゾーンが変わり、JST(日本時間)では9時間ずれることがあります。

NG と OK の違い(9時間ずれ問題)
// NG: ハイフン区切りは ISO 8601 として UTC 解釈される
const d1 = new Date('2026-04-06');
console.log(d1.toLocaleString('ja-JP'));  // "2026/04/06 9:00:00" ← JST では +9h 分ずれる

// OK: スラッシュ区切りはローカルタイム(JST)として解釈される
const d2 = new Date('2026/04/06');
console.log(d2.toLocaleString('ja-JP'));  // "2026/04/06 0:00:00" ← 意図通り

// OK: 日付のみ比較・フォーマット用途ならどちらもローカル日付メソッドで問題なし
//     ただし「日付またぎ」が起きることを知っておく
const d3 = new Date('2026-04-06');
console.log(`${d3.getFullYear()}/${d3.getMonth()+1}/${d3.getDate()}`);
// → JST では "2026/4/6"(ローカル時刻 09:00 なので日付は正しい)

// OK: タイムゾーンを明示した ISO 8601
const d4 = new Date('2026-04-06T00:00:00+09:00');
console.log(d4.toLocaleString('ja-JP'));  // "2026/4/6 0:00:00" ← 明示指定なら確実
「YYYY-MM-DD」と「YYYY/MM/DD」で挙動が違う理由:
ECMAScript の仕様上、ハイフン区切り YYYY-MM-DD は ISO 8601 として UTC の 00:00 に解釈されます。一方スラッシュ区切り YYYY/MM/DD は実装依存(多くの環境でローカルタイム)です。DB や API から受け取った日付文字列をパースするときは、タイムゾーンを明示した ISO 8601 拡張形式2026-04-06T00:00:00+09:00)を使うのが最も安全です。
安全な日付パース:区切り文字を統一する
/**
 * "YYYY-MM-DD" を JST の 00:00 として安全にパースする
 */
function parseLocalDate(str) {
  // ハイフンをスラッシュに置換 → ローカルタイムとして解釈される
  return new Date(str.replace(/-/g, '/'));
}

// または、年・月・日を個別に渡す(最も確実)
function parseLocalDateExplicit(str) {
  const [y, m, d] = str.split('-').map(Number);
  return new Date(y, m - 1, d);  // new Date(年, 月-1, 日) はローカルタイム
}

console.log(parseLocalDate('2026-04-06').toLocaleString('ja-JP'));         // "2026/4/6 0:00:00"
console.log(parseLocalDateExplicit('2026-04-06').toLocaleString('ja-JP')); // "2026/4/6 0:00:00"

toLocaleDateString() と Intl.DateTimeFormat の使い方

ロケール(言語・地域)に応じた自動フォーマットには toLocaleDateString()Intl.DateTimeFormat が使えます。

toLocaleDateString() の基本
const d = new Date('2026-04-06T09:30:00');

// 日本語形式
console.log(d.toLocaleDateString('ja-JP'));
// → "2026/4/6"

console.log(d.toLocaleDateString('ja-JP', {
  year: 'numeric', month: '2-digit', day: '2-digit'
}));
// → "2026/04/06"

console.log(d.toLocaleDateString('ja-JP', {
  year: 'numeric', month: 'long', day: 'numeric', weekday: 'short'
}));
// → "2026年4月6日(月)"

// 時刻込み(toLocaleString)
console.log(d.toLocaleString('ja-JP', {
  year: 'numeric', month: '2-digit', day: '2-digit',
  hour: '2-digit',  minute: '2-digit', second: '2-digit',
  hour12: false,
}));
// → "2026/04/06 09:30:00"
Intl.DateTimeFormat でフォーマッタを再利用
// 同じフォーマッタを複数の日付に使い回すなら Intl.DateTimeFormat がパフォーマンスに優れる
const fmt = new Intl.DateTimeFormat('ja-JP', {
  year: 'numeric', month: '2-digit', day: '2-digit',
});

const dates = [
  new Date('2026-04-01'),
  new Date('2026-04-15'),
  new Date('2026-04-30'),
];

dates.forEach(d => {
  console.log(fmt.format(d));  // "2026/04/01", "2026/04/15", "2026/04/30"
});
Intl API の詳細な使い方:
Intl.DateTimeFormat を使った日付・数値の国際化フォーマット(多言語対応・タイムゾーン指定・相対時間表示など)の詳細はIntl APIを使った日付・数値の国際化フォーマットで詳しく解説しています。

タイムゾーンを指定して変換する

API のレスポンスなど UTC で届いたタイムスタンプを、特定のタイムゾーンのローカル時刻として表示したい場面があります。

timeZone オプションで変換
const d = new Date('2026-04-06T00:00:00Z');  // UTC の 0:00

// UTC → JST(+9h)
console.log(d.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' }));
// → "2026/4/6 9:00:00"

// UTC → PST(-8h)
console.log(d.toLocaleString('en-US', { timeZone: 'America/Los_Angeles' }));
// → "4/5/2026, 4:00:00 PM"(UTC 0時 = PST 前日 16時)

// formatDate() にタイムゾーン対応を追加する場合
function formatDateTZ(date, pattern, timeZone = 'Asia/Tokyo') {
  const parts = new Intl.DateTimeFormat('en-CA', {
    timeZone,
    year: 'numeric', month: '2-digit', day: '2-digit',
    hour: '2-digit',  minute: '2-digit', second: '2-digit',
    hour12: false,
  }).formatToParts(date);

  const p = {};
  parts.forEach(({ type, value }) => { p[type] = value; });

  const map = {
    'YYYY': p.year,
    'MM'  : p.month,
    'DD'  : p.day,
    'HH'  : p.hour === '24' ? '00' : p.hour,  // midnight edge case
    'mm'  : p.minute,
    'ss'  : p.second,
  };

  return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, t => map[t] ?? t);
}

console.log(formatDateTZ(new Date('2026-04-06T00:00:00Z'), 'YYYY/MM/DD HH:mm', 'Asia/Tokyo'));
// → "2026/04/06 09:00"

ISO 8601 形式(YYYY-MM-DD)で出力する

REST API や DB への入力、localStorage への保存など、機械間通信にはロケール非依存の ISO 8601YYYY-MM-DD または YYYY-MM-DDTHH:mm:ssZ)が推奨されます。

ISO 8601 出力の各方法
const d = new Date('2026-04-06T09:30:00+09:00');

// ① toISOString() → UTC 基準(末尾に Z が付く)
console.log(d.toISOString());
// → "2026-04-06T00:30:00.000Z"(UTC 変換されることに注意)

// ② ローカルタイムで ISO 8601 形式を作る
function toLocalISO(date) {
  const Y  = date.getFullYear();
  const M  = String(date.getMonth() + 1).padStart(2, '0');
  const D  = String(date.getDate()).padStart(2, '0');
  const h  = String(date.getHours()).padStart(2, '0');
  const mi = String(date.getMinutes()).padStart(2, '0');
  const s  = String(date.getSeconds()).padStart(2, '0');
  return `${Y}-${M}-${D}T${h}:${mi}:${s}`;
}

console.log(toLocalISO(d));  // "2026-04-06T09:30:00"(ローカル時刻)

// ③ 日付のみ(YYYY-MM-DD)
function toLocalDateStr(date) {
  const Y = date.getFullYear();
  const M = String(date.getMonth() + 1).padStart(2, '0');
  const D = String(date.getDate()).padStart(2, '0');
  return `${Y}-${M}-${D}`;
}

console.log(toLocalDateStr(d));  // "2026-04-06"
toISOString() は UTC で出力することに注意:
new Date().toISOString() は常に UTC 基準で出力されます。JST(UTC+9)の午前0時は UTC では前日の15時になるため、2026-04-06T00:00:00+09:00toISOString() すると"2026-04-05T15:00:00.000Z" と前日日付になります。DB や API に日付文字列を渡す際は、ローカルISO形式(上の toLocalISO())か、タイムゾーン付き形式(+09:00)を使ってください。

将来の標準:Temporal API

現在の Date オブジェクトの問題点(タイムゾーン設計の不一致、getMonth() の0始まり、ミュータブルなど)を解決する Temporal API がECMAScriptの標準化プロセスにあります(2025年時点で Stage 3)。

Temporal API のプレビュー(将来の書き方)
// ※ Temporal APIはPolyfill(@js-temporal/polyfill)が必要、または対応ブラウザで利用可能
// import { Temporal } from "@js-temporal/polyfill";

// 今日の日付(ローカルタイムゾーン)
const today = Temporal.Now.plainDateISO();
console.log(today.toString());  // "2026-04-06"

// 日付のフォーマット
const dt = Temporal.PlainDate.from('2026-04-06');
console.log(dt.toLocaleString('ja-JP', {
  year: 'numeric', month: '2-digit', day: '2-digit',
}));  // "2026/04/06"

// 月が1始まり(0始まり問題なし)
console.log(dt.month);  // 4(1始まり、加算不要!)

// タイムゾーン付き
const zonedDT = Temporal.ZonedDateTime.from('2026-04-06T09:30:00+09:00[Asia/Tokyo]');
console.log(zonedDT.toString()); // "2026-04-06T09:30:00+09:00[Asia/Tokyo]"
今すぐ使うか・Temporal を待つか:
本番コードで今すぐ安定して動く実装が必要なら、この記事で解説したバニラJSの padStart() アプローチか Intl.DateTimeFormat を使ってください。Temporal APIはPolyfillで試すことができますが、仕様変更の可能性があるため本番採用は正式リリース後をおすすめします。

よくある質問(FAQ)

Qdate-fns や Day.js は使うべきですか?
A定型フォーマット変換だけならバニラJSで十分です。一方で「N日後を計算」「営業日判定」「相対時間(〜前)」「タイムゾーン変換を多用する」など複雑な日付操作が多い場合は date-fns や Day.js の導入を検討してください。date-fns は tree-shaking に対応しており、使う関数のみバンドルに含められます。
Qmoment.js はまだ使えますか?
Amoment.js はメンテナンスモードに移行しており、新規プロジェクトへの採用は非推奨です。既存コードで使用中の場合はすぐ壊れるわけではありませんが、軽量な date-fns や Day.js への移行を計画することをおすすめします。
QtoLocaleDateString() の出力がブラウザごとに変わることはありますか?
Aロケール('ja-JP' など)を必ず第1引数に指定してください。省略するとユーザーの OS 設定に依存し、環境によって出力が変わります。ロケールを明示すれば主要ブラウザ(Chrome/Firefox/Safari/Edge)で一貫した出力が得られます。
Q日付文字列を取得したら Invalid Date になります。原因は?
A代表的な原因は①フォーマットが仕様外(例: "2026.04.06")、②スペルミス("2026-4-6" はOKですが "2026-4-6T" はNG)、③存在しない日付("2026-02-30")です。new Date(str) を渡した後 isNaN(d.getTime()) で検証するか、年・月・日をパースして new Date(y, m-1, d) で生成すると確実です。
QUnix タイムスタンプ(ミリ秒)から日付文字列に変換するには?
Anew Date(timestamp) に数値(ミリ秒)を渡すだけで Date オブジェクトを作れます。例: new Date(1743897600000)。秒単位のタイムスタンプ(Unix time)の場合は new Date(timestamp * 1000) と1000倍して渡してください。

まとめ

やりたいこと 推奨アプローチ
YYYY/MM/DD など固定フォーマット padStart() + テンプレートリテラル、または formatDate()
曜日・日本語月名など 配列で曜日・月名を定義して getDay() / getMonth() で引く
多言語・ロケール対応 Intl.DateTimeFormat または toLocaleDateString()(ロケール必須指定)
タイムゾーン変換 toLocaleString()timeZone オプション
ISO 8601 出力(API/DB向け) toLocalISO()(ローカル時刻) または toISOString()(UTC)
文字列パース ハイフンをスラッシュ置換 or new Date(y, m-1, d) で9時間ずれ防止

日付の取得・加算・減算など操作全般はJavaScriptで時間の計算をマスターしようを、日付文字列をDateオブジェクトに変換する方法は文字列をDate型に変換する方法をあわせてご覧ください。