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 が出力されます。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日"
String(n).padStart(2, '0') は「文字列を最低2文字にし、足りない桁を先頭の ‘0’ で埋める」という処理です。例: String(4).padStart(2, '0') → '04'、String(12).padStart(2, '0') → '12'(変化なし)。よく使われるフォーマット一覧と変換例
実務でよく登場する形式をまとめます。すべてバニラJSで実装できます。
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)"
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")を渡すだけで変換できるユーティリティ関数を作成します。
/**
* 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: ハイフン区切りは 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" ← 明示指定なら確実
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 が使えます。
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 がパフォーマンスに優れる
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.DateTimeFormat を使った日付・数値の国際化フォーマット(多言語対応・タイムゾーン指定・相対時間表示など)の詳細はIntl APIを使った日付・数値の国際化フォーマットで詳しく解説しています。タイムゾーンを指定して変換する
API のレスポンスなど UTC で届いたタイムスタンプを、特定のタイムゾーンのローカル時刻として表示したい場面があります。
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 8601(YYYY-MM-DD または YYYY-MM-DDTHH:mm:ssZ)が推奨されます。
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"
new Date().toISOString() は常に UTC 基準で出力されます。JST(UTC+9)の午前0時は UTC では前日の15時になるため、2026-04-06T00:00:00+09:00 を toISOString() すると"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は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]"
本番コードで今すぐ安定して動く実装が必要なら、この記事で解説したバニラJSの
padStart() アプローチか Intl.DateTimeFormat を使ってください。Temporal APIはPolyfillで試すことができますが、仕様変更の可能性があるため本番採用は正式リリース後をおすすめします。よくある質問(FAQ)
'ja-JP' など)を必ず第1引数に指定してください。省略するとユーザーの OS 設定に依存し、環境によって出力が変わります。ロケールを明示すれば主要ブラウザ(Chrome/Firefox/Safari/Edge)で一貫した出力が得られます。"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) で生成すると確実です。new 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型に変換する方法をあわせてご覧ください。
