※タイトルに[JavaScript]と書いておきながら、コードはTypeScriptです。JavaScriptにする方法は後述します。
JavaScriptのIntlオブジェクトを使えば、西暦から和暦を調べることができます。
参考:JavaScriptが令和に対応。Intl.DateTimeFormatで日付を和暦(元号)表記に変換する #Chrome – Qiita
例えば、「2019/5/1」を和暦に変換すると「令和1/5/1」になり、「2019/4/30」を和暦に変換すると「平成31/4/30」となります。
ということは、JavaScriptのエンジン内部では元号と改元日の情報を持っているわけです。
というわけで、調べるコードを書いてみました。
/** 元号の漢字取得用フォーマット */
const formatterKanji = new Intl.DateTimeFormat("ja-JP-u-ca-japanese", {
era: "long",
});
/** 元号のイニシャル取得用フォーマット */
const formatterInitial = new Intl.DateTimeFormat("ja-JP-u-ca-japanese", {
era: "narrow",
});
/** 一日のミリ秒 */
const ONEDAY_MS = 86400000;
/** 和暦情報の型 */
type WarekiInfo = {
eraKanji: string;
eraInitial: string;
warekiYear: number | null;
year: number;
month: number;
day: number;
date: Date;
};
/** 和暦取得 */
function getWareki(date: Date): WarekiInfo {
const partsKanji = formatterKanji.formatToParts(date);
const partsInitial = formatterInitial.formatToParts(date);
const eraKanji =
partsKanji.find((val) => val.type === "era")?.value ?? "不明";
const warekiYear =
partsKanji.find((val) => val.type === "year")?.value ?? "-1";
const eraInitial =
partsInitial.find((val) => val.type === "era")?.value ?? "不明";
return {
eraKanji,
eraInitial,
warekiYear: warekiYear === "元" ? 1 : +warekiYear > 0 ? +warekiYear : null,
year: date.getFullYear(),
month: date.getMonth() + 1,
day: date.getDate(),
date,
};
}
/** 指定の日付が元年でなければ、元年12月31日を取得 */
function getWarekiStartLastDate(date: Date): WarekiInfo | null {
const dateWareki = getWareki(date);
if (dateWareki.warekiYear === null) {
return null;
}
if (dateWareki.warekiYear === 1) {
return dateWareki;
}
const startYear = date.getFullYear() - dateWareki.warekiYear + 1;
const retDate = new Date(startYear, 11, 31, 0, 0, 0);
if (retDate.getFullYear() !== startYear) {
// startYearが2桁の場合に1900年代になっている
retDate.setFullYear(startYear);
}
return {
...dateWareki,
warekiYear: 1,
date: retDate,
};
}
/** 同じ年の1月1日を取得 */
function getFirstDate(date: Date) {
const firstDate = new Date(+date);
firstDate.setMonth(0);
firstDate.setDate(1);
return firstDate;
}
/** 指定日の元号の開始日取得 */
function researchGengouStartDate(date: Date): WarekiInfo | null {
let baseWareki = getWarekiStartLastDate(date);
if (baseWareki === null) return null;
let rightWareki = baseWareki;
let leftWareki = getWareki(getFirstDate(rightWareki.date));
if (leftWareki.eraInitial === rightWareki.eraInitial) {
return leftWareki;
}
let middleWareki = getWareki(
new Date((+rightWareki.date + +leftWareki.date) / 2)
);
while (+middleWareki.date - +leftWareki.date > ONEDAY_MS) {
if (middleWareki.eraInitial === baseWareki.eraInitial) {
rightWareki = middleWareki;
} else {
leftWareki = middleWareki;
}
middleWareki = getWareki(
new Date((+rightWareki.date + +leftWareki.date) / 2)
);
}
if (middleWareki.eraInitial === baseWareki.eraInitial) {
return middleWareki;
}
return rightWareki;
}
/** 過去の元号と改元日取得 */
function getWarekiList(): WarekiInfo[] {
const warekiList: WarekiInfo[] = [];
let date = new Date();
while (true) {
const wareki = researchGengouStartDate(date);
if (wareki === null) {
break;
}
warekiList.push(wareki);
// 改元日の一日前を取得
date = new Date(+wareki.date - ONEDAY_MS);
}
return warekiList;
}
// 元号表示
getWarekiList().forEach((warekiInfo) => {
console.log(
`${warekiInfo.eraKanji}(${warekiInfo.eraInitial}):${warekiInfo.year}/${warekiInfo.month}/${warekiInfo.day}`
);
});
長いですが、やっていることは下記の通りです。
1.指定の日付(最初は今日)を取得して、その和暦を取得する
2.1で取得した和暦が元年ならその日付、そうでなければ和暦の年を西暦から引いて+1したもの(元年)の年の12月31日を取得
3.2で取得した日付と、その年の1月1日の日付を初期値として2分探索で改元日を探す
4.3で取得した改元日の1日前を取得して1~3を取得できるまで繰り返す
5.取得した和暦情報の一覧をコンソールに出力する
TypeScript Playgroundにコード貼って、「Run」を押すと実行できます。
「.JS」タブには変換後のJavaScriptコードが記載されています。
注意点として、明治以前の改元日は現在の西暦(グレゴリオ暦)ではなく、旧暦の日付となっているようです。明治の改元日は西暦だと1868年10月23日なのですが、このコードで調べると1868年9月8日となっています。
また、イニシャルが取得できるのは明治以降のみです(慶応以前は、{era: “narrow”}としても漢字で取得されます)。
後、2分探索で探している都合上、WarekiInfo.dateの日時は0時0分0秒ではないです。
warekiYear === "元" ? 1 : +warekiYear > 0 ? +warekiYear : null
としている箇所がありますが、場合によっては年が「元」となることがあるので一応、こうしています。
少なくとも、現在のChrome(v138)では、DateTimeFormatの第二引数のoptionsに{era: 'long', year: 'numeric'}
と指定すると「元年」となるようです({era: 'long', year: 'numeric', month: 'numeric', day: 'numeric'}
とすると「1」になるので仕様がよく分からないです…)。
それと、今回調べて初めて知ったのですが、Dateオブジェクトのコンストラクタの引数の年に2桁の数値をいれると、1990年代になってしまうようです。和暦が始まったの(大化の改心)は645年と3桁なので問題ないのですが、一応、年が変わってないか調べてます。
今に限ったことではないですが、Dateオブジェクトは扱いにくいですね。早く、Temporalが普通に使えるようにならないかなと思います(こちらは旧暦にも対応しているようです)。
Temporalで変わるJavaScriptの日時操作 [JS Modern Features no.1] | gihyo.jp
コメント