Design for sustainability image
Design for sustainability image
Design for sustainability image

Aug 16, 2023

Aug 16, 2023

Aug 16, 2023

25 mins

25 mins

25 mins

Swift, Xcode

Swift, Xcode

Swift, Xcode

Handling Time Zones in iOS Development

Handling Time Zones in iOS Development

Handling Time Zones in iOS Development

When developing an app for a global audience, the intricacies of managing time zones can pose unique challenges. It's not merely about keeping track of hours and minutes; it's a complex orchestration ensuring that every user, no matter where they are, receives data that's accurate and relevant.

One of the primary concerns is ensuring consistency in time and date data, irrespective of its source or destination. This necessitates storing data in a standardized format on servers, establishing a uniform reference point regardless of who's accessing it or from where.

However, storing is just half the battle. Presenting this data in a meaningful manner is equally crucial. Apps need to be adept at pinpointing a user's time zone. This allows for dates and times to be tailored, aligning seamlessly with a user's local setting, making the app experience more intuitive.

Universally Storing Time and Date on the Server


In a globally connected digital ecosystem, establishing a common language for date-time values is imperative. This unified approach becomes even more critical when various systems or segments of an application — be it the frontend, backend, or third-party services — need to interface with the stored data. The key is to obliterate any timezone-related ambiguity, ensuring seamless interactions especially in apps catering to users scattered across different time zones.

For optimal server-side data storage, it's recommended to use either UTC or an epoch timestamp

UTC (Coordinated Universal Time) is the primary time standard by which the world regulates clocks and time. It's not a time zone but a reference point derived from atomic clocks combined with the Earth's rotation

An epoch timestamp is the number of seconds (or milliseconds) that have elapsed since a specific starting point called the "epoch." In Unix-based systems, the epoch is set as midnight on January 1, 1970, UTC. So, a Unix epoch timestamp(or milliseconds) represents the number of seconds since this time.

Saving Date and Time in UTC


The importance of consistent date and time storage is unmistakably clear. In the iOS environment, achieving this often rests on the capable shoulders of Apple's Foundation framework, particularly the Date and TimeZone classes.

Let's dive into an illustrative Swift example:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Create a DateFormatter to format the date as a UTC string
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

// Convert the current date to a UTC string
let utcDateString = dateFormatter.string(from: currentDate)

// This will print the current date and time in UTC
print(utcDateString) 

// Result is "2023-08-15 11:04:03"


In this example:
1. We get the current date and time with Date().
2. We use a DateFormatter to format our date.
3. We set the timezone of our DateFormatter to UTC. You have to make sure that you use date in same date format on all platforms.
4. We then use the string(from:) method to get a string representation of our date in UTC.

Now, let's paint a scenario. Imagine two users — one basking under the Ukrainian sun (GMT+3) at 2:04 PM, and another sipping coffee in New York (GMT-4) at 7:04 AM.

Despite their differing local times, their saved UTC timestamps in the database perfectly align at 11:04 AM. Such consistency ensures that when this timestamp is later fetched, it uniformly reads 11:04 AM UTC for both users.

This methodological storage in UTC acts as a linchpin, sealing data integrity across disparate time zones, ensuring that user inputs, regardless of origin, resonate in harmony within the database.

Another effective way to manage time in a universally understood format is through timestamps, especially in milliseconds. Here's how it's done in Swift:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Convert the date to a timestamp in milliseconds
let utcTimestampInMilliseconds = Int(currentDate.timeIntervalSince1970 * 1000)

print(utcTimestampInMilliseconds) // This will print the current timestamp in milliseconds in UTC
// Result is 1692097456000 that is August 15, 2023 11:04:16 AM in UTC


In this example:
1. We get the current date and time with Date().
2. We use the timeIntervalSince1970 property of the Date object, which gives the time interval since the epoch (January 1, 1970, at 12:00 AM UTC) in seconds. To convert this to milliseconds, we multiply by 1000.
3. The result is printed out, and this value is consistent regardless of the local time zone of the device.

The fundamental lesson from both the UTC and timestamp methods is the pivotal role of UTC as a cornerstone time zone. Employing this standard ensures consistency, clarity, and avoids potential confusions arising from local time variations.

Detecting User's Timezone and displaying date and time


Having standardized the way we store date and time data, the next challenge is to present this data back to the user in a manner that aligns with their local context. In other words, how can we convert and display the stored UTC date and time to match the user's local timezone?

When presented with a UTC string like 2023-08-15 11:04:16, it can be converted to reflect the user's local date and time using Swift. Here's a step-by-step guide:

import Foundation

let utcDateString = "2023-08-15 11:04:03"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

if let utcDate = dateFormatter.date(from: utcDateString) {
    dateFormatter.timeZone = TimeZone.current // Take phone timezone
    let localDateString = dateFormatter.string(from: utcDate)
    print(localDateString) // This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"
}


Given the timestamp from our previous example, 1692097456000 (corresponding to August 15, 2023 11:04:16 AM in UTC), we can convert it as follows:


import Foundation

let timestampInMilliseconds: TimeInterval = 1692097456000 / 1000
let dateFromTimestamp = Date(timeIntervalSince1970: timestampInMilliseconds)

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone.current

let localDateString = dateFormatter.string(from: dateFromTimestamp)
print(localDateString)// This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"


By using the provided code snippets, developers can accurately display either a UTC formatted string or an epoch timestamp in milliseconds according to the local timezone settings of a device. This ensures that users receive time-sensitive information in a format that is both familiar and meaningful to them.

Pitfall of attempting to save local time directly in a Date object


For developers versed in Swift, using the Date() function might give rise to the temptation of storing dates directly in a user's local time zone, only to then convert them back to a Date() object. This method, though seeming straightforward, is riddled with potential issues.

import Foundation

let localDateString = "2023-08-15 14:04:03" // Local time in GMT+3
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+3")

if let localDate = dateFormatter.date(from: localDateString) {
    // The developer thinks they've stored the local date and time (14:04:03 in GMT+3)
    print(localDate) 
    // But when they print or use the Date object, this will show: "2023-08-15 11:04:03 +0000"
}


This example underscores the importance of working with dates in UTC format and reserving local time conversions strictly for display purposes. Storing local times directly in Dateobjects can be misleading. Such a practice can result in ambiguous data because, by design, the Date object encapsulates a specific moment in time in UTC, regardless of the format or time zone it was initially provided.

Handling Local Timezones in iOS Apps


One of the challenges in developing global applications is managing time data across various time zones. The strategy of uniformly storing dates and times, then translating them to local settings for presentation, is not only efficient but crucial for user comprehension. Here's a hands-on example to elucidate this:


import Foundation

extension Int { // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

struct User {
    var registrationTimestamp:Int

    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = TimeZone.current
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        let calendar = Calendar.current
        return calendar.isDateInToday(registrationDateUTC)
    }
}

let user = User(registrationTimestamp: 1692105637386)

print(user.registrationDateUTC) // 2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) // Tuesday, August 15, 2023 at 4:20 PM
print(user.registrationLocalDateString) // Tuesday, August 15, 2023
print(user.registrationLocalTimeString) // 4:20 PM


Navigating dates and times across diverse time zones can be intricate, but it's paramount for universal app accessibility. Adhering to a practice of storing dates in a universal format, such as epoch timestamps or UTC, and then converting them to local time only for user display, ensures a blend of data consistency and presentation flexibility. The above instance serves as an archetype for this methodology, confirming that users globally obtain date and time data that aligns with their local framework.

Manually Setting Timezones in Apps


Often, for a myriad of reasons – from traveling across time zones to handling tasks in a different location – users might want the flexibility to manually set their time zone in an application. This can pose challenges for developers, especially when balancing between this manually set time zone and the device's default time zone.


import Foundation

extension Int {
    // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

extension Date {
    // extention for converting Date to timestamp
    var millisecondsSince1970:Int {
        return Int((self.timeIntervalSince1970 * 1000.0).rounded())
    }
    
    // extention for converting Date to UTC string format
    func convertToUTCString() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.current
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        return dateFormatter.string(from: self)
    }
}

struct User {
    var registrationTimestamp:Int
    var selectedTimeZoneId:String?
    
    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = currentTimeZone()
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        var calendar = Calendar.current
        calendar.timeZone = currentTimeZone()
        return calendar.isDateInToday(registrationDateUTC)
    }
    
    func currentTimeZone() -> TimeZone {
        if let timeZoneId = selectedTimeZoneId {
            return TimeZone(identifier: timeZoneId)!
        } else {
            return TimeZone.current
        }
    }
    
    func convertDateToTimestamp() -> Int {
        return Date().millisecondsSince1970
    }
    
    func convertDateToUTC() -> String {
        return Date().convertToUTCString()
    }
}

let user = User(registrationTimestamp: 1692105637386, selectedTimeZoneId: "GMT-4")

print(user.registrationDateUTC) //2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) //Tuesday, August 15, 2023 at 9:20 AM
print(user.registrationLocalDateString) //Tuesday, August 15, 2023
print(user.registrationLocalTimeString) //9:20 AM
print(user.convertDateToTimestamp())// 1692105721987
print(user.convertDateToUTC()) //2023-08-15 13:22:01


Here, the currentTimeZone function checks if a user has selected a preferred time zone selectedTimeZoneId. If they have, it uses this time zone; otherwise, it defaults to the device's time zone.

Balancing between a device's default settings and a user's preferences can be tricky. However, with careful structuring and clear conditional checks, developers can provide users with the flexibility they desire without compromising on consistency or usability. The example above demonstrates a method to handle such scenarios, allowing users to interact with dates and times in their preferred context.

When developing an app for a global audience, the intricacies of managing time zones can pose unique challenges. It's not merely about keeping track of hours and minutes; it's a complex orchestration ensuring that every user, no matter where they are, receives data that's accurate and relevant.

One of the primary concerns is ensuring consistency in time and date data, irrespective of its source or destination. This necessitates storing data in a standardized format on servers, establishing a uniform reference point regardless of who's accessing it or from where.

However, storing is just half the battle. Presenting this data in a meaningful manner is equally crucial. Apps need to be adept at pinpointing a user's time zone. This allows for dates and times to be tailored, aligning seamlessly with a user's local setting, making the app experience more intuitive.

Universally Storing Time and Date on the Server


In a globally connected digital ecosystem, establishing a common language for date-time values is imperative. This unified approach becomes even more critical when various systems or segments of an application — be it the frontend, backend, or third-party services — need to interface with the stored data. The key is to obliterate any timezone-related ambiguity, ensuring seamless interactions especially in apps catering to users scattered across different time zones.

For optimal server-side data storage, it's recommended to use either UTC or an epoch timestamp

UTC (Coordinated Universal Time) is the primary time standard by which the world regulates clocks and time. It's not a time zone but a reference point derived from atomic clocks combined with the Earth's rotation

An epoch timestamp is the number of seconds (or milliseconds) that have elapsed since a specific starting point called the "epoch." In Unix-based systems, the epoch is set as midnight on January 1, 1970, UTC. So, a Unix epoch timestamp(or milliseconds) represents the number of seconds since this time.

Saving Date and Time in UTC


The importance of consistent date and time storage is unmistakably clear. In the iOS environment, achieving this often rests on the capable shoulders of Apple's Foundation framework, particularly the Date and TimeZone classes.

Let's dive into an illustrative Swift example:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Create a DateFormatter to format the date as a UTC string
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

// Convert the current date to a UTC string
let utcDateString = dateFormatter.string(from: currentDate)

// This will print the current date and time in UTC
print(utcDateString) 

// Result is "2023-08-15 11:04:03"


In this example:
1. We get the current date and time with Date().
2. We use a DateFormatter to format our date.
3. We set the timezone of our DateFormatter to UTC. You have to make sure that you use date in same date format on all platforms.
4. We then use the string(from:) method to get a string representation of our date in UTC.

Now, let's paint a scenario. Imagine two users — one basking under the Ukrainian sun (GMT+3) at 2:04 PM, and another sipping coffee in New York (GMT-4) at 7:04 AM.

Despite their differing local times, their saved UTC timestamps in the database perfectly align at 11:04 AM. Such consistency ensures that when this timestamp is later fetched, it uniformly reads 11:04 AM UTC for both users.

This methodological storage in UTC acts as a linchpin, sealing data integrity across disparate time zones, ensuring that user inputs, regardless of origin, resonate in harmony within the database.

Another effective way to manage time in a universally understood format is through timestamps, especially in milliseconds. Here's how it's done in Swift:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Convert the date to a timestamp in milliseconds
let utcTimestampInMilliseconds = Int(currentDate.timeIntervalSince1970 * 1000)

print(utcTimestampInMilliseconds) // This will print the current timestamp in milliseconds in UTC
// Result is 1692097456000 that is August 15, 2023 11:04:16 AM in UTC


In this example:
1. We get the current date and time with Date().
2. We use the timeIntervalSince1970 property of the Date object, which gives the time interval since the epoch (January 1, 1970, at 12:00 AM UTC) in seconds. To convert this to milliseconds, we multiply by 1000.
3. The result is printed out, and this value is consistent regardless of the local time zone of the device.

The fundamental lesson from both the UTC and timestamp methods is the pivotal role of UTC as a cornerstone time zone. Employing this standard ensures consistency, clarity, and avoids potential confusions arising from local time variations.

Detecting User's Timezone and displaying date and time


Having standardized the way we store date and time data, the next challenge is to present this data back to the user in a manner that aligns with their local context. In other words, how can we convert and display the stored UTC date and time to match the user's local timezone?

When presented with a UTC string like 2023-08-15 11:04:16, it can be converted to reflect the user's local date and time using Swift. Here's a step-by-step guide:

import Foundation

let utcDateString = "2023-08-15 11:04:03"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

if let utcDate = dateFormatter.date(from: utcDateString) {
    dateFormatter.timeZone = TimeZone.current // Take phone timezone
    let localDateString = dateFormatter.string(from: utcDate)
    print(localDateString) // This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"
}


Given the timestamp from our previous example, 1692097456000 (corresponding to August 15, 2023 11:04:16 AM in UTC), we can convert it as follows:


import Foundation

let timestampInMilliseconds: TimeInterval = 1692097456000 / 1000
let dateFromTimestamp = Date(timeIntervalSince1970: timestampInMilliseconds)

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone.current

let localDateString = dateFormatter.string(from: dateFromTimestamp)
print(localDateString)// This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"


By using the provided code snippets, developers can accurately display either a UTC formatted string or an epoch timestamp in milliseconds according to the local timezone settings of a device. This ensures that users receive time-sensitive information in a format that is both familiar and meaningful to them.

Pitfall of attempting to save local time directly in a Date object


For developers versed in Swift, using the Date() function might give rise to the temptation of storing dates directly in a user's local time zone, only to then convert them back to a Date() object. This method, though seeming straightforward, is riddled with potential issues.

import Foundation

let localDateString = "2023-08-15 14:04:03" // Local time in GMT+3
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+3")

if let localDate = dateFormatter.date(from: localDateString) {
    // The developer thinks they've stored the local date and time (14:04:03 in GMT+3)
    print(localDate) 
    // But when they print or use the Date object, this will show: "2023-08-15 11:04:03 +0000"
}


This example underscores the importance of working with dates in UTC format and reserving local time conversions strictly for display purposes. Storing local times directly in Dateobjects can be misleading. Such a practice can result in ambiguous data because, by design, the Date object encapsulates a specific moment in time in UTC, regardless of the format or time zone it was initially provided.

Handling Local Timezones in iOS Apps


One of the challenges in developing global applications is managing time data across various time zones. The strategy of uniformly storing dates and times, then translating them to local settings for presentation, is not only efficient but crucial for user comprehension. Here's a hands-on example to elucidate this:


import Foundation

extension Int { // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

struct User {
    var registrationTimestamp:Int

    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = TimeZone.current
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        let calendar = Calendar.current
        return calendar.isDateInToday(registrationDateUTC)
    }
}

let user = User(registrationTimestamp: 1692105637386)

print(user.registrationDateUTC) // 2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) // Tuesday, August 15, 2023 at 4:20 PM
print(user.registrationLocalDateString) // Tuesday, August 15, 2023
print(user.registrationLocalTimeString) // 4:20 PM


Navigating dates and times across diverse time zones can be intricate, but it's paramount for universal app accessibility. Adhering to a practice of storing dates in a universal format, such as epoch timestamps or UTC, and then converting them to local time only for user display, ensures a blend of data consistency and presentation flexibility. The above instance serves as an archetype for this methodology, confirming that users globally obtain date and time data that aligns with their local framework.

Manually Setting Timezones in Apps


Often, for a myriad of reasons – from traveling across time zones to handling tasks in a different location – users might want the flexibility to manually set their time zone in an application. This can pose challenges for developers, especially when balancing between this manually set time zone and the device's default time zone.


import Foundation

extension Int {
    // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

extension Date {
    // extention for converting Date to timestamp
    var millisecondsSince1970:Int {
        return Int((self.timeIntervalSince1970 * 1000.0).rounded())
    }
    
    // extention for converting Date to UTC string format
    func convertToUTCString() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.current
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        return dateFormatter.string(from: self)
    }
}

struct User {
    var registrationTimestamp:Int
    var selectedTimeZoneId:String?
    
    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = currentTimeZone()
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        var calendar = Calendar.current
        calendar.timeZone = currentTimeZone()
        return calendar.isDateInToday(registrationDateUTC)
    }
    
    func currentTimeZone() -> TimeZone {
        if let timeZoneId = selectedTimeZoneId {
            return TimeZone(identifier: timeZoneId)!
        } else {
            return TimeZone.current
        }
    }
    
    func convertDateToTimestamp() -> Int {
        return Date().millisecondsSince1970
    }
    
    func convertDateToUTC() -> String {
        return Date().convertToUTCString()
    }
}

let user = User(registrationTimestamp: 1692105637386, selectedTimeZoneId: "GMT-4")

print(user.registrationDateUTC) //2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) //Tuesday, August 15, 2023 at 9:20 AM
print(user.registrationLocalDateString) //Tuesday, August 15, 2023
print(user.registrationLocalTimeString) //9:20 AM
print(user.convertDateToTimestamp())// 1692105721987
print(user.convertDateToUTC()) //2023-08-15 13:22:01


Here, the currentTimeZone function checks if a user has selected a preferred time zone selectedTimeZoneId. If they have, it uses this time zone; otherwise, it defaults to the device's time zone.

Balancing between a device's default settings and a user's preferences can be tricky. However, with careful structuring and clear conditional checks, developers can provide users with the flexibility they desire without compromising on consistency or usability. The example above demonstrates a method to handle such scenarios, allowing users to interact with dates and times in their preferred context.

When developing an app for a global audience, the intricacies of managing time zones can pose unique challenges. It's not merely about keeping track of hours and minutes; it's a complex orchestration ensuring that every user, no matter where they are, receives data that's accurate and relevant.

One of the primary concerns is ensuring consistency in time and date data, irrespective of its source or destination. This necessitates storing data in a standardized format on servers, establishing a uniform reference point regardless of who's accessing it or from where.

However, storing is just half the battle. Presenting this data in a meaningful manner is equally crucial. Apps need to be adept at pinpointing a user's time zone. This allows for dates and times to be tailored, aligning seamlessly with a user's local setting, making the app experience more intuitive.

Universally Storing Time and Date on the Server


In a globally connected digital ecosystem, establishing a common language for date-time values is imperative. This unified approach becomes even more critical when various systems or segments of an application — be it the frontend, backend, or third-party services — need to interface with the stored data. The key is to obliterate any timezone-related ambiguity, ensuring seamless interactions especially in apps catering to users scattered across different time zones.

For optimal server-side data storage, it's recommended to use either UTC or an epoch timestamp

UTC (Coordinated Universal Time) is the primary time standard by which the world regulates clocks and time. It's not a time zone but a reference point derived from atomic clocks combined with the Earth's rotation

An epoch timestamp is the number of seconds (or milliseconds) that have elapsed since a specific starting point called the "epoch." In Unix-based systems, the epoch is set as midnight on January 1, 1970, UTC. So, a Unix epoch timestamp(or milliseconds) represents the number of seconds since this time.

Saving Date and Time in UTC


The importance of consistent date and time storage is unmistakably clear. In the iOS environment, achieving this often rests on the capable shoulders of Apple's Foundation framework, particularly the Date and TimeZone classes.

Let's dive into an illustrative Swift example:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Create a DateFormatter to format the date as a UTC string
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

// Convert the current date to a UTC string
let utcDateString = dateFormatter.string(from: currentDate)

// This will print the current date and time in UTC
print(utcDateString) 

// Result is "2023-08-15 11:04:03"


In this example:
1. We get the current date and time with Date().
2. We use a DateFormatter to format our date.
3. We set the timezone of our DateFormatter to UTC. You have to make sure that you use date in same date format on all platforms.
4. We then use the string(from:) method to get a string representation of our date in UTC.

Now, let's paint a scenario. Imagine two users — one basking under the Ukrainian sun (GMT+3) at 2:04 PM, and another sipping coffee in New York (GMT-4) at 7:04 AM.

Despite their differing local times, their saved UTC timestamps in the database perfectly align at 11:04 AM. Such consistency ensures that when this timestamp is later fetched, it uniformly reads 11:04 AM UTC for both users.

This methodological storage in UTC acts as a linchpin, sealing data integrity across disparate time zones, ensuring that user inputs, regardless of origin, resonate in harmony within the database.

Another effective way to manage time in a universally understood format is through timestamps, especially in milliseconds. Here's how it's done in Swift:


import Foundation

// Get the current date and time.
let currentDate = Date()

// Convert the date to a timestamp in milliseconds
let utcTimestampInMilliseconds = Int(currentDate.timeIntervalSince1970 * 1000)

print(utcTimestampInMilliseconds) // This will print the current timestamp in milliseconds in UTC
// Result is 1692097456000 that is August 15, 2023 11:04:16 AM in UTC


In this example:
1. We get the current date and time with Date().
2. We use the timeIntervalSince1970 property of the Date object, which gives the time interval since the epoch (January 1, 1970, at 12:00 AM UTC) in seconds. To convert this to milliseconds, we multiply by 1000.
3. The result is printed out, and this value is consistent regardless of the local time zone of the device.

The fundamental lesson from both the UTC and timestamp methods is the pivotal role of UTC as a cornerstone time zone. Employing this standard ensures consistency, clarity, and avoids potential confusions arising from local time variations.

Detecting User's Timezone and displaying date and time


Having standardized the way we store date and time data, the next challenge is to present this data back to the user in a manner that aligns with their local context. In other words, how can we convert and display the stored UTC date and time to match the user's local timezone?

When presented with a UTC string like 2023-08-15 11:04:16, it can be converted to reflect the user's local date and time using Swift. Here's a step-by-step guide:

import Foundation

let utcDateString = "2023-08-15 11:04:03"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")

if let utcDate = dateFormatter.date(from: utcDateString) {
    dateFormatter.timeZone = TimeZone.current // Take phone timezone
    let localDateString = dateFormatter.string(from: utcDate)
    print(localDateString) // This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"
}


Given the timestamp from our previous example, 1692097456000 (corresponding to August 15, 2023 11:04:16 AM in UTC), we can convert it as follows:


import Foundation

let timestampInMilliseconds: TimeInterval = 1692097456000 / 1000
let dateFromTimestamp = Date(timeIntervalSince1970: timestampInMilliseconds)

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone.current

let localDateString = dateFormatter.string(from: dateFromTimestamp)
print(localDateString)// This will print the date and time in user's local time zone
// For my local timezone GMT+3 it's "2023-08-15 14:04:03"


By using the provided code snippets, developers can accurately display either a UTC formatted string or an epoch timestamp in milliseconds according to the local timezone settings of a device. This ensures that users receive time-sensitive information in a format that is both familiar and meaningful to them.

Pitfall of attempting to save local time directly in a Date object


For developers versed in Swift, using the Date() function might give rise to the temptation of storing dates directly in a user's local time zone, only to then convert them back to a Date() object. This method, though seeming straightforward, is riddled with potential issues.

import Foundation

let localDateString = "2023-08-15 14:04:03" // Local time in GMT+3
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(abbreviation: "GMT+3")

if let localDate = dateFormatter.date(from: localDateString) {
    // The developer thinks they've stored the local date and time (14:04:03 in GMT+3)
    print(localDate) 
    // But when they print or use the Date object, this will show: "2023-08-15 11:04:03 +0000"
}


This example underscores the importance of working with dates in UTC format and reserving local time conversions strictly for display purposes. Storing local times directly in Dateobjects can be misleading. Such a practice can result in ambiguous data because, by design, the Date object encapsulates a specific moment in time in UTC, regardless of the format or time zone it was initially provided.

Handling Local Timezones in iOS Apps


One of the challenges in developing global applications is managing time data across various time zones. The strategy of uniformly storing dates and times, then translating them to local settings for presentation, is not only efficient but crucial for user comprehension. Here's a hands-on example to elucidate this:


import Foundation

extension Int { // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

struct User {
    var registrationTimestamp:Int

    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = TimeZone.current
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = TimeZone.current
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        let calendar = Calendar.current
        return calendar.isDateInToday(registrationDateUTC)
    }
}

let user = User(registrationTimestamp: 1692105637386)

print(user.registrationDateUTC) // 2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) // Tuesday, August 15, 2023 at 4:20 PM
print(user.registrationLocalDateString) // Tuesday, August 15, 2023
print(user.registrationLocalTimeString) // 4:20 PM


Navigating dates and times across diverse time zones can be intricate, but it's paramount for universal app accessibility. Adhering to a practice of storing dates in a universal format, such as epoch timestamps or UTC, and then converting them to local time only for user display, ensures a blend of data consistency and presentation flexibility. The above instance serves as an archetype for this methodology, confirming that users globally obtain date and time data that aligns with their local framework.

Manually Setting Timezones in Apps


Often, for a myriad of reasons – from traveling across time zones to handling tasks in a different location – users might want the flexibility to manually set their time zone in an application. This can pose challenges for developers, especially when balancing between this manually set time zone and the device's default time zone.


import Foundation

extension Int {
    // extention for converting timestamp to Date
    var toDate: Date {
        return Date(timeIntervalSince1970: Double(self)/1000.0)
    }
}

extension Date {
    // extention for converting Date to timestamp
    var millisecondsSince1970:Int {
        return Int((self.timeIntervalSince1970 * 1000.0).rounded())
    }
    
    // extention for converting Date to UTC string format
    func convertToUTCString() -> String {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale.current
        dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
        dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
        return dateFormatter.string(from: self)
    }
}

struct User {
    var registrationTimestamp:Int
    var selectedTimeZoneId:String?
    
    var registrationDateUTC: Date {
        return registrationTimestamp.toDate
    }

    var registrationLocalDateFullString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .short
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023 at 2:04 PM"
    }
    
    var registrationLocalDateString: String {
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .full
        dateFormatter.timeStyle = .none
        dateFormatter.timeZone = currentTimeZone()
        return dateFormatter.string(from: registrationDateUTC)
        // Expected format: "Tuesday, August 15, 2023"
    }

    var registrationLocalTimeString: String {
        let timeFormatter = DateFormatter()
        timeFormatter.dateStyle = .none
        timeFormatter.timeStyle = .short
        timeFormatter.timeZone = currentTimeZone()
        return timeFormatter.string(from: registrationDateUTC)
        // Expected format: "2:04 PM"
    }

    var isUserRegisteredToday: Bool {
        var calendar = Calendar.current
        calendar.timeZone = currentTimeZone()
        return calendar.isDateInToday(registrationDateUTC)
    }
    
    func currentTimeZone() -> TimeZone {
        if let timeZoneId = selectedTimeZoneId {
            return TimeZone(identifier: timeZoneId)!
        } else {
            return TimeZone.current
        }
    }
    
    func convertDateToTimestamp() -> Int {
        return Date().millisecondsSince1970
    }
    
    func convertDateToUTC() -> String {
        return Date().convertToUTCString()
    }
}

let user = User(registrationTimestamp: 1692105637386, selectedTimeZoneId: "GMT-4")

print(user.registrationDateUTC) //2023-08-15 13:20:37 +0000
print(user.registrationLocalDateFullString) //Tuesday, August 15, 2023 at 9:20 AM
print(user.registrationLocalDateString) //Tuesday, August 15, 2023
print(user.registrationLocalTimeString) //9:20 AM
print(user.convertDateToTimestamp())// 1692105721987
print(user.convertDateToUTC()) //2023-08-15 13:22:01


Here, the currentTimeZone function checks if a user has selected a preferred time zone selectedTimeZoneId. If they have, it uses this time zone; otherwise, it defaults to the device's time zone.

Balancing between a device's default settings and a user's preferences can be tricky. However, with careful structuring and clear conditional checks, developers can provide users with the flexibility they desire without compromising on consistency or usability. The example above demonstrates a method to handle such scenarios, allowing users to interact with dates and times in their preferred context.