RelativeDateTimeFormatter: 概要、相対日付とSwift

著者: IT Sectr 公開日: 2026-07-13 読了時間: 10 分

RelativeDateTimeFormatterは、iOSとmacOSのFoundationクラスで、絶対日付を人間が読める相対表現に変換します:「5分前」、「昨日」、「3日後」。Apple Developer Documentation, 2024によると、RelativeDateTimeFormatterは適切な単位(秒、分、時間、日)を自動的に選択し、デバイスの現在のロケールの言語で出力をローカライズします。Calendarを介して日付間の差を手動計算するのとは異なり、このクラスは各言語の言語学的特徴を考慮します:一部の言語では数詞が活用され、他の言語では「昨日」という単語に特別な形式が使用されます。このクラスはiOS 13およびmacOS 10.15以降で利用可能です。

重要なポイント

  • RelativeDateTimeFormatter — iOSおよびmacOSで相対日付を表示するためのクラス(iOS 13+)
  • ローカライズされた出力 — 現在のロケールの言語で自動的に表現を選択
  • 3種類のコンテキスト — 過去(前)、未来(後)、現在(今)で異なる表現
  • 自動単位選択 — 秒、分、時間、日、週、月、年
  • スタイルのカスタマイズ — numeric(3日後)またはabbreviated(3日後)

RelativeDateTimeFormatterとは?

RelativeDateTimeFormatterは、FoundationのFormatterのサブクラスで、Date(または秒単位の差)を受け取り、相対時間を含むローカライズされた文字列を返します。例えば、現在より5分前の日付の場合、「5分前」を返します。このクラスは3つの時間コンテキスト(過去、未来、現在)をサポートしています。

RelativeDateTimeFormatterの内部ロジックは、CalendarとLocaleを使用して日付間の差を計算し、正しい文法形式を選択します。日本語の場合、「1分前」と「5分前」を適切に選びます。この機能はICU(International Components for Unicode)データに基づいており、開発者による追加設定は不要です。

Apple WWDC 2019によると、RelativeDateTimeFormatterはローカライズを簡素化するためにフレームワークの一部となりました。導入前は、開発者は手動で日付の差を計算し、String.localizedStringWithFormatを介してローカライズされた文字列を置き換える必要がありました。これにより、活用エラー(特にスラブ語やアラビア語)や測定単位の誤った選択が発生していました。

RelativeDateTimeFormatterは「5分前」をどのように表示するか?

アルゴリズムは3つのステップで構成されます:渡された日付と現在時刻の差の計算、適切な単位の選択(ゼロにならない最大のもの)、ロケールに従った書式設定。例えば、3720秒(1時間2分)の差の場合、「時間」単位が選択され、結果は「1時間前」となり、「62分前」とはなりません。

単位は「最大非ゼロ」の原則に従って選択されます:差が86400秒(1日)を超える場合は日、604800(1週間)を超える場合は週が使用されます。このアルゴリズムにより、結果が常に自然に読めることが保証されます:「518400秒前」ではなく、ユーザーは「6日前」を目にします。単位の正確な境界は現在のロケールのカレンダーによって決定されます。

差の範囲単位ja_JPの例
0〜59秒Seconds30秒前
1〜59分Minutes5分前
1〜23時間Hours3時間前
1〜6日Days2日前
7〜27日Weeks1週間前
28日〜11ヶ月Months3ヶ月前
12ヶ月以上Years1年前

書式設定コンテキストはフレーズの末尾を決定します。過去:「前」(日本語)。未来:「3日後」(日本語)。現在:「今」(日本語)。コンテキストはlocalizeString(fromTimeInterval:)メソッドまたはstring(from: Date)を介して直接設定されます。

単位とスタイルの設定

RelativeDateTimeFormatterは出力を制御するためのいくつかの設定を提供します:unitsStyleプロパティは書式設定スタイル(numeric、abbreviated、full、spellOut)を決定し、maximumUnitCountは表示される単位の数を制限します。例えば、maximumUnitCount = 1の場合、1時間30分の差は「1時間30分前」ではなく「1時間前」と表示されます。

書式設定スタイル

  • .numeric — 完全な数値:「3日前」、「2週間後」。デフォルトのUIに推奨
  • .abbreviated — 短縮形:「3日前」、「2週間後」。テーブルやリストでのコンパクト表示用
  • .full — 数字を使わない口語形:「三日前」。アクセシビリティと音声インターフェース用
  • .spellOut — 代替表記の文字形式:「three days ago」。まれに使用され、主に特殊な用途向け

単位の制限:デフォルトでは、RelativeDateTimeFormatterは1つの(最大の)単位のみを表示します。maximumUnitCount = 2を設定すると、より正確な説明のために次の単位が含まれます:「1時間30分前」。ただし、これにより短いメッセージ(プッシュ通知、アラート)では文字列が過度に長くなる可能性があります。UIでは、maximumUnitCount = 1を維持することをお勧めします。

swift
import Foundation

let formatter = RelativeDateTimeFormatter()

// Configure styles
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Examples with different dates
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 min ago: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")

let twoDaysLater = Date().addingTimeInterval(172800)
print("2 days later: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")

// Abbreviated style
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Abbreviated: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")

// Full style (spelled out)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Full: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")

異なるコンテキストでのスタイルの選択:ニュースフィードには、maximumUnitCount = 1で.numericを使用します — これはTwitter、Instagram、Facebookの標準です。アクセシビリティ(VoiceOver)には、.fullを使用します — スペルアウトされた数字はより自然に読み上げられます。コンパクトな要素(通知バッジ、ステータスバー)には、スペースを節約するために.abbreviatedを使用します。

SwiftにおけるRelativeDateTimeFormatterの例

基本的な使用法は、インスタンスの作成、プロパティの設定、書式設定メソッドの呼び出しに集約されます。主なメソッドは:localizedString(for:relativeTo:) — 日付のペア用、localizedString(fromTimeInterval:) — 秒単位の差用、string(for:) — 自動コンテキスト(過去/未来)付きのDate用です。

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Social network UI examples
let postDates: [(title: String, date: Date)] = [
    ("Just now", Date().addingTimeInterval(-30)),
    ("5 min ago", Date().addingTimeInterval(-300)),
    ("Yesterday", Date().addingTimeInterval(-90000)),
    ("Last week", Date().addingTimeInterval(-700000)),
    ("Last year", Date().addingTimeInterval(-32000000))
]

for (title, postDate) in postDates {
    let relative = formatter.localizedString(
        for: postDate,
        relativeTo: Date()
    )
    print("\(title): \(relative)")
}

// Future dates
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
    for: inOneHour,
    relativeTo: Date()
)
print("Reminder: \(reminderText)")

「たった今」シナリオの処理 — RelativeDateTimeFormatterには、非常に短い間隔に対する「たった今」という表現の組み込みサポートがありません。5秒未満の差の場合、「0秒前」を返し、UIで見栄えが悪くなります。フォーマッタの呼び出しを条件付きロジックでラップすることをお勧めします:差が設定されたしきい値(例:5秒)未満の場合は手動で「たった今」を表示し、それ以外の場合は日付をフォーマッタに渡します。

swift
import Foundation

func relativeTimeString(from date: Date) -> String {
    let interval = Date().timeIntervalSince(date)

    // "Just now" threshold
    if interval < 5 {
        return "just now"
    }

    // "Today" threshold
    if interval < 60 {
        return "just now"
    }

    let formatter = RelativeDateTimeFormatter()
    formatter.unitsStyle = .numeric
    formatter.maximumUnitCount = 1

    // Display without "ago" suffix
    return formatter.localizedString(
        for: date,
        relativeTo: Date()
    )
}

print(relativeTimeString(from: Date().addingTimeInterval(-3)))
print(relativeTimeString(from: Date().addingTimeInterval(-120)))
print(relativeTimeString(from: Date().addingTimeInterval(-3600)))

string(fromTimeInterval:)メソッドは秒単位の差を受け入れ、自動的にコンテキストを決定します(正の値 — 未来、負の値 — 過去)。これは差が既にわかっている場合(例:サーバーからUnixタイムスタンプとして受信)に便利です。この場合、Dateを作成する必要はなく、差が直接渡されます。

相対日付のローカライズ

RelativeDateTimeFormatterはLocale.currentに基づいて自動的に出力をローカライズします。書式設定言語を変更するには、localeプロパティを設定します — DateFormatterとは異なり、RelativeDateTimeFormatterではロケールは固定されておらず、呼び出しごとに変更できます。これにより、インターフェース言語とは異なる言語で相対日付を表示できます(例:元の言語のコンテンツ)。

相対日付のローカライズの複雑さは、異なる言語の文法的特徴にあります。日本語では「1分前」、「5分前」のように数詞の後に適切な助数詞が付きます。アラビア語は3〜10の数字に複数形、11以上に特別な形式を使用します。中国語には活用がなく、タスクが簡素化されます。RelativeDateTimeFormatterはICUルールを通じてこれらすべてのケースをカバーし、追加のコードは不要です。

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

let targetDate = Date().addingTimeInterval(-7200) // 2 hours ago

// Different locales
let locales: [String] = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "ar_SA"]

for identifier in locales {
    formatter.locale = Locale(identifier: identifier)
    let result = formatter.localizedString(
        for: targetDate,
        relativeTo: Date()
    )
    print("\(identifier): \(result)")
}

// Check Japanese pluralization
formatter.locale = Locale(identifier: "ru_RU")
let intervals: [TimeInterval] = [-60, -120, -180, -300]
for interval in intervals {
    let date = Date().addingTimeInterval(interval)
    print("\(-Int(interval / 60)) min: \(formatter.localizedString(for: date, relativeTo: Date()))")
}

重要なニュアンス:RelativeDateTimeFormatterは、.numeric設定の差の計算時にTimeZoneを無視します — 秒単位の絶対差を使用します。ただし、.fullスタイル(スペルアウトされた数字)と特別なケース(昨日、今日)では、TimeZoneが考慮されます。一貫性のために常にTimeZoneを明示的に設定してください。特にアプリケーションがUTCのサーバー日付を扱う場合は重要です。

よくある書式設定エラー

相対日付計算時のTimeZoneの無視 — サーバー日付を扱う際の一般的な間違いです。サーバーがUTCでDateを送信し、RelativeDateTimeFormatterがTimeZone.currentを使用する場合、現在時刻に近い日付の差が正しく計算されない可能性があります。サーバーデータには常にformatter.timeZone = TimeZone(secondsFromGMT: 0)を設定することをお勧めします。

短い間隔での誤った単位選択 — RelativeDateTimeFormatterは差を最大単位に丸めます。25時間の場合、結果は「1日前」となり、ユーザーを誤解させる可能性があります。高い精度が必要な場合(例:カウントダウンタイマー)、RelativeDateTimeFormatterの代わりにDateComponentsFormatterを使用してください — 複数の単位を同時に表示できます。

負のTimeIntervalのチェック不足 — 未来の日付が過去として渡された場合(string(fromTimeInterval:)で負の値)、フォーマッタは誤った文字列を返す可能性があります。フォーマッタに渡す前に常に間隔の符号をチェックしてください。特にタイムゾーンが計算を歪める可能性のあるサーバーデータを扱う場合は重要です。

Hacker News(2024)によると、RelativeDateTimeFormatterの最も議論されている問題の1つは、英語での「昨日」と「今日」の組み込みサポートの欠如です。「昨日」の代わりに、90000秒の差のフォーマッタは「1日前」を返します。日本語にはそのような問題はなく、「1日前」は自然に聞こえますが、英語のUIでは「yesterday」が好ましいです。この機能はサポートされておらず、Calendar.isDateInToday/Yesterdayによる手動チェックが必要です。

よくある質問

RelativeDateTimeFormatterとは?

RelativeDateTimeFormatterは、日付を相対形式で表示するためのFoundationクラスです:「5分前」、「2日後」。iOS 13およびmacOS 10.15以降で利用可能です。

RelativeDateTimeFormatterはどのように単位を選択しますか?

最大非ゼロ単位の原則による — 秒、分、時間、日、週、月、年。例えば、3720秒(1時間2分)の差の場合、「時間」単位が選択され、「分」ではありません。

出力言語を変更するには?

localeプロパティを設定します目的のLocaleインスタンスに。デフォルトではLocale.currentが使用されます。例:ドイツ語の場合、formatter.locale = Locale(identifier: "de_DE")。

.numericと.abbreviatedの違いは?

.numeric — 完全形式(「3日前」)、.abbreviated — 短縮形式(「3日前」)。選択はコンテキストに依存します:メインUIにはnumeric、コンパクト要素にはabbreviated。

「0秒前」の代わりに「たった今」を表示するには?

手動チェックを追加します5〜10秒未満の間隔に対して。RelativeDateTimeFormatterは「たった今」をサポートしていません — 小さな間隔では「0秒前」を返します。しきい値付きの条件付きロジックを使用してください。

まとめ

  • RelativeDateTimeFormatter — iOS 13+で相対日付を表示するための便利なクラス
  • 自動ローカライズ — ICUによる全サポート言語の正しい活用
  • 3つのスタイル — .numeric(標準)、.abbreviated(コンパクト)、.full(文字表記)
  • 単位選択 — 最大非ゼロ値に基づく自動選択
  • TimeZone設定 — サーバー日付を扱う際の一貫性のために必須
  • 「たった今」しきい値 — 組み込みでは未サポート。手動の間隔チェックが必要
  • 「昨日」のサポートなし — フォーマッタは英語のyesterday形式を使用しない

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください