Ημερομηνία και ώρα στην ανάπτυξη κινητών — ένα από τα βασικά θέματα στη μηχανική κινητών. Το iOS χρησιμοποιεί DateFormatter και ISO8601DateFormatter, το Android χρησιμοποιεί DateTimeFormatter και LocalDate από java.time. Σύμφωνα με το Android Developer Docs, η σωστή επιλογή του κινητού API για μορφοποίηση ημερομηνίας και ώρας επηρεάζει άμεσα την απόδοση και τη σωστή εμφάνιση σε διαφορετικές ζώνες ώρας.
Βασικά σημεία
Στο iOS, η ημερομηνία και ώρα μορφοποιούνται παραδοσιακά μέσω του DateFormatter από το Foundation. Αυτή η κλάση μετατρέπει το Date σε συμβολοσειρά και αντίστροφα χρησιμοποιώντας ένα καθορισμένο μοτίβο, locale και ζώνη ώρας. Το κύριο πρόβλημα του DateFormatter — δεν είναι ασφαλές για νήματα και δημιουργείται αργά, επομένως στον κώδικα κινητών τα στιγμιότυπα πρέπει να αποθηκεύονται στην κρυφή μνήμη. Η ημερομηνία στην ανάπτυξη κινητών στο iOS απαιτεί τον σωστό μορφοποιητή.
Η δημιουργία ενός DateFormatter διαρκεί περίπου 1 χιλιοστό του δευτερολέπτου λόγω της ανάλυσης του μοτίβου dateFormat, της φόρτωσης του locale και του προσδιορισμού της ζώνης ώρας. Κατά τη μορφοποίηση μιας λίστας εκατοντάδων ημερομηνιών, η επαναλαμβανόμενη δημιουργία οδηγεί σε αισθητή καθυστέρηση στο UI. Η λύση — δημιουργήστε τον μορφοποιητή μία φορά για ολόκληρη την οθόνη και επαναχρησιμοποιήστε τον. Για πρόσβαση πολλαπλών νημάτων, χρησιμοποιήστε συγχρονισμό μέσω DispatchQueue.
import Foundation
// DateFormatter στην κρυφή μνήμη — δημιουργείται μία φορά
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 — ελαφριά εναλλακτική
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"
Για τη μορφή ISO 8601, χρησιμοποιήστε ISO8601DateFormatter — δεν απαιτεί καθορισμό μοτίβου και λειτουργεί ταχύτερα από το DateFormatter. Στο iOS 10+, αυτή η κλάση είναι προτιμότερη για εργασία με ημερομηνίες διακομιστή. Για παλιές μορφές, κρατήστε το DateFormatter αλλά φροντίστε να αποθηκεύσετε το στιγμιότυπο στην κρυφή μνήμη ως στατική ιδιότητα ή singleton.
Στο Android, η ημερομηνία και ώρα επεξεργάζονται μέσω του πακέτου java.time, διαθέσιμου από το API 26. Κύριες κλάσεις: LocalDate (μόνο ημερομηνία), LocalTime (μόνο ώρα), LocalDateTime (ημερομηνία και ώρα χωρίς ζώνη ώρας) και ZonedDateTime (με ζώνη ώρας). DateTimeFormatter — ένας ασφαλής για νήματα μορφοποιητής για την πλατφόρμα κινητών Android. Η ημερομηνία σε εφαρμογές κινητών μορφοποιείται μέσω DateTimeFormatter και LocalDate χωρίς απώλεια απόδοσης.
LocalDate αποθηκεύει ημερομηνία χωρίς ώρα — ιδανικό για γενέθλια. Το ZonedDateTime αποθηκεύει πλήρεις πληροφορίες με ζώνη ώρας. Το DateTimeFormatter υποστηρίζει προκαθορισμένα μοτίβα (ISO_LOCAL_DATE, ISO_DATE_TIME) και προσαρμοσμένα μέσω ofPattern. Όλες οι κλάσεις java.time είναι αμετάβλητες και ασφαλείς για νήματα, γεγονός που εξαλείφει τις συνθήκες ανταγωνισμού κατά την εργασία σε πολλαπλά νήματα.
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.time.ZoneId
// Δημιουργία και μορφοποίηση τρέχουσας ημερομηνίας
val today = LocalDate.now()
val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
val formatted = today.format(formatter)
// Εργασία με ζώνες ώρας
val utcTime = ZonedDateTime.now(ZoneId.of("UTC"))
val moscowTime = utcTime.withZoneSameInstant(ZoneId.of("Europe/Moscow"))
DateTimeFormatter είναι σημαντικά πιο αποδοτικό από το SimpleDateFormat, επειδή δεν αναλύει το μοτίβο σε κάθε κλήση. Συνιστάται η δημιουργία σταθερών μορφοποιητή σε επίπεδο κλάσης ή αντικειμένου συντρόφου. Για μετατροπή Unix Timestamp, χρησιμοποιήστε Instant.ofEpochSecond, στη συνέχεια μετατρέψτε σε LocalDateTime ή ZonedDateTime μέσω ζώνης ώρας.
Η σωστή ημερομηνία και ώρα στην οθόνη του χρήστη εξαρτάται από τη σωστή επεξεργασία των ζωνών ώρας. Η ημερομηνία και ώρα στην ανάπτυξη κινητών για Android απαιτεί τη συνεκτίμηση της ζώνης ώρας της συσκευής. Ο χρυσός κανόνας: αποθηκεύστε την ώρα σε UTC, μετατρέψτε σε τοπική ζώνη ώρας μόνο για εμφάνιση. Το iOS παρέχει TimeZone από το Foundation, το Android παρέχει ZoneId από java.time. Και τα δύο API για πλατφόρμες κινητών υποστηρίζουν αυτόματο εντοπισμό της τρέχουσας ζώνης ώρας της συσκευής. Η ημερομηνία σε μια εφαρμογή κινητού εξαρτάται από το ZoneId και το TimeZone.
Θερινή ώρα δημιουργεί ασάφεια: μια χρονική στιγμή μπορεί να αναπαρασταθεί από δύο διαφορετικές τοπικές ώρες. Η java.time και το Foundation χειρίζονται αυτόματα τη DST. Χρησιμοποιήστε αναγνωριστικά IANA (Europe/Moscow, America/New_York), όχι συντομογραφίες (MSK, EST) — οι συντομογραφίες είναι διφορούμενες και αγνοούν τη μετάβαση στη θερινή ώρα.
import Foundation
// Μετατροπή UTC σε τοπική ώρα στο 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)
}
// Προσδιορισμός ζώνης ώρας συσκευής
let localTimeZone = TimeZone.current
let isDST = localTimeZone.isDaylightSavingTime()
let secondsFromGMT = localTimeZone.secondsFromGMT()
Στο Android, χρησιμοποιήστε ZoneId.systemDefault() για να λάβετε τη ζώνη ώρας της συσκευής. Κατά τη μετατροπή UTC σε τοπική ώρα, δημιουργήστε ένα ZonedDateTime με ZoneOffset.UTC και εφαρμόστε withZoneSameInstant. Για παλιά API χωρίς java.time, χρησιμοποιήστε TimeZone.getDefault() και Calendar — αλλά είναι καλύτερο να προσθέσετε απευθείας το ThreeTenABP.
import java.time.Instant
import java.time.ZonedDateTime
import java.time.ZoneId
// Μετατροπή Unix Timestamp σε τοπική ώρα
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 — μια βιβλιοθήκη προσαρμογέα που μεταφέρει την java.time σε Android API κάτω από 26. Παρέχει τις ίδιες κλάσεις: LocalDate, LocalTime, ZonedDateTime, DateTimeFormatter, Instant. Εάν η εφαρμογή υποστηρίζει Android 5 ή 6 (API 21-25), το ThreeTenABP είναι ο μόνος τρόπος να χρησιμοποιήσετε ένα σύγχρονο κινητό API χωρίς μετάβαση στην παρωχημένη Calendar. Η ημερομηνία και ώρα στην ανάπτυξη κινητών για παλιές συσκευές υλοποιείται μέσω αυτής της βιβλιοθήκης. Για παλιές συσκευές, η ημερομηνία και ώρα υλοποιείται μέσω ThreeTenABP.
Η βιβλιοθήκη απαιτεί αρχικοποίηση στη μέθοδο Application.onCreate() καλώντας AndroidThreeTen.init(). Αυτό φορτώνει τα δεδομένα ζωνών ώρας από τα assets. Μετά την αρχικοποίηση, το API είναι πλήρως ταυτόσημο με την java.time — ο κώδικας για API 26+ και για API 21+ θα είναι ο ίδιος. Το μέγεθος της βιβλιοθήκης είναι περίπου 400 KB, το οποίο είναι αποδεκτό για τις περισσότερες εφαρμογές κινητών.
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
// Αρχικοποίηση στο Application
class App : Application() {
override fun onCreate() {
super.onCreate()
AndroidThreeTen.init(this)
}
}
// Το API είναι ταυτόσημο με java.time
val today = LocalDate.now()
val formatted = today.format(DateTimeFormatter.ISO_LOCAL_DATE)
Η απόδοση του ThreeTenABP είναι συγκρίσιμη με την αρχική java.time σε σύγχρονες συσκευές. Σε παλιές συσκευές, μπορεί να υπάρξει καθυστέρηση στην πρώτη κλήση λόγω φόρτωσης δεδομένων ζωνών ώρας. Μεταφέρετε σταδιακά τον υπάρχοντα κώδικα από Calendar σε ThreeTenABP: αντικαταστήστε τα Date και Calendar με Instant και LocalDate σε νέες ενότητες, αφήνοντας τον παλιό κώδικα αμετάβλητο.
Unix Timestamp — ο αριθμός των δευτερολέπτων που έχουν παρέλθει από την 1η Ιανουαρίου 1970 00:00 UTC. Αυτή είναι μια καθολική μορφή για την αποθήκευση και ανταλλαγή χρόνου μεταξύ διακομιστή και πελάτη. Ωστόσο, η ώρα στη συσκευή μπορεί να διαφέρει από την πραγματική ώρα λόγω ρυθμίσεων του χρήστη. Για συγχρονισμό χρησιμοποιείται το NTP — ένα πρωτόκολλο ακριβούς ώρας μέσω UDP. Το Unix Timestamp είναι η μορφή στην οποία αποθηκεύεται η ημερομηνία και ώρα στον διακομιστή.
Date().timeIntervalSince1970 στο iOS επιστρέφει δευτερόλεπτα από το 1970. Στο Android, System.currentTimeMillis() επιστρέφει χιλιοστά του δευτερολέπτου. Για ακρίβεια, χρησιμοποιήστε NTP: η βιβλιοθήκη TrueTime για iOS και το AndroidNtp για Android στέλνουν ένα αίτημα σε έναν διακομιστή NTP και υπολογίζουν τη μετατόπιση ώρας. Η ακρίβεια NTP είναι 1-10 ms σε τοπικό δίκτυο και 10-100 ms μέσω διαδικτύου.
import Foundation
// Λήψη Unix Timestamp στο iOS
let seconds = Date().timeIntervalSince1970
let milliseconds = Int64(seconds * 1000)
// Συγχρονισμός NTP με TrueTime
TrueTime.shared.start { result in
switch result {
case .success:
let ntpTime = TrueTime.shared.now()
print("Ακριβής ώρα: \(ntpTime)")
case .failure(let error):
print("Σφάλμα NTP: \(error)")
}
}
Χωρίς συγχρονισμό σε μια εφαρμογή κινητού, είναι πιθανά σφάλματα: εσφαλμένες χρονικές σημάνσεις συμβάντων, αποσυγχρονισμός με τον διακομιστή. Ο χρόνος στην ανάπτυξη κινητών συγχρονίζεται μέσω NTP μία φορά κατά την εκκίνηση της εφαρμογής, μετά την οποία χρησιμοποιείται η υπολογισμένη μετατόπιση. Για εφαρμογές κινητών κρίσιμες ως προς την ακρίβεια (οικονομικά, logistics), το πρωτόκολλο NTP είναι υποχρεωτικό πριν από την αποστολή δεδομένων στον διακομιστή.
Συχνές ερωτήσεις
DateFormatter αναλύει το μοτίβο μορφοποίησης, φορτώνει τη locale και καθορίζει τη ζώνη ώρας κάθε φορά που δημιουργείται ένα στιγμιότυπο. Αποθηκεύστε τον μορφοποιητή στην κρυφή μνήμη ως στατική σταθερά ή χρησιμοποιήστε ISO8601DateFormatter για ISO 8601 — είναι ελαφρύτερο και ταχύτερο.
DateTimeFormatter από το πακέτο java.time — ασφαλές για νήματα και αποδοτικό. Διαθέσιμο από API 26. Για παλιές εκδόσεις, χρησιμοποιήστε ThreeTenABP που παρέχει πανομοιότυπο API.
Προσθέστε ThreeTenABP — backport της java.time για Android API 21+. Αρχικοποίηση μέσω AndroidThreeTen.init() στο Application.onCreate(). Το API είναι πλήρως ταυτόσημο με την αρχική java.time.
Ναι, αποθηκεύστε την ώρα σε UTC στον διακομιστή και στην τοπική βάση δεδομένων. Μετατρέψτε στην τοπική ζώνη ώρας του χρήστη μόνο στο στάδιο εμφάνισης. Αυτό εξαλείφει σφάλματα κατά την αλλαγή ζώνης ώρας ή τα ταξίδια.
Date().timeIntervalSince1970 επιστρέφει τον αριθμό των δευτερολέπτων από την 1η Ιανουαρίου 1970. Για χιλιοστά του δευτερολέπτου, πολλαπλασιάστε επί 1000 και μετατρέψτε σε Int64. Για ακριβή συγχρονισμό, χρησιμοποιήστε τη βιβλιοθήκη TrueTime με έναν διακομιστή NTP.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.