DateFormatter: Các khái niệm chính, định dạng ngày và bản địa hóa

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

DateFormatter là một lớp Foundation được thiết kế để chuyển đổi hai chiều giữa các đối tượng Date và biểu diễn chuỗi của chúng. Lớp này tính đến ngôn ngữ vùng, múi giờ và lịch của người dùng, đảm bảo hiển thị ngày chính xác ở bất kỳ khu vực nào trên thế giới. Theo Tài liệu dành cho nhà phát triển Apple (2025), DateFormatter hỗ trợ bốn kiểu ngày và giờ được đặt sẵn, cũng như các định dạng hoàn toàn tùy chỉnh thông qua chuỗi mẫu. Nếu không có DateFormatter, không thể hiển thị ngày chính xác cho người dùng trong một ứng dụng đã được quốc tế hóa.

Những điểm chính

  • DateFormatter — một lớp để chuyển đổi Date thành chuỗi và ngược lại với hỗ trợ ngôn ngữ vùng và múi giờ.
  • dateStyle và timeStyle — các kiểu được đặt sẵn (.short, .medium, .long, .full) để định dạng nhanh.
  • dateFormat — một chuỗi mẫu cho định dạng tùy chỉnh được chỉ định qua các ký hiệu Unicode LDML.
  • LocaleTimeZone — các thuộc tính của bộ định dạng xác định hiển thị khu vực và múi giờ.
  • ISO8601DateFormatter — một giải pháp thay thế nhanh hơn cho định dạng ISO 8601 khi tuần tự hóa trong API.

DateFormatter là gì?

DateFormatter là một lớp từ framework Foundation thực hiện chuyển đổi hai chiều giữa Date và chuỗi. Nó xuất hiện lần đầu tiên trong OpenStep với tên NSDateFormatter và từ đó vẫn là công cụ chính để định dạng ngày trên tất cả các nền tảng Apple. Lớp này kế thừa từ Formatter và cung cấp API thuận tiện cho việc hiển thị ngày bản địa hóa.

DateFormatter hoạt động dựa trên các mẫu Unicode LDML — giống như được sử dụng trong ICU (International Components for Unicode). Mẫu được đặt qua thuộc tính dateFormat, trong đó các ký hiệu y, M, d, H, m, s tương ứng với năm, tháng, ngày, giờ, phút, giây. Việc lặp lại một ký hiệu xác định định dạng: “y” — năm hai chữ số, “yyyy” — năm bốn chữ số.

Tạo DateFormatter là một thao tác tốn kém, vì dữ liệu ngôn ngữ vùng và lịch được tải trong quá trình khởi tạo. Apple khuyến nghị tạo một bộ định dạng một lần cho mỗi loại định dạng và tái sử dụng nó. Trong SwiftUI và UIKit, các bộ định dạng thường được lưu trong bộ nhớ đệm trong các thuộc tính tĩnh hoặc được tạo một cách lười biếng khi truy cập lần đầu.

DateFormatter trong iOS SDK

DateFormatter được sử dụng trong nhiều thành phần hệ thống iOS. UIDatePicker sử dụng DateFormatter nội bộ để hiển thị ngày trong chế độ countDownTimer. TextField với bộ định dạng có thể tự động xác thực ngày người dùng nhập. Core Data hỗ trợ các thuộc tính kiểu Date, nhưng biểu diễn chuỗi của chúng luôn được xử lý qua DateFormatter.

An toàn luồng — DateFormatter không an toàn cho luồng. Việc sửa đổi các thuộc tính của bộ định dạng từ các luồng khác nhau dẫn đến hành vi không xác định. Để sử dụng đa luồng, hãy tạo các phiên bản bộ định dạng riêng biệt cho mỗi luồng hoặc sử dụng đồng bộ hóa qua NSLock hoặc hàng đợi tuần tự.

Các kiểu định dạng DateFormatter

dateStyletimeStyle là những cách đơn giản nhất để cấu hình hiển thị ngày. Mỗi kiểu có bốn biến thể: .short, .medium, .long, .full. Sự kết hợp giữa dateStyle và timeStyle cho phép cấu hình độc lập định dạng ngày và giờ, và thuộc tính .none vô hiệu hóa phần tương ứng.

Đối với ngôn ngữ vùng Hoa Kỳ, .short định dạng ngày là “7/21/26”, và đối với ngôn ngữ vùng Nga là “21.07.2026”. Kiểu .long đối với ngôn ngữ vùng Nga xuất ra “21 tháng 7, 2026”, và .full xuất ra “Thứ Ba, 21 tháng 7, 2026” kèm ngày trong tuần. Cả bốn kiểu tự động thích ứng với các tiêu chuẩn khu vực, bao gồm thứ tự thành phần và dấu phân cách.

SFDateFormatter trong iOS 15+ cung cấp một cách tiếp cận thay thế qua RelativeDateFormatter và DateIntervalFormatter. RelativeDateFormatter xuất ra “hôm nay”, “hôm qua”, “trong 3 ngày” cho ngữ cảnh tức thì. DateIntervalFormatter hiển thị phạm vi ngày: “21–25 tháng 7, 2026” — cho đặt chỗ và lập kế hoạch.

KiểuVí dụ (ru_RU)Ví dụ (en_US)
.short21.07.20267/21/26
.medium21 thg 7, 2026Jul 21, 2026
.long21 tháng 7, 2026July 21, 2026
.fullThứ Ba, 21 tháng 7, 2026Tuesday, July 21, 2026

Khi kết hợp các kiểu, DateFormatter tự động chọn dấu phân cách: cho .short.date + .short.time, kết quả có thể là “21.07.2026, 14:30”. Cho .full.date + .full.time — “Thứ Ba, 21 tháng 7, 2026, 14:30:00 MSK”. Dấu phân cách được quản lý bởi ngôn ngữ vùng, không phải nhà phát triển — điều này đảm bảo tuân thủ kỳ vọng khu vực của người dùng.

Định dạng tùy chỉnh qua dateFormat

dateFormat cho phép đặt một mẫu định dạng tùy ý bằng cách sử dụng các ký hiệu đặc tả Unicode LDML. Điều này mang lại toàn quyền kiểm soát hiển thị: bạn có thể chỉ hiển thị năm và tháng, hoặc ngày trong tuần mà không có ngày, hoặc giờ không có giây. Định dạng tùy chỉnh là không thể thiếu cho các yêu cầu thiết kế cụ thể.

Các ký hiệu chính — yyyy (năm: 2026), MM (tháng: 07), dd (ngày: 21), HH (giờ: 14), mm (phút: 30), ss (giây: 00). Đối với tên tháng đầy đủ, sử dụng MMMM (tháng 7), viết tắt — MMM (thg 7). Ngày trong tuần — EEEE (Thứ Ba), viết tắt — E (Thứ Ba).

Khi sử dụng dateFormat, điều quan trọng là đặt ngôn ngữ vùng của bộ định dạng. Nếu không đặt locale, bộ định dạng sử dụng ngôn ngữ vùng hệ thống, điều này có thể không mong muốn cho một định dạng cố định trong API. Apple khuyến nghị đặt locale = Locale(identifier: “en_US_POSIX”) cho định dạng cố định giữa các khu vực, đặc biệt khi phân tích ngày từ phản hồi máy chủ.

swift
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"

// Phân tích chuỗi tùy chỉnh
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!

Lỗi trong dateFormat là một trong những nguyên nhân phổ biến gây sập ứng dụng. Nếu định dạng không khớp với chuỗi, phương thức date(from:) trả về nil. Sử dụng guard let hoặc ?? để mở gói tùy chọn an toàn. Để xác thực định dạng, hãy kiểm tra nó trên tất cả các ngôn ngữ được hỗ trợ — một số ký hiệu LDML hoạt động khác nhau trong các ngôn ngữ vùng khác nhau.

Bản địa hóa và TimeZone

Locale xác định cách hiển thị tên tháng, tên ngày trong tuần và dấu phân cách. DateFormatter sử dụng Locale.current theo mặc định, nhưng trong một số tình huống, cần chỉ định ngôn ngữ vùng cụ thể: cho định dạng cố định trong nhật ký, sử dụng en_US_POSIX; cho ngày máy chủ — ngôn ngữ vùng khớp với máy chủ.

Thuộc tính TimeZone xác định múi giờ cho hiển thị. Theo mặc định, múi giờ hệ thống được sử dụng, nhưng đối với các ứng dụng có khán giả quốc tế, thường cần hiển thị ngày trong múi giờ của người dùng hoặc trong UTC. Thay đổi timeZone chỉ ảnh hưởng đến hiển thị — giá trị Date không thay đổi.

Một tính năng quan trọng: nếu DateFormatter được sử dụng để phân tích chuỗi và chuỗi có chứa chỉ định múi giờ (ví dụ: “2026-07-21T14:30:00Z” với Z cho UTC), thuộc tính timeZone bị bỏ qua — bộ định dạng sử dụng múi giờ từ chuỗi. Nếu chuỗi không có múi giờ, timeZone của bộ định dạng được áp dụng.

swift
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short

let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"

// Phân tích không có múi giờ trong chuỗi
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!

AutoupdatingCurrentLocale — một loại ngôn ngữ vùng đặc biệt tự động cập nhật khi cài đặt hệ thống của người dùng thay đổi. DateFormatter hỗ trợ nó theo mặc định. Nếu ứng dụng đang chạy nền và người dùng thay đổi ngôn ngữ hệ thống, bộ định dạng được tạo trước khi thay đổi sẽ tiếp tục sử dụng ngôn ngữ vùng cũ — để cập nhật, cần tạo một phiên bản mới.

ISO8601DateFormatter và các giải pháp thay thế

ISO8601DateFormatter là một bộ định dạng chuyên biệt để làm việc với ngày ở định dạng ISO 8601. Định dạng này là tiêu chuẩn thực tế cho REST API, JSON và trao đổi dữ liệu. ISO8601DateFormatter hoạt động nhanh hơn đáng kể so với DateFormatter vì nó không phụ thuộc vào ngôn ngữ vùng và sử dụng ngữ pháp phân tích cố định.

Các tùy chọn chính của bộ định dạng — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (thêm mili giây), .withTimeZone (bao gồm chênh lệch múi giờ). Bằng cách kết hợp các tùy chọn, bạn có thể nhận được bất kỳ biến thể ISO 8601 nào: với mili giây, với múi giờ, chỉ với ngày.

JSONEncoder.DateEncodingStrategy cho phép cấu hình toàn cầu mã hóa ngày cho tất cả các mô hình Codable. Các tùy chọn — .iso8601 (sử dụng ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. Việc chọn chiến lược ảnh hưởng đến toàn bộ vòng đời tuần tự hóa và phải nhất quán trên tất cả các điểm cuối API.

swift
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"

// JSONEncoder với ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601

// Thay thế: JSONEncoder với bộ định dạng tùy chỉnh
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)

DateFormatter so với ISO8601DateFormatter — chọn ISO8601DateFormatter để tuần tự hóa và phân tích ngày trong API, vì nó nhanh gấp 5-10 lần DateFormatter và không dễ bị lỗi bản địa hóa. Sử dụng DateFormatter cho giao diện người dùng nơi cần hiển thị bản địa hóa với tên tháng và ngày bằng ngôn ngữ mẹ đẻ của người dùng.

Ví dụ về định dạng ngày

Hãy xem các tình huống thực tế sử dụng DateFormatter trong ứng dụng iOS: hiển thị ngày trong nguồn cấp tin tức, nhập ngày sinh và xuất báo cáo với ngày trong các múi giờ khác nhau.

Hiển thị ngày tin tức trong danh sách

RelativeDateFormatter là tối ưu cho nguồn cấp tin tức. Nó hiển thị “vừa xong”, “5 phút trước”, “hôm qua” cho tin tức mới và chuyển sang ngày đầy đủ cho tin cũ. Ngưỡng chuyển đổi được cấu hình qua lịch: cho tin tức, sử dụng ngưỡng 24 giờ; cho tin nhắn — một tuần.

swift
func formatRelativeDate(_ date: Date) -> String {
    let relative = RelativeDateFormatter()
    relative.unitsStyle = .full

    let formatter = DateFormatter()
    formatter.dateStyle = .medium
    formatter.timeStyle = .short

    let daysDiff = Calendar.current.dateComponents(
        [.day], from: date, to: Date()
    ).day ?? 0

    return daysDiff < 1
        ? relative.localizedString(for: date, relativeTo: Date())
        : formatter.string(from: date)
}

Nhập ngày sinh — một tình huống phổ biến khác. DateFormatter được cấu hình với dateFormat cụ thể “dd.MM.yyyy” và locale “ru_RU”. Khi phân tích chuỗi đã nhập, điều quan trọng là xử lý các lỗi có thể xảy ra: bộ định dạng trả về nil cho chuỗi không hợp lệ. Sau khi phân tích thành công, ngày được kiểm tra để nằm trong phạm vi chấp nhận được — không sớm hơn 1900, không muộn hơn hôm nay.

Xuất báo cáo với ngày yêu cầu định dạng cố định không phụ thuộc vào ngôn ngữ vùng của người dùng. Sử dụng dateFormat “yyyy-MM-dd HH:mm:ss” với locale en_US_POSIX và múi giờ UTC. Cách tiếp cận này đảm bảo tệp sẽ mở chính xác ở bất kỳ quốc gia nào bất kể cài đặt khu vực của hệ thống.

Tình huốngBộ định dạngCài đặt chính
Nguồn cấp tin tứcRelativeDateFormatterunitsStyle = .full
Nhập ngàyDateFormatterdateFormat + dự phòng
Tuần tự hóa APIISO8601DateFormatterwithInternetDateTime
Xuất báo cáoDateFormatteren_US_POSIX + UTC

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

Tại sao DateFormatter trả về nil cho một chuỗi hợp lệ?

Lý do phổ biến nhất — sự không khớp giữa dateFormat và định dạng chuỗi. Ví dụ, định dạng “dd.MM.yyyy” sẽ không phân tích chuỗi “2026-07-21”. Lý do thứ hai — không khớp ngôn ngữ vùng: chuỗi “July 21, 2026” sẽ không được phân tích với ngôn ngữ vùng ru_RU. Thứ ba — lỗi chính tả trong ký hiệu LDML: sử dụng yyyy, không phải YYYY (ý nghĩa khác nhau).

Tôi có nên tạo DateFormatter mới cho mỗi lần gọi không?

Không. DateFormatter là một đối tượng nặng, việc khởi tạo nó bao gồm tải dữ liệu ngôn ngữ vùng. Tạo một phiên bản cho mỗi loại định dạng và tái sử dụng nó. Trong môi trường đa luồng, sử dụng bộ nhớ cục bộ luồng hoặc nhóm bộ định dạng với hàng đợi tuần tự để đồng bộ hóa.

DateFormatter khác RelativeDateFormatter như thế nào?

DateFormatter hiển thị ngày tuyệt đối (21 tháng 7, 2026), trong khi RelativeDateFormatter hiển thị ngày tương đối (hôm nay, hôm qua, trong 3 ngày). RelativeDateFormatter được giới thiệu trong iOS 15+ và sử dụng cùng mẫu LDML nhưng tự động chọn hiển thị tương đối.

Làm thế nào để xử lý ngày không có múi giờ từ API?

Đặt timeZone của bộ định dạng thành UTC trước khi phân tích. Nếu máy chủ trả về ngày theo giờ địa phương mà không có chỉ định múi giờ, hãy kiểm tra đặc tả API — rất có thể UTC được ngụ ý. Đối với ISO 8601 có Z ở cuối, không cần timeZone — bộ định dạng phân tích chênh lệch từ chuỗi.

Làm thế nào để làm cho DateFormatter an toàn cho luồng?

Không sử dụng một phiên bản duy nhất từ các luồng khác nhau mà không đồng bộ hóa. Tạo một phiên bản mới trong mỗi luồng hoặc sử dụng Thread.current.threadDictionary để lưu trữ. Một giải pháp thay thế là NSLock với khóa trong suốt thời gian string(from:) và date(from:).

Tổng kết

  • DateFormatter — một lớp Foundation để chuyển đổi Date thành chuỗi và ngược lại với hỗ trợ ngôn ngữ vùng, múi giờ và lịch.
  • Các kiểu được đặt sẵn dateStyle và timeStyle với các biến thể .short, .medium, .long, .full bao phủ hầu hết các tình huống giao diện người dùng.
  • dateFormat tùy chỉnh qua các ký hiệu LDML mang lại toàn quyền kiểm soát định dạng nhưng cần thận trọng với bản địa hóa.
  • Locale và TimeZone — các thuộc tính cần thiết để hiển thị chính xác: cho giao diện người dùng — ngôn ngữ vùng hệ thống, cho API — en_US_POSIX và UTC.
  • ISO8601DateFormatter — lựa chọn ưa thích để tuần tự hóa ngày trong API nhờ tốc độ và độ ổn định.
  • DateFormatter không an toàn cho luồng — sử dụng các phiên bản riêng biệt cho mỗi luồng hoặc đồng bộ hóa truy cập.
  • RelativeDateFormatter (iOS 15+) — giải pháp tối ưu để hiển thị ngày tương đối trong nguồn cấp tin tức và tin nhắn.

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