RelativeDateTimeFormatter adalah kelas Foundation di iOS dan macOS yang mengubah tanggal absolut menjadi formulasi relatif yang mudah dibaca manusia: “5 menit yang lalu”, “kemarin”, “dalam 3 hari”. Menurut Apple Developer Documentation, 2024, RelativeDateTimeFormatter secara otomatis memilih unit yang sesuai (detik, menit, jam, hari) dan melokalkan output dalam bahasa locale perangkat saat ini. Berbeda dengan penghitungan manual selisih antar tanggal melalui Calendar, kelas ini memperhitungkan karakteristik linguistik setiap bahasa: untuk beberapa bahasa bilangan dideklinasi, untuk bahasa lain — digunakan bentuk khusus untuk kata “kemarin”. Kelas ini tersedia mulai iOS 13 dan macOS 10.15.
Hal utama
RelativeDateTimeFormatter adalah subkelas dari Formatter di Foundation yang menerima Date (atau selisih dalam detik) dan mengembalikan string terlokalisasi dengan waktu relatif. Misalnya, untuk tanggal 5 menit lebih awal dari tanggal saat ini, ia akan mengembalikan “5 menit yang lalu” untuk ru_RU atau “5 minutes ago” untuk en_US. Kelas ini mendukung tiga konteks waktu: masa lalu (past), masa depan (future), dan masa kini (present).
Logika internal RelativeDateTimeFormatter menggunakan Calendar dan Locale untuk menghitung selisih antar tanggal dan memilih bentuk gramatikal yang benar. Untuk bahasa Indonesia, kelas ini mendeklinasi bilangan dengan benar: “1 menit yang lalu”, “2 menit yang lalu”, “5 menit yang lalu”. Untuk bahasa Inggris — memilih antara “minute ago” dan “minutes ago”. Fungsionalitas ini didasarkan pada data ICU (International Components for Unicode) dan tidak memerlukan konfigurasi tambahan dari pengembang.
Menurut Apple WWDC 2019, RelativeDateTimeFormatter menjadi bagian dari framework untuk menyederhanakan lokalisasi — sebelum kemunculannya, pengembang terpaksa menghitung selisih tanggal secara manual dan menyisipkan string terlokalisasi melalui String.localizedStringWithFormat. Ini menyebabkan kesalahan dalam deklinasi (terutama untuk bahasa Slavia dan Arab) dan pemilihan unit pengukuran yang tidak tepat.
Algoritma kerja RelativeDateTimeFormatter terdiri dari tiga langkah: menghitung selisih antara tanggal yang diberikan dan momen saat ini, memilih unit yang sesuai (yang terbesar yang tidak menghasilkan nol), dan memformat dengan memperhitungkan locale. Misalnya, untuk selisih 3720 detik (1 jam 2 menit) akan dipilih unit “jam”, dan hasilnya adalah “1 jam yang lalu”, bukan “62 menit yang lalu”.
Unit dipilih berdasarkan prinsip “terbesar yang tidak nol”: jika selisih lebih besar dari 86400 detik (1 hari), digunakan hari; jika lebih besar dari 604800 (1 minggu) — minggu, dan seterusnya. Algoritma ini memastikan bahwa hasil selalu terbaca secara alami: alih-alih “518400 detik yang lalu”, pengguna melihat “6 hari yang lalu”. Batas pasti unit ditentukan oleh kalender locale saat ini.
| Rentang selisih | Unit | Contoh untuk ru_RU |
|---|---|---|
| 0–59 detik | Seconds | 30 detik yang lalu |
| 1–59 menit | Minutes | 5 menit yang lalu |
| 1–23 jam | Hours | 3 jam yang lalu |
| 1–6 hari | Days | 2 hari yang lalu |
| 7–27 hari | Weeks | 1 minggu yang lalu |
| 28 hari–11 bulan | Months | 3 bulan yang lalu |
| 12+ bulan | Years | 1 tahun yang lalu |
Konteks pemformatan menentukan akhir frasa. Untuk masa lalu: “yang lalu” (Indonesia), “ago” (Inggris). Untuk masa depan: “dalam 3 hari” (Indonesia), “in 3 days” (Inggris). Untuk masa kini: “sekarang” (Indonesia), “now” (Inggris). Konteks diatur melalui metode localizeString(fromTimeInterval:) atau langsung melalui string(from: Date).
RelativeDateTimeFormatter menyediakan beberapa pengaturan untuk mengontrol output: properti unitsStyle menentukan gaya pemformatan (numeric, abbreviated, full, spellOut), dan maximumUnitCount membatasi jumlah unit yang ditampilkan. Misalnya, dengan maximumUnitCount = 1, selisih 1 jam 30 menit akan ditampilkan sebagai “1 jam yang lalu”, bukan “1 jam 30 menit yang lalu”.
Pembatasan unit: secara default RelativeDateTimeFormatter hanya menampilkan satu (terbesar) unit. Mengatur maximumUnitCount = 2 mengaktifkan unit berikutnya untuk deskripsi yang lebih akurat: “1 jam 30 menit yang lalu”. Namun, ini dapat membuat string terlalu panjang untuk pesan singkat (push, notifikasi). Untuk UI disarankan membiarkan maximumUnitCount = 1.
import Foundation
let formatter = RelativeDateTimeFormatter()
// Konfigurasi gaya
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Contoh dengan tanggal yang berbeda
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 mnt lalu: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")
let twoDaysLater = Date().addingTimeInterval(172800)
print("2 hari lagi: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")
// Gaya singkatan
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Singkatan: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")
// Gaya penuh (tertulis)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Penuh: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")
Memilih gaya untuk konteks yang berbeda: untuk feed berita gunakan .numeric dengan maximumUnitCount = 1 — ini standar untuk Twitter, Instagram, dan Facebook. Untuk Accessibility (VoiceOver) gunakan .full — angka yang dituliskan terbaca lebih natural. Untuk elemen ringkas (sudut notifikasi, baris status) gunakan .abbreviated untuk menghemat ruang.
Penggunaan dasar RelativeDateTimeFormatter terangkum dalam membuat instance, mengonfigurasi properti, dan memanggil salah satu metode pemformatan. Metode utama: localizedString(for:relativeTo:) — untuk sepasang tanggal, localizedString(fromTimeInterval:) — untuk selisih dalam detik, dan string(for:) — untuk Date dengan konteks otomatis (masa lalu/masa depan).
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Contoh UI media sosial
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)")
}
// Tanggal masa depan
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
for: inOneHour,
relativeTo: Date()
)
print("Pengingat: \(reminderText)")
Menangani skenario “baru saja” — RelativeDateTimeFormatter tidak memiliki dukungan bawaan untuk frasa “baru saja” untuk interval yang sangat kecil. Untuk selisih kurang dari 5 detik, ia akan mengembalikan “0 detik yang lalu”, yang terlihat buruk di UI. Disarankan untuk membungkus panggilan formatter dalam logika kondisional: jika selisihnya lebih kecil dari ambang batas yang ditentukan (misalnya 5 detik) — tampilkan “baru saja” secara manual, jika tidak, berikan tanggal ke formatter.
import Foundation
func relativeTimeString(from date: Date) -> String {
let interval = Date().timeIntervalSince(date)
// Ambang batas “baru saja”
if interval < 5 {
return "just now"
}
// Ambang batas “har ini”
if interval < 60 {
return "just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Tampilkan tanpa akhiran “yang lalu”
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)))
Metode string(fromTimeInterval:) menerima selisih dalam detik dan secara otomatis menentukan konteks (nilai positif — masa depan, negatif — masa lalu). Ini nyaman ketika selisihnya sudah diketahui (misalnya, diterima dari server sebagai unix timestamp). Dalam kasus ini, tidak perlu membuat Date — selisih diberikan secara langsung.
RelativeDateTimeFormatter secara otomatis melokalkan output berdasarkan Locale.current. Untuk mengubah bahasa pemformatan, atur properti locale — tidak seperti DateFormatter, untuk RelativeDateTimeFormatter locale tidak tetap dan dapat diubah untuk setiap panggilan. Ini memungkinkan menampilkan tanggal relatif dalam bahasa yang berbeda dari bahasa antarmuka (misalnya, konten dalam bahasa asli).
Kompleksitas lokalisasi tanggal relatif terletak pada karakteristik gramatikal berbagai bahasa. Bahasa Rusia membutuhkan berbagai bentuk bilangan: “1 menit”, “2 menit”, “5 menit”. Bahasa Arab — menggunakan bentuk jamak untuk angka 3 hingga 10 dan bentuk khusus untuk 11+. Bahasa Cina — tidak memiliki deklinasi sama sekali, yang menyederhanakan tugas. RelativeDateTimeFormatter mencakup semua kasus ini melalui aturan ICU, tanpa perlu kode tambahan.
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
let targetDate = Date().addingTimeInterval(-7200) // 2 jam yang lalu
// Locale yang berbeda
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)")
}
// Periksa deklinasi Rusia
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)) mnt: \(formatter.localizedString(for: date, relativeTo: Date()))")
}
Nuansa penting: RelativeDateTimeFormatter mengabaikan TimeZone saat menghitung selisih untuk pengaturan .numeric — ia menggunakan selisih absolut dalam detik. Namun, untuk gaya .full (dengan angka tertulis) dan kasus khusus (kemarin, hari ini), TimeZone diperhitungkan. Selalu atur TimeZone secara eksplisit untuk konsistensi, terutama jika aplikasi bekerja dengan tanggal server dalam UTC.
Mengabaikan TimeZone saat menghitung tanggal relatif — kesalahan umum saat bekerja dengan tanggal server. Jika server mengirim Date dalam UTC, dan RelativeDateTimeFormatter menggunakan TimeZone.current, selisihnya dapat dihitung tidak tepat untuk tanggal yang dekat dengan momen saat ini. Disarankan untuk selalu mengatur formatter.timeZone = TimeZone(secondsFromGMT: 0) untuk data server.
Pemilihan unit yang salah untuk interval pendek — RelativeDateTimeFormatter membulatkan selisih ke unit terbesar. Untuk 25 jam, hasilnya akan menjadi “1 hari yang lalu”, yang dapat menyesatkan pengguna. Jika diperlukan presisi tinggi (misalnya, untuk timer hitung mundur), gunakan DateComponentsFormatter, bukan RelativeDateTimeFormatter — ia memungkinkan menampilkan beberapa unit secara bersamaan.
Tidak ada pemeriksaan TimeInterval negatif — jika tanggal di masa depan diberikan sebagai masa lalu (nilai negatif dalam string(fromTimeInterval:)), formatter dapat mengembalikan string yang salah. Selalu periksa tanda interval sebelum memberikan ke formatter, terutama saat bekerja dengan data server, di mana zona waktu dapat mendistorsi penghitungan.
Menurut Hacker News (2024), salah satu masalah yang paling banyak dibahas dari RelativeDateTimeFormatter adalah kurangnya dukungan bawaan untuk “kemarin” dan “hari ini” dalam bahasa Inggris. Alih-alih “kemarin”, formatter untuk selisih 90000 detik akan mengembalikan “1 hari yang lalu”. Untuk bahasa Rusia, masalah seperti itu tidak ada — “1 hari yang lalu” terdengar alami, tetapi untuk UI bahasa Inggris, “yesterday” lebih disukai. Fungsionalitas ini tidak didukung dan memerlukan pemeriksaan manual melalui Calendar.isDateInToday/Yesterday.
Pertanyaan yang sering diajukan
RelativeDateTimeFormatter — kelas Foundation untuk menampilkan tanggal dalam format relatif: “5 menit yang lalu”, “dalam 2 hari”. Tersedia sejak iOS 13 dan macOS 10.15.
Berdasarkan prinsip unit terbesar yang tidak nol — detik, menit, jam, hari, minggu, bulan, atau tahun. Misalnya, untuk selisih 3720 detik (1 jam 2 menit) akan dipilih unit “jam”, bukan “menit”.
Atur properti locale ke instance Locale yang diinginkan. Secara default, Locale.current digunakan. Contoh: formatter.locale = Locale(identifier: "de_DE") untuk bahasa Jerman.
.numeric — bentuk lengkap (“3 hari yang lalu”), .abbreviated — singkatan (“3 hari yang lalu”). Pilihan tergantung pada konteks: numeric untuk UI utama, abbreviated untuk elemen ringkas.
Tambahkan pemeriksaan manual untuk interval kurang dari 5–10 detik. RelativeDateTimeFormatter tidak mendukung “baru saja” — untuk interval kecil ia mengembalikan “0 detik yang lalu”. Gunakan logika kondisional dengan ambang batas.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga