DateComponents — thành phần lịch và NSCalendar là gì

Tác giả: IT Sectr Đã đăng: 2026-07-12 Thời gian đọc: 7 phút

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 — cấu trúc lưu trữ các thành phần ngày (năm, tháng, ngày) dưới dạng trường số nguyên tùy chọn.
  • Calendar.dateComponents — phương thức trích xuất các thành phần được chỉ định từ Date có tính đến múi giờ.
  • Calendar.date(from:) — chuyển đổi ngược DateComponents thành Date với tự động điền các trường bị thiếu.
  • Trường tùy chọn — mỗi trường DateComponents có thể là nil, cho phép chỉ định ngày không đầy đủ.
  • Range và thành phần — DateComponents được sử dụng trong Calendar để tính chênh lệch giữa các ngày và tìm ngày trong một phạm vi.

DateComponents là gì?

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.

Tính tùy chọn của trường

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.

Thành phần ngày: năm, tháng, ngày

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ụcTrườngPhạm vi
Lịchyear, month, day1..∞, 1..12, 1..31
Thời gianhour, minute, second, nanosecond0..23, 0..59, 0..59, 0..999999999
Tuầnweekday, weekOfMonth, weekOfYear1..7, 1..5, 1..53
Đặc biệtquarter, yearForWeekOfYear1..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ẻ.

Tạo DateComponents

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.

swift
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.

Chuyển đổi DateComponents thành Date

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.

swift
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 và DateComponents

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.nextDateCalendar.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.

swift
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è.

Ví dụ DateComponents

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.

Nhắc nhở vào ngày đầu tiên của mỗi 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.

swift
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 CalendarVai trò DateComponents
Ngày đầu thángnextDate(after:matching:)day: 1
Tính tuổidateComponents(from:to:)[.year] từ chênh lệch
Phân nhóm ngàydateComponents(_:from:)khóa year + month
Tìm ngày trong tuầnnextDate(after:matching:)weekday: N

Câu hỏi thường gặp

Tại sao Calendar.date(from:) trả về nil cho DateComponents?

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ó thể so sánh DateComponents với nhau không?

, 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.

DateComponents khác Date như thế nào?

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.

Làm thế nào để chỉ định năm và tháng trong DateComponents?

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 xử lý múi giờ như thế nào?

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

  • DateComponents — cấu trúc Foundation lưu trữ các thành phần lịch (năm, tháng, ngày, giờ) dưới dạng trường Int? tùy chọn.
  • Calendar.dateComponents trích xuất thành phần từ Date có tính đến múi giờ và hệ thống lịch.
  • Calendar.date(from:) lắp ráp Date từ DateComponents, sử dụng giá trị mặc định cho các trường bị thiếu.
  • Trường tùy chọn cho phép chỉ định ngày không đầy đủ — Calendar điền các giá trị bị thiếu.
  • Calendar.nextDate tìm ngày tiếp theo khớp với DateComponents — cho nhắc nhở và sự kiện định kỳ.
  • 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.
  • MatchingPolicy kiểm soát hành vi của Calendar khi không phải tất cả thành phần khớp — tham số quan trọng cho tìm kiếm ngày.

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.

Thảo luận dự án

Đọc thêm