Last active
October 23, 2024 12:06
-
-
Save manabuyasuda/09175a988876e03385ea4e9d1397e386 to your computer and use it in GitHub Desktop.
Intl.RelativeTimeFormatで相対日付に変換する
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 相対的な日付を取得する関数です。 | |
* 6ヶ月以上前の日付は絶対日付で、それ以外は「〜前」の形式で出力します。 | |
* 週や月はカレンダー通りではなく、日数(ミリ秒)をベースにした近似値なので、わずかに誤差があります。 | |
* @param updatedAt - 更新日時の文字列 | |
* @param now - 現在の日時(デフォルトは現在時刻) | |
* @returns 相対的な日付を表す文字列 | |
*/ | |
export function getRelativeDate( | |
updatedAt: string, | |
now: Date = new Date(), | |
): string { | |
// Dateオブジェクトに変換した更新日時 | |
const updated = new Date(updatedAt); | |
// 現在時刻と更新日時の差をミリ秒で取得する | |
const diffMs = now.getTime() - updated.getTime(); | |
// 更新日時が何ヶ月前か計算する | |
// 例1:nowが2024年6月(2024-06)、updatedが2023年12月(2023-12)の場合 | |
// 1. 年数:2024 - 2023 = 1, 1 * 12 = 12ヶ月 | |
// 2. 月数:6 - 12 = -6ヶ月 | |
// 3. 合計:12 + (-6) = 6ヶ月 | |
// 例2:nowが2024年6月(2024-06)、updatedが2022年12月(2022-12)の場合 | |
// 1. 年数:2024 - 2022 = 2, 2 * 12 = 24ヶ月 | |
// 2. 月数:6 - 12 = -6ヶ月 | |
// 3. 合計:24 + (-6) = 18ヶ月 | |
const diffMonths = | |
(now.getFullYear() - updated.getFullYear()) * 12 + | |
now.getMonth() - | |
updated.getMonth(); | |
// 更新日時が6ヶ月以上前の場合はYYYY.MM.DD形式で出力する | |
if (Math.abs(diffMonths) >= 6) { | |
return ( | |
updated | |
.toLocaleDateString('ja-JP', { | |
// 年を数値で出力 | |
year: 'numeric', | |
// 月を2桁で出力 | |
month: '2-digit', | |
// 日を2桁で出力 | |
day: '2-digit', | |
}) | |
// YYYY/MM/DD形式になるため、すべてのスラッシュをドットに変換する | |
.replace(/\//g, '.') | |
); | |
} | |
// Intl.RelativeTimeFormatのインスタンスを作成 | |
const rtf = new Intl.RelativeTimeFormat('ja', { | |
// 必ず数値で出力する(`auto`にすると「今」や「先月」といった数値以外の表記になる) | |
numeric: 'always', | |
}); | |
// 時間差に応じて適切な単位を選択するためのデータを定義する | |
// 月や週は固定された数値ではないため、誤差は出るが近似値とする | |
const units: [Intl.RelativeTimeFormatUnit, number][] = [ | |
// 30日をひと月とする近似値(30 * 24 * 60 * 60 * 1000) | |
['month', 2_592_000_000], | |
// 7日を1週間とする近似値(7 * 24 * 60 * 60 * 1000) | |
['week', 604_800_000], | |
// 1日のミリ秒数(24 * 60 * 60 * 1000) | |
['day', 86_400_000], | |
// 1時間のミリ秒数(60 * 60 * 1000) | |
['hour', 3_600_000], | |
// 1分のミリ秒数(60 * 1000) | |
['minute', 60_000], | |
// 1秒のミリ秒数(1000) | |
['second', 1_000], | |
]; | |
// 単位をループして適切な単位でフォーマットする | |
for (const [unit, msInUnit] of units) { | |
// 現在の単位のミリ秒以上であれば、その単位が適用される | |
// 運用的には起こり得ないが、1秒未満の場合も処理されるようにする | |
if (Math.abs(diffMs) >= msInUnit || unit === 'second') { | |
// 「1.25日前」のような出力を避けるため、相対時間を整数で計算する | |
// 例:diffMsが3,725,000ミリ秒約1時間2分5秒の場合 | |
// Math.floor(3,725,000 / 3,600,000) = 1時間 | |
const value = Math.floor(Math.abs(diffMs) / msInUnit); | |
return ( | |
rtf | |
// 負の値にして過去の日付にする | |
.format(-value, unit) | |
// 「3日後」のようになるので「3日前」のように変換する | |
.replace('後', '前') | |
// 「1 時間前」のように入るスペースを削除する | |
.replace(' ', '') | |
// デザインどおり「〜か月」を「〜ヶ月」に変換する(「か月」のほうが適切そう) | |
// https://www.nhk.or.jp/bunken/summary/kotoba/term/006.html | |
.replace('か月', 'ヶ月') | |
); | |
} | |
} | |
// ここには到達しないはずだが、TypeScriptの型チェックを満たすために追加する | |
return '0分前'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment