RelativeDateTimeFormatter: สาระสำคัญ วันที่สัมพัทธ์และ Swift

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-13 เวลาอ่าน: 10 นาที

RelativeDateTimeFormatter เป็นคลาส Foundation ใน iOS และ macOS ที่แปลงวันที่แบบสัมบูรณ์เป็นวลีสัมพัทธ์ที่มนุษย์อ่านได้: "5 นาทีที่แล้ว", "เมื่อวานนี้", "ใน 3 วัน"> ตาม Apple Developer Documentation, 2024, RelativeDateTimeFormatter จะเลือกหน่วยที่เหมาะสม (วินาที นาที ชั่วโมง วัน) โดยอัตโนมัติและแปลผลลัพธ์เป็นภาษาของสถานที่ปัจจุบันของอุปกรณ์ แตกต่างจากการคำนวณความแตกต่างระหว่างวันที่ด้วยตนเองผ่าน Calendar คลาสนี้คำนึงถึงลักษณะทางภาษาของแต่ละภาษา: สำหรับบางภาษา ตัวเลขจะผันรูป สำหรับภาษาอื่น จะใช้รูปแบบพิเศษสำหรับคำว่า "เมื่อวานนี้" คลาสนี้พร้อมใช้งานตั้งแต่ iOS 13 และ macOS 10.15

ประเด็นสำคัญ

  • RelativeDateTimeFormatter — คลาสสำหรับแสดงวันที่สัมพัทธ์ใน iOS และ macOS (iOS 13+)
  • ผลลัพธ์ที่แปลแล้ว — เลือกวลีในภาษาของสถานที่ปัจจุบันโดยอัตโนมัติ
  • สามประเภทบริบท — อดีต (ที่แล้ว), อนาคต (ใน), ปัจจุบัน (ตอนนี้) ด้วยวลีที่แตกต่างกัน
  • การเลือกหน่วยอัตโนมัติ — วินาที นาที ชั่วโมง วัน สัปดาห์ เดือน ปี
  • การปรับแต่งสไตล์ — numeric (ใน 3 วัน) หรือ abbreviated (ใน 3 ว.)

RelativeDateTimeFormatter คืออะไร

RelativeDateTimeFormatter เป็นคลาสย่อยของ Formatter ใน Foundation ที่รับ Date (หรือความแตกต่างเป็นวินาที) และส่งคืนสตริงที่แปลแล้วพร้อมเวลาสัมพัทธ์ ตัวอย่างเช่น สำหรับวันที่ 5 นาทีก่อนวันที่ปัจจุบัน จะส่งคืน "5 นาทีที่แล้ว" คลาสรองรับสามบริบทเวลา: อดีต อนาคต และปัจจุบัน

ตรรกะภายในของ RelativeDateTimeFormatter ใช้ Calendar และ Locale เพื่อคำนวณความแตกต่างระหว่างวันที่และเลือกรูปแบบไวยากรณ์ที่ถูกต้อง สำหรับภาษาไทย จะเลือกระหว่าง "1 นาทีที่แล้ว" และ "5 นาทีที่แล้ว" ฟังก์ชันการทำงานนี้ขึ้นอยู่กับข้อมูล ICU (International Components for Unicode) และไม่ต้องการการกำหนดค่าเพิ่มเติมจากนักพัฒนา

ตาม Apple WWDC 2019 RelativeDateTimeFormatter ได้กลายเป็นส่วนหนึ่งของเฟรมเวิร์กเพื่อทำให้การแปลง่ายขึ้น — ก่อนที่จะมีการนำเสนอ นักพัฒนาต้องคำนวณความแตกต่างของวันที่ด้วยตนเองและแทนที่สตริงที่แปลแล้วผ่าน String.localizedStringWithFormat สิ่งนี้นำไปสู่ข้อผิดพลาดในการผันคำ (โดยเฉพาะสำหรับภาษาสลาฟและอาหรับ) และการเลือกหน่วยวัดที่ไม่ถูกต้อง

RelativeDateTimeFormatter แสดง "5 นาทีที่แล้ว" อย่างไร

อัลกอริทึม ของ RelativeDateTimeFormatter ประกอบด้วยสามขั้นตอน: การคำนวณความแตกต่างระหว่างวันที่ที่ส่งผ่านและช่วงเวลาปัจจุบัน การเลือกหน่วยที่เหมาะสม (ใหญ่ที่สุดที่ไม่ให้ค่าศูนย์) และการจัดรูปแบบตามสถานที่ ตัวอย่างเช่น สำหรับความแตกต่าง 3720 วินาที (1 ชั่วโมง 2 นาที) จะเลือกหน่วย "ชั่วโมง" และผลลัพธ์คือ "1 ชั่วโมงที่แล้ว" ไม่ใช่ "62 นาทีที่แล้ว"

หน่วยถูกเลือกตามหลักการ "ใหญ่ที่สุดที่ไม่เป็นศูนย์": หากความแตกต่างมากกว่า 86400 วินาที (1 วัน) จะใช้วัน หากมากกว่า 604800 (1 สัปดาห์) — จะใช้สัปดาห์ และอื่นๆ อัลกอริทึมนี้รับประกันว่าผลลัพธ์จะอ่านได้อย่างเป็นธรรมชาติเสมอ: แทนที่จะเป็น "518400 วินาทีที่แล้ว" ผู้ใช้จะเห็น "6 วันที่แล้ว" ขอบเขตที่แน่นอนของหน่วยถูกกำหนดโดยปฏิทินของสถานที่ปัจจุบัน

ช่วงความแตกต่างหน่วยตัวอย่างสำหรับ th_TH
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 ชั่วโมงที่แล้ว" แทนที่จะเป็น "1 ชั่วโมง 30 นาทีที่แล้ว"

สไตล์การจัดรูปแบบ

  • .numeric — ค่าตัวเลขเต็ม: "3 วันที่แล้ว", "ใน 2 สัปดาห์" แนะนำสำหรับ UI เริ่มต้น
  • .abbreviated — รูปแบบย่อ: "3 ว. ที่แล้ว", "ใน 2 สัปดาห์" สำหรับการแสดงผลแบบกะทัดรัดในตารางและรายการ
  • .full — รูปแบบคำพูดโดยไม่มีตัวเลข: "สามวันที่แล้ว" สำหรับการเข้าถึงและอินเทอร์เฟซเสียง
  • .spellOut — รูปแบบตัวอักษรด้วยการสะกดทางเลือก: "three days ago" ใช้น้อยมาก ส่วนใหญ่สำหรับการใช้งานเฉพาะทาง

การจำกัดหน่วย: โดยค่าเริ่มต้น RelativeDateTimeFormatter จะแสดงเพียงหนึ่งหน่วย (ใหญ่ที่สุด) การตั้งค่า 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()))")

การเลือกสไตล์สำหรับบริบทต่างๆ: สำหรับฟีดข่าว ให้ใช้ .numeric ด้วย maximumUnitCount = 1 — นี่เป็นมาตรฐานสำหรับ Twitter, Instagram และ Facebook สำหรับการเข้าถึง (VoiceOver) ให้ใช้ .full — ตัวเลขที่สะกดจะถูกอ่านอย่างเป็นธรรมชาติมากกว่า สำหรับองค์ประกอบขนาดกะทัดรัด (ป้ายการแจ้งเตือน แถบสถานะ) ให้ใช้ .abbreviated เพื่อประหยัดพื้นที่

RelativeDateTimeFormatter ใน 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:) รับความแตกต่างเป็นวินาทีและกำหนดบริบทโดยอัตโนมัติ (ค่าบวก — อนาคต ค่าลบ — อดีต) สะดวกเมื่อทราบความแตกต่างอยู่แล้ว (เช่น ได้รับจากเซิร์ฟเวอร์เป็น timestamp unix) ในกรณีนี้ ไม่จำเป็นต้องสร้าง Date — ความแตกต่างจะถูกส่งโดยตรง

การแปลวันที่สัมพัทธ์

RelativeDateTimeFormatter จะแปลผลลัพธ์โดยอัตโนมัติตาม Locale.current ในการเปลี่ยนภาษาการจัดรูปแบบ ให้ตั้งค่าคุณสมบัติ locale — แตกต่างจาก DateFormatter สำหรับ RelativeDateTimeFormatter สถานที่ไม่ได้รับการแก้ไขและสามารถเปลี่ยนแปลงได้ในแต่ละการเรียก ซึ่งช่วยให้แสดงวันที่สัมพัทธ์ในภาษาที่แตกต่างจากภาษาอินเทอร์เฟซ (เช่น เนื้อหาในภาษาต้นฉบับ)

ความซับซ้อนของการแปลวันที่สัมพัทธ์อยู่ที่ลักษณะทางไวยากรณ์ของภาษาต่างๆ ภาษาไทยต้องการรูปแบบตัวเลขที่แตกต่างกัน: "1 นาที", "2 นาที" ภาษาอาหรับใช้รูปพหูพจน์สำหรับตัวเลขตั้งแต่ 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 Thai 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 ไม่สนใจ TimeZone เมื่อคำนวณความแตกต่างสำหรับการตั้งค่า .numeric — ใช้ความแตกต่างสัมบูรณ์เป็นวินาที อย่างไรก็ตาม สำหรับสไตล์ .full (ด้วยตัวเลขที่สะกด) และกรณีพิเศษ (เมื่อวานนี้ วันนี้) จะพิจารณา TimeZone ตั้งค่า TimeZone อย่างชัดเจนเสมอเพื่อความสอดคล้อง โดยเฉพาะอย่างยิ่งหากแอปพลิเคชันทำงานกับวันที่ของเซิร์ฟเวอร์ใน UTC

ข้อผิดพลาดการจัดรูปแบบทั่วไป

การไม่สนใจ TimeZone เมื่อคำนวณวันที่สัมพัทธ์ — ข้อผิดพลาดทั่วไปเมื่อทำงานกับวันที่ของเซิร์ฟเวอร์ หากเซิร์ฟเวอร์ส่ง Date ใน UTC และ RelativeDateTimeFormatter ใช้ TimeZone.current ความแตกต่างอาจคำนวณไม่ถูกต้องสำหรับวันที่ใกล้กับช่วงเวลาปัจจุบัน แนะนำให้ตั้งค่า formatter.timeZone = TimeZone(secondsFromGMT: 0) สำหรับข้อมูลเซิร์ฟเวอร์เสมอ

การเลือกหน่วยที่ไม่ถูกต้องสำหรับช่วงเวลาสั้น — RelativeDateTimeFormatter จะปัดเศษความแตกต่างเป็นหน่วยที่ใหญ่ที่สุด สำหรับ 25 ชั่วโมง ผลลัพธ์จะเป็น "1 วันที่แล้ว" ซึ่งอาจทำให้ผู้ใช้เข้าใจผิด หากต้องการความแม่นยำสูง (เช่น สำหรับตัวจับเวลาถอยหลัง) ให้ใช้ DateComponentsFormatter แทน RelativeDateTimeFormatter — สามารถแสดงหลายหน่วยพร้อมกันได้

ขาดการตรวจสอบ TimeInterval ติดลบ — หากวันที่ในอนาคตถูกส่งผ่านเป็นอดีต (ค่าลบใน string(fromTimeInterval:)) ฟอร์แมตเตอร์อาจส่งคืนสตริงที่ไม่ถูกต้อง ตรวจสอบเครื่องหมายของช่วงเวลาก่อนส่งไปยังฟอร์แมตเตอร์เสมอ โดยเฉพาะเมื่อทำงานกับข้อมูลเซิร์ฟเวอร์ที่เขตเวลาอาจบิดเบือนการคำนวณ

ตาม Hacker News (2024) หนึ่งในปัญหาที่ถูกพูดถึงมากที่สุดของ RelativeDateTimeFormatter คือการขาดการสนับสนุนในตัวสำหรับ "เมื่อวานนี้" และ "วันนี้" สำหรับภาษาอังกฤษ แทนที่จะเป็น "เมื่อวานนี้" ฟอร์แมตเตอร์สำหรับความแตกต่าง 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 ว. ที่แล้ว") การเลือกขึ้นอยู่กับบริบท: numeric สำหรับ UI หลัก abbreviated สำหรับองค์ประกอบขนาดกะทัดรัด

จะแสดง "เมื่อกี้" แทน "0 วินาทีที่แล้ว" อย่างไร

เพิ่มการตรวจสอบด้วยตนเอง สำหรับช่วงเวลาน้อยกว่า 5-10 วินาที RelativeDateTimeFormatter ไม่รองรับ "เมื่อกี้" — สำหรับช่วงเวลาเล็กจะส่งคืน "0 วินาทีที่แล้ว" ใช้ตรรกะแบบมีเงื่อนไขพร้อมเกณฑ์

สรุป

  • RelativeDateTimeFormatter — คลาสที่สะดวกสำหรับแสดงวันที่สัมพัทธ์ใน iOS 13+
  • การแปลอัตโนมัติ — การผันคำที่ถูกต้องสำหรับทุกภาษาที่รองรับผ่าน ICU
  • สามสไตล์ — .numeric (มาตรฐาน), .abbreviated (กะทัดรัด), .full (ตัวอักษร)
  • การเลือกหน่วย — อัตโนมัติตามค่าที่ไม่เป็นศูนย์มากที่สุด
  • การกำหนดค่า TimeZone — จำเป็นสำหรับความสอดคล้องเมื่อทำงานกับวันที่ของเซิร์ฟเวอร์
  • เกณฑ์ "เมื่อกี้" — ไม่รองรับในตัว; ต้องการการตรวจสอบช่วงเวลาด้วยตนเอง
  • ไม่รองรับ "เมื่อวานนี้" — ฟอร์แมตเตอร์ไม่ใช้รูปแบบ yesterday สำหรับภาษาอังกฤษ

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม