Ngày và giờ trong phát triển di động — một trong những chủ đề chính trong kỹ thuật di động. iOS sử dụng DateFormatter và ISO8601DateFormatter, Android sử dụng DateTimeFormatter và LocalDate từ java.time. Theo Android Developer Docs, việc chọn API di động phù hợp để định dạng ngày và giờ ảnh hưởng trực tiếp đến hiệu suất và độ chính xác hiển thị ở các múi giờ khác nhau.
Những điểm chính
Trên iOS, ngày và giờ theo truyền thống được định dạng qua DateFormatter từ Foundation. Lớp này chuyển đổi Date thành chuỗi và ngược lại bằng cách sử dụng mẫu, ngôn ngữ và múi giờ được chỉ định. Vấn đề chính của DateFormatter — nó không an toàn luồng và được tạo chậm, do đó trong mã di động, các phiên bản phải được lưu vào bộ nhớ đệm. Ngày trong phát triển di động trên iOS yêu cầu bộ định dạng phù hợp.
Việc tạo DateFormatter mất khoảng 1 mili giây do phân tích mẫu dateFormat, tải ngôn ngữ và xác định múi giờ. Khi định dạng danh sách hàng trăm ngày, việc tạo lặp đi lặp lại dẫn đến độ trễ đáng kể trong giao diện người dùng. Giải pháp — tạo bộ định dạng một lần cho toàn bộ màn hình và tái sử dụng. Đối với truy cập đa luồng, hãy sử dụng đồng bộ hóa qua DispatchQueue.
import Foundation
// DateFormatter được lưu vào bộ nhớ đệm — tạo một lần
private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
return formatter
}()
// ISO8601DateFormatter — thay thế nhẹ
private let isoFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter
}()
let now = Date()
let formattedDate = dateFormatter.string(from: now)
// "01.07.2026 14:30"
let isoDate = isoFormatter.string(from: now)
// "2026-07-01T14:30:00.000Z"
Đối với định dạng ISO 8601, hãy sử dụng ISO8601DateFormatter — nó không yêu cầu chỉ định mẫu và hoạt động nhanh hơn DateFormatter. Trên iOS 10+, lớp này được ưu tiên để làm việc với ngày từ máy chủ. Đối với các định dạng cũ, hãy giữ DateFormatter nhưng nhớ lưu phiên bản vào bộ nhớ đệm như thuộc tính tĩnh hoặc singleton.
Trên Android, ngày và giờ được xử lý qua gói java.time, có sẵn từ API 26. Các lớp chính: LocalDate (chỉ ngày), LocalTime (chỉ giờ), LocalDateTime (ngày và giờ không có múi giờ) và ZonedDateTime (có múi giờ). DateTimeFormatter — bộ định dạng an toàn luồng cho nền tảng di động Android. Ngày trong ứng dụng di động được định dạng qua DateTimeFormatter và LocalDate mà không mất hiệu suất.
LocalDate lưu trữ ngày không có giờ — lý tưởng cho sinh nhật. ZonedDateTime lưu trữ thông tin đầy đủ với múi giờ. DateTimeFormatter hỗ trợ các mẫu được xác định trước (ISO_LOCAL_DATE, ISO_DATE_TIME) và tùy chỉnh qua ofPattern. Tất cả các lớp java.time đều bất biến và an toàn luồng, loại bỏ các điều kiện cạnh tranh khi làm việc trong nhiều luồng.
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId
// Tạo và định dạng ngày hiện tại
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)
// Làm việc với múi giờ
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
DateTimeFormatter hiệu quả hơn đáng kể so với SimpleDateFormat vì nó không phân tích mẫu mỗi lần gọi. Nên tạo các hằng số bộ định dạng ở cấp lớp hoặc đối tượng đi kèm. Để chuyển đổi Unix Timestamp, hãy sử dụng Instant.ofEpochSecond, sau đó chuyển đổi thành LocalDateTime hoặc ZonedDateTime qua múi giờ.
Ngày và giờ chính xác trên màn hình người dùng phụ thuộc vào việc xử lý múi giờ phù hợp. Ngày và giờ trong phát triển di động cho Android yêu cầu xem xét múi giờ của thiết bị. Nguyên tắc vàng: lưu trữ thời gian ở UTC, chỉ chuyển đổi sang múi giờ địa phương để hiển thị. iOS cung cấp TimeZone từ Foundation, Android cung cấp ZoneId từ java.time. Cả hai API cho nền tảng di động đều hỗ trợ phát hiện tự động múi giờ hiện tại của thiết bị. Ngày trong ứng dụng di động phụ thuộc vào ZoneId và TimeZone.
Giờ mùa hè tạo ra sự không rõ ràng: một thời điểm có thể được biểu diễn bằng hai giờ địa phương khác nhau. java.time và Foundation xử lý DST tự động. Sử dụng mã định danh IANA (Europe/Moscow, America/New_York), không phải chữ viết tắt (MSK, EST) — chữ viết tắt không rõ ràng và bỏ qua chuyển đổi giờ mùa hè.
import Foundation
// Chuyển đổi UTC sang giờ địa phương trên iOS
func formatToLocalTime(utcDate: Date, timeZoneId: String) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "dd.MM.yyyy HH:mm"
formatter.timeZone = TimeZone(identifier: timeZoneId)
return formatter.string(from: utcDate)
}
// Xác định múi giờ thiết bị
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()
Trên Android, sử dụng ZoneId.systemDefault() để lấy múi giờ của thiết bị. Khi chuyển đổi UTC sang giờ địa phương, hãy tạo ZonedDateTime với ZoneOffset.UTC và áp dụng withZoneSameInstant. Đối với API cũ không có java.time, hãy sử dụng TimeZone.getDefault() và Calendar — nhưng tốt hơn nên thêm trực tiếp ThreeTenABP.
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId
// Chuyển đổi Unix Timestamp sang giờ địa phương
fun toLocalDisplay(unixSeconds: Long): String {
val instant = Instant.ofEpochSecond(unixSeconds)
val local = instant.atZone(ZoneId.systemDefault())
return local.format(DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"))
}
ThreeTenABP — một thư viện bộ chuyển đổi, chuyển java.time sang Android API dưới 26. Nó cung cấp các lớp tương tự: LocalDate, LocalTime, ZonedDateTime, DateTimeFormatter, Instant. Nếu ứng dụng hỗ trợ Android 5 hoặc 6 (API 21-25), ThreeTenABP là cách duy nhất để sử dụng API di động hiện đại mà không chuyển sang Calendar lỗi thời. Ngày và giờ trong phát triển di động cho thiết bị cũ được triển khai qua thư viện này. Đối với thiết bị cũ, ngày và giờ được triển khai qua ThreeTenABP.
Thư viện yêu cầu khởi tạo trong phương thức Application.onCreate() bằng cách gọi AndroidThreeTen.init(). Điều này tải dữ liệu múi giờ từ assets. Sau khi khởi tạo, API hoàn toàn giống với java.time — mã cho API 26+ và API 21+ sẽ giống nhau. Kích thước thư viện khoảng 400 KB, chấp nhận được cho hầu hết các ứng dụng di động.
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
// Khởi tạo trong Application
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// API giống với java.time
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)
Hiệu suất của ThreeTenABP tương đương với java.time gốc trên các thiết bị hiện đại. Trên thiết bị cũ, có thể có độ trễ trong lần gọi đầu tiên do tải dữ liệu múi giờ. Di chuyển mã hiện có từ Calendar sang ThreeTenABP dần dần: thay thế Date và Calendar bằng Instant và LocalDate trong các mô-đun mới, giữ nguyên mã cũ.
Unix Timestamp — số giây đã trôi qua kể từ ngày 1 tháng 1 năm 1970 00:00 UTC. Đây là định dạng phổ quát để lưu trữ và trao đổi thời gian giữa máy chủ và máy khách. Tuy nhiên, thời gian trên thiết bị có thể khác với thời gian thực do cài đặt của người dùng. Để đồng bộ hóa, NTP được sử dụng — giao thức thời gian chính xác qua UDP. Unix Timestamp là định dạng trong đó ngày và giờ được lưu trữ trên máy chủ.
Date().timeIntervalSince1970 trên iOS trả về số giây kể từ năm 1970. Trên Android, System.currentTimeMillis() trả về mili giây. Để có độ chính xác, hãy sử dụng NTP: thư viện TrueTime cho iOS và AndroidNtp cho Android gửi yêu cầu đến máy chủ NTP và tính toán độ lệch thời gian. Độ chính xác của NTP là 1-10 ms trên mạng cục bộ và 10-100 ms qua internet.
import Foundation
// Lấy Unix Timestamp trên iOS
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)
// Đồng bộ hóa NTP với TrueTime
TrueTime.shared.start { result in
switch result {
case .success:
let ntpTime = TrueTime.shared.now()
print("Thời gian chính xác: \(ntpTime)")
case .failure(let error):
print("Lỗi NTP: \(error)")
}
}
Nếu không đồng bộ hóa trong ứng dụng di động, có thể xảy ra lỗi: dấu thời gian sự kiện không chính xác, mất đồng bộ với máy chủ. Thời gian trong phát triển di động được đồng bộ hóa qua NTP một lần khi khởi động ứng dụng, sau đó sử dụng độ lệch đã tính toán. Đối với các ứng dụng di động yêu cầu độ chính xác (tài chính, hậu cần), giao thức NTP là bắt buộc trước khi gửi dữ liệu lên máy chủ.
Câu hỏi thường gặp
DateFormatter phân tích mẫu định dạng, tải ngôn ngữ và xác định múi giờ mỗi khi tạo phiên bản. Lưu bộ định dạng vào bộ nhớ đệm như hằng số tĩnh hoặc sử dụng ISO8601DateFormatter cho ISO 8601 — nó nhẹ hơn và nhanh hơn.
DateTimeFormatter từ gói java.time — an toàn luồng và hiệu quả. Có sẵn từ API 26. Đối với các phiên bản cũ, hãy sử dụng ThreeTenABP cung cấp API tương tự.
Thêm ThreeTenABP — backport của java.time cho Android API 21+. Khởi tạo qua AndroidThreeTen.init() trong Application.onCreate(). API hoàn toàn giống với java.time gốc.
Có, hãy lưu trữ thời gian ở UTC trên máy chủ và trong cơ sở dữ liệu cục bộ. Chỉ chuyển đổi sang múi giờ địa phương của người dùng ở giai đoạn hiển thị. Điều này loại bỏ lỗi khi thay đổi múi giờ hoặc đi du lịch.
Date().timeIntervalSince1970 trả về số giây kể từ ngày 1 tháng 1 năm 1970. Để có mili giây, nhân với 1000 và chuyển đổi thành Int64. Để đồng bộ hóa chính xác, hãy sử dụng thư viện TrueTime với máy chủ NTP.
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.