DateComponents là một cấu trúc Foundation lưu trữ các thành phần ngày tháng dưới dạng các trường riêng biệt: năm, tháng, ngày, giờ, phút, giây và các trường khác. Không giống như Date, đại diện cho một thời điểm tuyệt đối, DateComponents chứa các giá trị có thể đọc được phụ thuộc vào lịch và múi giờ. Theo Tài liệu Nhà phát triển Apple (2025), DateComponents được sử dụng như một liên kết trung gian giữa Date và Calendar — thông qua nó, các ngày tháng được trích xuất và xây dựng, các phép tính và dịch chuyển ngày được thực hiện mà không cần tính toán thủ công.
Những điểm chính
DateComponents là một kiểu giá trị Foundation được thiết kế để lưu trữ các thành phần thời gian lịch. Mỗi thành phần được biểu diễn bằng một trường Int tùy chọn: year, month, day, hour, minute, second, nanosecond, weekday, weekOfMonth, weekOfYear, quarter, yearForWeekOfYear và các trường khác.
Sự khác biệt chính so với Date là ràng buộc lịch. Date lưu trữ thời gian tuyệt đối (số giây từ ngày tham chiếu), trong khi DateComponents là biểu diễn có thể đọc được chỉ có ý nghĩa trong ngữ cảnh của một Calendar cụ thể. Cùng một Date có thể được biểu diễn bằng các DateComponents khác nhau trong các lịch và múi giờ khác nhau.
DateComponents không phải là một kiểu thời gian độc lập, mà là một vùng chứa dữ liệu. Để diễn giải DateComponents như một ngày, cần có Calendar hiểu cách các thành phần liên quan đến hệ thống lịch. Calendar.dateComponents(from: Date) thực hiện trích xuất thành phần, Calendar.date(from: DateComponents) thực hiện lắp ráp ngược lại.
Mỗi trường DateComponents là tùy chọn (Int?), điều này cơ bản cho việc làm việc với ngày không đầy đủ. Nếu chỉ chỉ định năm và tháng, Calendar điền các trường bị thiếu bằng giá trị mặc định: ngày = 1, giờ = 0, phút = 0. Điều này thuận tiện cho việc tạo ngày bắt đầu khoảng thời gian — chỉ cần chỉ định các thành phần liên quan.
Khi so sánh DateComponents bằng toán tử ==, chỉ các trường được chỉ định (không nil) được so sánh. Hai cấu trúc DateComponents cùng năm 2026 nhưng tháng khác nhau được coi là khác biệt. isEqual từ NSObjectProtocol không áp dụng cho DateComponents — DateComponents không kế thừa NSObject.
Các trường chính của DateComponents bao gồm year, month, day, hour, minute, second, nanosecond. Mỗi trường lưu trữ một giá trị số trong đơn vị tương ứng: năm — 2026, tháng — 1..12, ngày — 1..31, giờ — 0..23, phút — 0..59, giây — 0..59. Nano giây có thể từ 0 đến 999999999.
Trường tuần — weekday (1..7, trong đó 1 = Chủ nhật trong lịch Gregory), weekOfMonth, weekOfYear. Các trường này phụ thuộc vào Calendar và không có ý nghĩa bên ngoài ngữ cảnh của nó. weekday phụ thuộc vào cài đặt firstWeekday của lịch: trong ngôn ngữ Nga, tuần bắt đầu từ Thứ hai (weekday = 2 trong hệ thống Gregory), trong khi ở ngôn ngữ Mỹ, tuần bắt đầu từ Chủ nhật (weekday = 1).
Các trường chuyên biệt — quarter (1..4), yearForWeekOfYear (năm mà tuần thuộc về), isLeapMonth (cờ boolean cho tháng nhuận trong lịch Hebrew hoặc Trung Quốc). Các trường calendar và timeZone lưu trữ tham chiếu đến các đối tượng tương ứng mà cấu trúc được tạo ra.
| Danh mục | Trường | Phạm vi |
|---|---|---|
| Lịch | year, month, day | 1..∞, 1..12, 1..31 |
| Thời gian | hour, minute, second, nanosecond | 0..23, 0..59, 0..59, 0..999999999 |
| Tuần | weekday, weekOfMonth, weekOfYear | 1..7, 1..5, 1..53 |
| Đặc biệt | quarter, yearForWeekOfYear | 1..4, phụ thuộc |
Khi trích xuất thành phần qua Calendar.dateComponents, điều quan trọng là chỉ yêu cầu các trường cần thiết để đảm bảo hiệu suất. Calendar trích xuất tất cả các trường được yêu cầu trong một lần — điều này nhanh hơn đáng kể so với gọi Calendar.component cho từng trường riêng lẻ.
Khởi tạo DateComponents — cách đơn giản nhất: tạo một cấu trúc rỗng và điền các trường cần thiết. Tất cả các trường không được chỉ định tự động nhận nil. Một ngày được tạo từ các thành phần một phần không được xác thực tại thời điểm khởi tạo — lỗi chỉ có thể xảy ra khi chuyển đổi thành Date qua Calendar.
Bộ khởi tạo DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:…) cho phép đặt tất cả các trường trong một lần gọi. Bộ khởi tạo này thuận tiện để tạo một ngày hoàn chỉnh từ các giá trị có sẵn, nhưng hiếm khi được sử dụng với hơn 5-6 đối số do tính dễ đọc.
Calendar.dateComponents(_:from:) — cách chính để lấy DateComponents từ một Date hiện có. Đối số thứ hai là tập hợp các thành phần cần trích xuất. Calendar thực hiện các tính toán lịch có tính đến múi giờ và trả về một cấu trúc chỉ với các trường được yêu cầu; các trường còn lại vẫn là nil.
import Foundation
// Tạo qua bộ khởi tạo trường
var components = DateComponents()
components.year = 2026
components.month = 7
components.day = 21
// Trích xuất từ Date
let now = Date()
let extracted = Calendar.current.dateComponents(
[.year, .month, .day],
from: now
)
print("Today: \(extracted.day!).\(extracted.month!).\(extracted.year!)")
// Tạo qua bộ khởi tạo mở rộng
let birthday = DateComponents(
calendar: Calendar.current,
year: 1990, month: 5, day: 15
)
Khi tạo DateComponents bằng cách đặt trường thủ công, hãy luôn kiểm tra Calendar trước khi chuyển đổi thành Date. Khi chuyển đổi date(from:), Calendar có thể trả về nil nếu các thành phần tạo thành một ngày không tồn tại — ví dụ: 31 tháng 2 hoặc 30 tháng 2 trong năm không nhuận. Xác thực ngày là trách nhiệm của Calendar, không phải của DateComponents.
Calendar.date(from:) — phương thức chính để chuyển đổi DateComponents thành Date. Calendar diễn giải các thành phần theo lịch và múi giờ riêng của nó. Nếu một số trường không được đặt (nil), Calendar sử dụng giá trị mặc định: ngày = 1, giờ = 0, phút = 0, giây = 0.
Phương thức trả về một Date tùy chọn — nil xảy ra nếu các thành phần mâu thuẫn với nhau hoặc tạo thành một ngày không hợp lệ. Nguyên nhân điển hình của nil: ngày không tồn tại (32 tháng 1, 29 tháng 2 năm 2023), trường mâu thuẫn (weekday=1, day=5 trong cùng một tập hợp), năm không thể có cho lịch đã cho (năm 0 trong lịch Gregory).
DateComponents với timeZone — nếu DateComponents chứa timeZone, Calendar sử dụng nó trong quá trình chuyển đổi. Nếu timeZone không được chỉ định, Calendar sử dụng timeZone hiện tại của riêng nó. Nếu Calendar.timeZone không khớp với múi giờ dự kiến của ngày, kết quả có thể chênh lệch vài giờ — hãy đảm bảo timeZone được đặt rõ ràng trong một trong các đối tượng.
let calendar = Calendar(identifier: .gregorian)
// Tạo Date từ DateComponents
var comps = DateComponents()
comps.year = 2026
comps.month = 12
comps.day = 25
comps.hour = 10
if let date = calendar.date(from: comps) {
print("Christmas: \(date)")
}
// Tạo với chỉ định timeZone
calendar.timeZone = TimeZone(identifier: "UTC")!
let utcComps = DateComponents(
calendar: calendar, year: 2026, month: 7, day: 21,
hour: 12
)
let utcDate = calendar.date(from: utcComps)!
Calendar.dateComponents cho chênh lệch ngày — một trường hợp sử dụng khác của DateComponents. Calendar.dateComponents([.year, .month, .day], from: Date(), to: futureDate) trả về chênh lệch năm, tháng và ngày giữa hai ngày. Đây là cách chính xác để tính tuổi thay vì chia TimeInterval cho số giây trong một năm, vì Calendar tính đến năm nhuận.
Calendar — lớp trung tâm làm việc với DateComponents. Tất cả các thao tác trích xuất, lắp ráp và so sánh ngày đều thông qua Calendar. Nếu không có Calendar, DateComponents chỉ là một tập hợp các số không có ý nghĩa thời gian. Calendar cung cấp diễn giải cho các thành phần: nó xác định rằng tháng 2 là tháng Hai và weekday 2 là Thứ hai.
Calendar.nextDate và Calendar.enumerateDates — hai phương thức dựa trên DateComponents. nextDate(after: Date(), matching: DateComponents) tìm ngày tiếp theo khớp với các thành phần được chỉ định — ví dụ: Thứ hai tiếp theo sau hôm nay. enumerateDates(startingAfter:matching:matchingPolicy:using:) lặp qua tất cả các ngày khớp với mẫu cho đến giới hạn được chỉ định.
Calendar.dateInterval — một phương thức trả về DateInterval cho thành phần được chỉ định. dateInterval(of: .month, for: Date()) trả về đầu và cuối của tháng hiện tại. Bên trong, phương thức này sử dụng DateComponents để tìm ranh giới khoảng thời gian: nó tạo DateComponents với ngày đầu tiên và ngày cuối cùng của tháng và chuyển đổi chúng thành Date qua Calendar.
let calendar = Calendar.current
// Thứ Hai tới
let nextMonday = calendar.nextDate(
after: Date(),
matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime
)!
// Chênh lệch giữa các ngày tính bằng ngày
let diff = calendar.dateComponents(
[.day], from: Date(), to: nextMonday
)
// Phạm vi tháng
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
let startOfMonth = monthInterval.start
let endOfMonth = monthInterval.end
MatchingPolicy — một tham số quan trọng của các phương thức Calendar khi làm việc với DateComponents. strictPolicy yêu cầu khớp chính xác tất cả các thành phần, nextTimePolicy chọn kết quả khớp theo thời gian tiếp theo, nextTimePreservingSmallerComponents giữ lại các thành phần nhỏ hơn (phút, giây) từ ngày nguồn. Việc chọn chính sách ảnh hưởng đến kết quả tìm kiếm ngày, đặc biệt khi dịch chuyển qua các thay đổi giờ mùa hè.
Hãy khám phá các trường hợp sử dụng thực tế của DateComponents trong ứng dụng. Mỗi ví dụ minh họa một tác vụ điển hình mà nhà phát triển iOS gặp phải khi làm việc với ngày tháng.
Calendar.nextDate với DateComponents(day: 1) tìm ngày đầu tiên của tháng tiếp theo. Calendar tự động xác định số ngày trong tháng hiện tại và chuyển sang tháng tiếp theo. Đối với thông báo định kỳ, hãy sử dụng enumerateDates hoặc Combine.Timer với khóa Calendar.
func firstDayOfNextMonth(from date: Date) -> Date {
let calendar = Calendar.current
let comps = DateComponents(day: 1)
return calendar.nextDate(
after: date,
matching: comps,
matchingPolicy: .nextTime
)!
}
// Tính tuổi theo năm
func ageInYears(from birthDate: Date) -> Int {
let calendar = Calendar.current
let ageComponents = calendar.dateComponents(
[.year], from: birthDate, to: Date()
)
return ageComponents.year ?? 0
}
// Phân nhóm sự kiện theo năm và tháng
func groupEventsByMonth(_ events: [Event]) -> [String: [Event]] {
let calendar = Calendar.current
return Dictionary(grouping: events) { event in
let comps = calendar.dateComponents(
[.year, .month], from: event.date
)
return "\(comps.year!)-\(comps.month!)"
}
}
Tính tuổi qua Calendar.dateComponents([.year], from:to:) — cách chính xác duy nhất có tính đến năm nhuận. Tính toán dựa trên TimeInterval (giây / 31536000) cho kết quả sai cho những người sinh ngày 29 tháng 2. Calendar xác định chính xác liệu sinh nhật có xảy ra trong năm hiện tại hay không và trả về tuổi chính xác.
Phân nhóm theo năm và tháng — một tác vụ phổ biến cho màn hình lịch sử hoặc lịch. DateComponents đóng vai trò là khóa phân nhóm: trích xuất năm và tháng từ ngày sự kiện, tạo khóa chuỗi và phân nhóm qua Dictionary(grouping:). Để hiển thị, hãy sử dụng DateFormatter với mẫu "LLLL yyyy" để có tên tháng được bản địa hóa.
| Tác vụ | Phương thức Calendar | Vai trò DateComponents |
|---|---|---|
| Ngày đầu tháng | nextDate(after:matching:) | day: 1 |
| Tính tuổi | dateComponents(from:to:) | [.year] từ chênh lệch |
| Phân nhóm ngày | dateComponents(_:from:) | khóa year + month |
| Tìm ngày trong tuần | nextDate(after:matching:) | weekday: N |
Câu hỏi thường gặp
Nguyên nhân: ngày không tồn tại (31 tháng 4), trường mâu thuẫn (weekday=1 với day=5), kết hợp trường không hợp lệ cho lịch đã chọn. Calendar cố gắng diễn giải các thành phần trong hệ thống của nó — nếu kết hợp là không thể, kết quả là nil. Luôn sử dụng guard let hoặc if let khi chuyển đổi.
Có, qua toán tử ==. DateComponents triển khai Equatable, so sánh tất cả các trường. Hai cấu trúc bằng nhau nếu tất cả các trường của chúng bằng nhau (nil == nil được coi là đúng). Để chỉ so sánh một tập hợp con các trường — hãy trích xuất cùng một tập hợp qua Calendar.dateComponents.
Date là một thời điểm tuyệt đối không có ràng buộc lịch. DateComponents là một tập hợp các số có thể đọc được (năm, tháng, ngày) chỉ có ý nghĩa trong ngữ cảnh của Calendar. Date có thể được so sánh, trừ, tuần tự hóa thành ISO 8601. DateComponents là biểu diễn trung gian để tương tác với lịch.
Chỉ đặt các trường year và month, để các trường còn lại là nil. Khi chuyển đổi thành Date qua Calendar.date(from:), Calendar sẽ tự động đặt ngày = 1, giờ = 0, phút = 0. Kết quả là Date tương ứng với ngày đầu tiên của tháng được chỉ định vào lúc nửa đêm.
DateComponents không lưu trữ thông tin múi giờ trong các trường của nó — giá trị trường (năm, tháng, ngày) tự phụ thuộc vào timeZone mà chúng được trích xuất. Các thành phần "21 tháng 7 năm 2026 14:00 MSK" và "21 tháng 7 năm 2026 10:00 UTC" đại diện cho cùng một Date, nhưng các trường DateComponents khác nhau.
Tổng kế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