Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Last active October 23, 2024 12:06
Show Gist options
  • Save manabuyasuda/09175a988876e03385ea4e9d1397e386 to your computer and use it in GitHub Desktop.
Save manabuyasuda/09175a988876e03385ea4e9d1397e386 to your computer and use it in GitHub Desktop.
Intl.RelativeTimeFormatで相対日付に変換する
/**
* 相対的な日付を取得する関数です。
* 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