きっかけ
「令和 7 年って西暦何年?」くらいならスマホの電卓で暗算できますが、**「昭和 64 年 1 月 7 日ってパースできる?」**と聞くと、多くの和暦コンバータがコケます。
- 昭和 64 年 1 月 7 日 → 1989-01-07
- 昭和 64 年 1 月 8 日 → 存在しない(その日は平成 1 年 1 月 8 日)
元号の切り替えは「年の境目」ではなく特定の日付で起きるので、「昭和 64 年 = 1989 年」だけでは足りず、月日まで見ないと正しく変換できません。この、境界日をまたぐ変換を正確に扱うツールを作りました。
作ったもの
和暦 ↔ 西暦変換 — https://sen.ltd/portfolio/era-converter/
- 西暦 → 和暦(
1989-01-07→ 昭和 64 年 1 月 7 日) - 和暦 → 西暦(
昭和62年5月3日→ 1987-05-03) -
「元年」パース対応(
平成元年= 平成 1 年) - 元号一覧表(読み仮名と開始日付き)
vanilla JS + HTML + CSS、ゼロ依存、ビルド不要。元号データは明治以降の 5 元号のみ。node --test で 19 ケース。
元号データは境界日の配列
元号定義は「その元号がいつから始まるか」の一覧です。月日まで持つのがポイント:
export const ERAS = [
{ id: 'meiji', name: '明治', reading: 'めいじ', start: [1868, 10, 23] },
{ id: 'taisho', name: '大正', reading: 'たいしょう', start: [1912, 7, 30] },
{ id: 'showa', name: '昭和', reading: 'しょうわ', start: [1926, 12, 25] },
{ id: 'heisei', name: '平成', reading: 'へいせい', start: [1989, 1, 8] },
{ id: 'reiwa', name: '令和', reading: 'れいわ', start: [2019, 5, 1] },
]
よく見る「明治 = 1868 年」という記述だけでは、1868 年の前半が江戸時代だったことを見落としてしまいます。明治の開始は 1868 年 10 月 23 日(改元詔書の日付)なので、例えば 1868 年 3 月 15 日は「明治 1 年 3 月 15 日」ではありません。
同じく、昭和 64 年は 1989 年 1 月 1 日から 7 日までの 7 日間しか存在しません。8 日以降は平成 1 年。
西暦 → 和暦の逆順探索
年月日の 3 要素を 1 つの単調増加な値として比較できれば、「どの元号の範囲に入るか」を二分探索 or 線形探索できます。タプル比較を使って境界日より新しい元号を逆順に探すのが素直:
function tupleCmp(a, b) {
if (a[0] !== b[0]) return a[0] - b[0]
if (a[1] !== b[1]) return a[1] - b[1]
return a[2] - b[2]
}
export function gregorianToEra(year, month, day) {
const date = [year, month, day]
if (tupleCmp(date, ERAS[0].start) < 0) {
return { error: 'before Meiji' }
}
for (let i = ERAS.length - 1; i >= 0; i--) {
const era = ERAS[i]
if (tupleCmp(date, era.start) >= 0) {
const eraYear = year - era.start[0] + 1
return { era, year: eraYear }
}
}
}
逆順で回して最初に境界日を過ぎている元号で止まると、その日付が属する元号が求まります。1989-01-07 なら平成の境界 [1989, 1, 8] に届かないので昭和にマッチ、1989-01-08 なら平成の境界にぴったり or 超えたので平成にマッチ。
元号年 (eraYear) の計算は year - era.start[0] + 1。昭和の開始年が 1926 なので、1989 年は 1989 - 1926 + 1 = 64 年目、正確。
和暦 → 西暦の「元年」パース
和暦の文字列入力には 平成元年 とか 令和元年 という書き方が普通に出てきます。元 は「1」の意味で、年を 1 から始める習慣の名残。
export function parseEraString(str) {
const match = /^(明治|大正|昭和|平成|令和)\s*(\d+|元)(?:年(?:\s*(\d+)月)?(?:\s*(\d+)日)?)?$/.exec(
str.trim()
)
if (!match) return { error: '読み取れない和暦形式' }
const [, name, yearStr, month, day] = match
const era = ERAS.find((e) => e.name === name)
const year = yearStr === '元' ? 1 : Number(yearStr)
return {
era,
year,
month: month ? Number(month) : undefined,
day: day ? Number(day) : undefined,
}
}
正規表現の (\d+|元) で数字または「元」を捕まえて、year 計算時に '元' === 1 に変換。これで 平成元年 と 平成1年 が同じ値に畳み込まれます。
月日は optional。令和7年 だけなら year: 7, month: undefined, day: undefined、昭和62年5月3日 なら全部入る。UI 側で「月日を補完する場合は元号の開始月日を使う」か「西暦年だけ返す」を選べます。
範囲チェック:昭和 65 年は存在しない
昭和65年 と入力されると弾きたい。元号の範囲は次の元号の開始年で決まるので:
const nextIdx = ERAS.indexOf(era) + 1
if (nextIdx < ERAS.length) {
const nextStart = ERAS[nextIdx].start
if (gregYear > nextStart[0]) {
return { error: `${era.name} ${eraYear}年 は範囲外(${era.name} は最大 ${nextStart[0] - era.start[0] + 1}年)` }
}
}
昭和は 1926 + 64 - 1 = 1989 年までなので、昭和 65 年 (1990 年) は平成境界の 1989 を超えて NG。エラーメッセージに「最大 64 年」と具体的な数字を入れているのは、ユーザーが即座に訂正できるようにするため。
テスト
node --test で 19 ケース。境界周辺と「元年」処理が中心:
test('Showa to Heisei boundary: 1989-01-07 is Showa 64', () => {
const r = gregorianToEra(1989, 1, 7)
assert.equal(r.era.name, '昭和')
assert.equal(r.year, 64)
})
test('Showa to Heisei boundary: 1989-01-08 is Heisei 1', () => {
const r = gregorianToEra(1989, 1, 8)
assert.equal(r.era.name, '平成')
assert.equal(r.year, 1)
})
test('Heisei to Reiwa boundary: 2019-04-30 is Heisei 31', () => {
const r = gregorianToEra(2019, 4, 30)
assert.equal(r.era.name, '平成')
assert.equal(r.year, 31)
})
test('Heisei to Reiwa boundary: 2019-05-01 is Reiwa 1', () => {
const r = gregorianToEra(2019, 5, 1)
assert.equal(r.era.name, '令和')
assert.equal(r.year, 1)
})
test('parse 令和元年 as Reiwa year 1', () => {
const r = parseEraString('令和元年')
assert.equal(r.era.name, '令和')
assert.equal(r.year, 1)
})
境界日の両側をテストしているのがミソ。片側だけテストすると >= を > に書き間違えたバグが見逃されます。
おわりに
SEN 合同会社の ポートフォリオシリーズ 100+ の 10 件目です。
- 📦 レポジトリ: https://github.com/sen-ltd/era-converter
- 🌐 ライブデモ: https://sen.ltd/portfolio/era-converter/
- 🏢 会社: https://sen.ltd/
「平成から遡って慶応 / 江戸まで対応したい」系のリクエストは Issue でお待ちしてます(今のところ明治以降のみ)。
