DateIntervalFormatter là một lớp Foundation trong iOS và macOS được thiết kế để định dạng phạm vi ngày thành chuỗi bản địa hóa: “21–25 tháng 7 năm 2024”, “từ ngày 21 đến ngày 25 tháng 7 năm 2024” hoặc “Jul 21–25, 2024”. Theo Apple Developer Documentation, 2024, DateIntervalFormatter tự động xác định thành phần ngày nào cần lặp lại (nếu phạm vi trong cùng một tháng, tháng được hiển thị một lần) và bản địa hóa dấu phân cách và thứ tự từ. Không giống như việc nối thủ công hai ngày đã định dạng, lớp này tính đến các quy tắc ngữ pháp của ngôn ngữ. Lớp này có sẵn từ iOS 8 và macOS 10.10.
Các điểm chính
DateIntervalFormatter là một lớp con của Formatter trong Foundation nhận một đối tượng DateInterval (một phạm vi giữa hai ngày) và trả về một chuỗi bản địa hóa mô tả phạm vi đó. Ví dụ, cho phạm vi từ ngày 21 đến ngày 25 tháng 7 năm 2024, kết quả sẽ là “21–25 tháng 7 năm 2024” cho vi_VN. Lớp này thông minh nhóm các phần tử lặp lại: nếu cả hai ngày trong cùng một tháng, tên tháng được hiển thị một lần.
Giá trị chính của DateIntervalFormatter là xử lý tự động ngữ pháp phạm vi. Khi định dạng thủ công, nhà phát triển phải so sánh tháng/năm bắt đầu và kết thúc, chọn dấu phân cách (dấu gạch ngang, “đến”, “bis”) và xử lý biến cách. DateIntervalFormatter thực hiện việc này tự động dựa trên dữ liệu CLDR (Common Locale Data Repository), giảm lỗi trong các ứng dụng quốc tế, đặc biệt cho các ngôn ngữ có ngữ pháp phức tạp.
Theo Unicode CLDR v44 (2024), các quy tắc định dạng khoảng ngày khác nhau đáng kể giữa các ngôn ngữ. Tiếng Đức sử dụng giới từ “bis” (21.–25. Juli 2024), tiếng Pháp sử dụng “au” (du 21 au 25 juillet 2024), tiếng Nhật sử dụng dấu gạch nối (2024 Jul 21–25). DateIntervalFormatter bao gồm tất cả các biến thể này mà không cần thêm mã từ phía nhà phát triển.
Nguyên lý hoạt động của DateIntervalFormatter dựa trên việc phân tích hai ngày tạo thành một phạm vi và chọn biểu diễn chuỗi tối ưu cân nhắc đến ngôn ngữ địa phương. Lớp so sánh các thành phần ngày (năm, tháng, ngày) và đưa ra quyết định nhóm: nếu năm giống nhau, nó được hiển thị một lần; nếu tháng giống nhau, nó được hiển thị một lần; nếu ngày khác nhau, một phạm vi ngày được hiển thị.
| Phạm vi | vi_VN |
|---|---|
| Một ngày | 21 tháng 7 năm 2024 |
| Trong tháng | 21–25 tháng 7 năm 2024 |
| Tháng khác nhau | 21 tháng 7 – 5 tháng 8 năm 2024 |
| Năm khác nhau | 25 tháng 12 năm 2024 – 5 tháng 1 năm 2025 |
| Có giờ | 21 tháng 7 năm 2024, 14:30–16:45 |
Logic nhóm được triển khai dựa trên các mẫu CLDR. Bộ định dạng kiểm tra sự bằng nhau của từng thành phần ngày (Calendar.Component: year, month, day) theo thứ tự từ lớn đến nhỏ. Khi một thành phần khác, tất cả các thành phần nhỏ hơn được hiển thị cho cả hai ngày. Ví dụ, cho 21–25 tháng 7: năm khớp, tháng khớp, ngày khác → tháng được hiển thị một lần, các ngày được hiển thị cho mỗi ngày.
Lưu ý quan trọng: DateIntervalFormatter không hỗ trợ tùy chỉnh dấu phân cách qua API. Dấu phân cách được xác định bởi ngôn ngữ địa phương và không thể ghi đè. Nếu cần dấu phân cách không chuẩn (ví dụ, “đến” thay vì “đến”), bạn sẽ cần định dạng từng ngày thủ công bằng DateFormatter và nối chuỗi với dấu phân cách tùy chỉnh.
DateIntervalFormatter cung cấp hai bộ kiểu: dateStyle và timeStyle cho mỗi đầu của phạm vi. Các kiểu này hoạt động tương tự DateFormatter: .none ẩn thành phần, .short đưa ra định dạng ngắn (21/07/2024), .medium (21 thg 7, 2024), .long (21 tháng 7 năm 2024), .full (Chủ nhật, 21 tháng 7 năm 2024). Bằng cách kết hợp dateStyle và timeStyle, có thể đạt được các mức độ chi tiết khác nhau.
Quan trọng: dateStyle và timeStyle áp dụng cho cả hai đầu của phạm vi như nhau. Bạn không thể đặt kiểu khác nhau cho đầu và cuối (ví dụ, .medium cho đầu và .short cho cuối). Nếu cần kiểu khác nhau, bạn phải định dạng từng ngày bằng DateFormatter riêng và nối kết quả.
import Foundation
let formatter = DateIntervalFormatter()
// Kết hợp kiểu khác nhau
let startDate = Date()
let endDate = Date().addingTimeInterval(345600) // +4 ngày
let interval = DateInterval(start: startDate, end: endDate)
// Chỉ ngày, định dạng trung bình
formatter.dateStyle = .medium
formatter.timeStyle = .none
print("Chỉ ngày: \(formatter.string(from: interval))")
// Định dạng ngắn với giờ
formatter.dateStyle = .short
formatter.timeStyle = .short
print("Ngắn với giờ: \(formatter.string(from: interval))")
// Định dạng đầy đủ
formatter.dateStyle = .full
formatter.timeStyle = .none
print("Ngày đầy đủ: \(formatter.string(from: interval))")
Sử dụng cơ bản DateIntervalFormatter bao gồm tạo một phiên bản, đặt kiểu và gọi string(from:). Phương thức chấp nhận DateInterval, một cấu tr với các thuộc tính start và end. DateInterval có thể được tạo từ hai giá trị Date hoặc khởi tạo với Duration (iOS 16+). Sau khi định dạng, kết quả sẵn sàng để hiển thị trong giao diện mà không cần xử lý thêm.
import Foundation
let calendar = Calendar.current
let now = Date()
// Ví dụ khoảng cụ thể
let examples: [(title: String, start: Date, end: Date)] = [
(
"2-hour event",
now,
now.addingTimeInterval(7200)
),
(
"Week-long vacation",
now,
now.addingTimeInterval(604800)
),
(
"Cross-year period",
calendar.date(from: DateComponents(
year: 2024, month: 12, day: 25
))!,
calendar.date(from: DateComponents(
year: 2025, month: 1, day: 5
))!
)
]
let intervalFormatter = DateIntervalFormatter()
intervalFormatter.dateStyle = .medium
intervalFormatter.timeStyle = .short
for (title, start, end) in examples {
let interval = DateInterval(start: start, end: end)
let result = intervalFormatter.string(from: interval)
print("\(title): \(result)")
}
// Định dạng qua Calendar (iOS 16+)
if let nextWeek = calendar.date(
byAdding: .day, value: 7, to: now
) {
let weekInterval = DateInterval(start: now, end: nextWeek)
print("7 ngày tới: \(intervalFormatter.string(from: weekInterval))")
}
DateIntervalFormatter và đặt phòng lịch: trong các ứng dụng đặt phòng (khách sạn, vé), khoảng ngày là một yếu tố giao diện chính. DateIntervalFormatter xử lý chính xác các khoảng qua năm (ví dụ, 28 tháng 12 đến 3 tháng 1), nhóm tháng và năm riêng cho từng ngày. Cho các kịch bản này, nên sử dụng dateStyle = .medium và timeStyle = .none, định dạng dễ đọc nhất cho ngày không có giờ.
import Foundation
let formatter = DateIntervalFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
// Ví dụ đặt phòng
struct Booking {
let checkIn: Date
let checkOut: Date
var formattedInterval: String {
let interval = DateInterval(start: checkIn, end: checkOut)
return formatter.string(from: interval)
}
}
let booking = Booking(
checkIn: calendar.date(from: DateComponents(
year: 2024, month: 7, day: 21
))!,
checkOut: calendar.date(from: DateComponents(
year: 2024, month: 7, day: 25
))!
)
print("Đặt phòng: \(booking.formattedInterval)")
// Sử dụng với ngôn ngữ địa phương khác
let enFormatter = DateIntervalFormatter()
enFormatter.dateStyle = .medium
enFormatter.timeStyle = .none
enFormatter.locale = Locale(identifier: "en_US")
print("Tiếng Anh: \(enFormatter.string(from: interval))")
DateInterval vs DateIntervalFormatter: DateInterval là một cấu trúc đại diện cho một phạm vi ngày (bắt đầu và kết thúc). DateIntervalFormatter là bộ định dạng chuyển đổi cấu trúc này thành chuỗi. DateInterval cũng hỗ trợ kiểm tra contains(Date) và intersection(with:), hữu ích cho logic đặt phòng (kiểm tra chồng chéo ngày).
DateIntervalFormatter bản địa hóa đầu ra thông qua thuộc tính locale. Mặc định, Locale.current được sử dụng, ngôn ngữ địa phương của thiết bị. Để hiển thị khoảng bằng ngôn ngữ khác (ví dụ, nội dung tiếng Anh với giao diện tiếng Việt), hãy đặt locale một cách tường minh. Không giống DateFormatter, DateIntervalFormatter không yêu cầu đặt locale cho dữ liệu phía máy chủ. Nó luôn sử dụng logic nhóm nội bộ dựa trên CLDR.
Sự khác biệt ngôn ngữ trong định dạng khoảng ngày phức tạp hơn đáng kể so với ngày đơn lẻ. Tiếng Việt sử dụng giới từ “đến”: từ ngày 21 đến ngày 25 tháng 7. Tiếng Anh sử dụng dấu gạch ngang đơn giản: Jul 21–25. Tiếng Pháp sử dụng giới từ “au”: du 21 au 25 juillet. DateIntervalFormatter bao gồm tất cả các biến thể này.
import Foundation
let formatter = DateIntervalFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
let start = Date()
let end = Date().addingTimeInterval(259200) // +3 ngày
let interval = DateInterval(start: start, end: end)
// Hiển thị ngôn ngữ địa phương khác
let localeIds = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "zh_CN"]
for localeId in localeIds {
formatter.locale = Locale(identifier: localeId)
print("\(localeId): \(formatter.string(from: interval))")
}
// Bản địa hóa cho khoảng thời gian
formatter.dateStyle = .none
formatter.timeStyle = .short
let todayStart = Date()
let todayEnd = Date().addingTimeInterval(3600)
let timeInterval = DateInterval(start: todayStart, end: todayEnd)
for localeId in ["ru_RU", "en_US", "de_DE"] {
formatter.locale = Locale(identifier: localeId)
print("\(localeId) giờ: \(formatter.string(from: timeInterval))")
}
Đặc điểm bản địa hóa: cho tiếng Việt, DateIntervalFormatter sử dụng “đến” cho định dạng dài. Cho định dạng ngắn, nó sử dụng dấu gạch ngang. Lựa chọn định dạng phụ thuộc vào ngữ cảnh: cho danh sách đặt phòng sử dụng .medium, cho xem chi tiết sử dụng .long hoặc .full.
DateIntervalFormatter lấp đầy mảng định dạng phạm vi ngày, điều mà các lớp Foundation khác không bao phủ. DateFormatter định dạng ngày đơn lẻ, RelativeDateTimeFormatter định dạng ngày tương đối (cách đây/trong), DateComponentsFormatter định dạng thời lượng (2 giờ 30 phút). Mỗi lớp này giải quyết một nhiệm vụ cụ thể và không thể thay thế cho nhau.
| Lớp | Mục đích | Ví dụ đầu ra |
|---|---|---|
| DateIntervalFormatter | Phạm vi ngày | 21–25 tháng 7 năm 2024 |
| DateFormatter | Ngày đơn lẻ | 21 tháng 7 năm 2024 |
| RelativeDateTimeFormatter | Thời gian tương đối | cách đây 3 ngày |
| DateComponentsFormatter | Thời lượng | 2 giờ 30 phút |
| ISO8601DateFormatter | Định dạng ISO 8601 | 2024-07-21T14:30:00Z |
Khi nào sử dụng DateIntervalFormatter: để hiển thị phạm vi ngày trong giao diện — đặt phòng khách sạn, sự kiện, lọc theo ngày, lịch sử hoạt động. Để tính thời lượng giữa các ngày, sử dụng DateComponentsFormatter, không phải DateIntervalFormatter, vì cái sau không được thiết kế để hiển thị thời lượng. DateFormatter chỉ nên được sử dụng cho ngày đơn lẻ, không phải để nối hai ngày đã định dạng qua nối chuỗi, vì điều này dẫn đến mất bản địa hóa dấu phân cách.
Trường hợp đặc biệt: DateIntervalFormatter không hỗ trợ hiển thị khoảng mở (không có ngày kết thúc). Nếu kết thúc của phạm vi không xác định (ví dụ, “từ ngày 21 tháng 7”), sử dụng DateFormatter với giới từ “từ”. Ngoài ra, DateIntervalFormatter không hỗ trợ khoảng với giờ được chỉ định cho các ngày khác nhau. Giờ được áp dụng cho từng ngày riêng biệt, có thể trùng lặp cho các khoảng ngắn.
Câu hỏi thường gặp
DateIntervalFormatter là một lớp Foundation để định dạng phạm vi ngày thành chuỗi bản địa hóa. Có sẵn từ iOS 8, nó tự động nhóm các thành phần lặp lại (tháng, năm) và chọn dấu phân cách chính xác cho ngôn ngữ.
Nó so sánh các thành phần từ năm đến ngày. Nếu năm khớp, nó được hiển thị một lần. Nếu tháng khớp, nó được hiển thị một lần. Các ngày khác nhau được hiển thị dưới dạng phạm vi. Điều này cho “21–25 tháng 7 năm 2024” thay vì “21 tháng 7 năm 2024 – 25 tháng 7 năm 2024”.
Không, dấu phân cách được xác định bởi ngôn ngữ địa phương và không thể tùy chỉnh qua API. Cho dấu phân cách tùy chỉnh (ví dụ, “đến” thay vì “đến”), hãy định dạng từng ngày bằng DateFormatter riêng và nối chuỗi thủ công.
DateIntervalFormatter định dạng một phạm vi ngày (hai ngày) như một đơn vị duy nhất với nhóm thành phần. DateFormatter định dạng một ngày đơn lẻ. Cho phạm vi, luôn sử dụng DateIntervalFormatter vì nó cung cấp bản địa hóa dấu phân cách chính xác.
Tự động — DateIntervalFormatter phát hiện năm khác nhau và hiển thị ngày đầy đủ cho cả hai đầu: “25 tháng 12 năm 2024 – 5 tháng 1 năm 2025”. Không cần cấu hình thêm, chỉ cần đặt dateStyle.
Tóm tắt
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm