How to use DateComponents to represent the extra 1 hour period at the end of day light saving?

65 Views Asked by At

Usually, during end of day light saving, we will be gaining extra 1 hours.

Take Tehran timezone as an example.

During 22 September 2021, Tehran will backward by 1 hour from 00:00 AM, to 11:00 PM.

I wrote the following code to demonstrate such.

import UIKit

func date(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int) -> Date {
    var dateComponents = DateComponents()
    dateComponents.year = year
    dateComponents.month = month
    dateComponents.day = day
    dateComponents.hour = hour
    dateComponents.minute = minute
    dateComponents.second = second

    let date = Calendar.current.date(from: dateComponents)!
    return date
}

// During 22 September 2021, Tehran will backward by 1 hour from 00:00 AM, to 11:00 PM.

let tehranTimeZone = TimeZone(identifier: "Asia/Tehran")!
let oldDefault = NSTimeZone.default
NSTimeZone.default = tehranTimeZone
defer {
    NSTimeZone.default = oldDefault
}


let date1 = date(year: 2021, month: 09, day: 21, hour: 23, minute: 59, second: 59)
let date2 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 00)
let date3 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 01)

// STEP 1: 2021 Sep 21 23:59:59 => 1632252599.0, Tuesday, September 21, 2021 at 11:59:59 PM Iran Daylight Time
print("STEP 1: 2021 Sep 21 23:59:59 => \(date1.timeIntervalSince1970), \(date1.description(with: .current))")

// STEP 2: 2021 Sep 22 00:00:00 => 1632256200.0, Wednesday, September 22, 2021 at 12:00:00 AM Iran Standard Time
print("STEP 2: 2021 Sep 22 00:00:00 => \(date2.timeIntervalSince1970), \(date2.description(with: .current))")

// STEP 3: 2021 Sep 22 00:00:01 => 1632256201.0, Wednesday, September 22, 2021 at 12:00:01 AM Iran Standard Time
print("STEP 3: 2021 Sep 22 00:00:01 => \(date3.timeIntervalSince1970), \(date3.description(with: .current))")

From STEP 1 transits to STEP 2, instead for their timeIntervalSince1970 different by +1 seconds, their difference are +3601 seconds, due to the extra 1 hour gain.

Now, my question is, how can we use DateComponents to represent the extra 1 hour period at the end of day light saving?

In another, how can I use DateComponents to generate a Date which is capable to print the following?

2021 Sep 21 23:00:00 => 1632252600.0, Tuesday, September 21, 2021 at 11:00:00 PM Iran Standard Time

Now, we understand that, in Tehran, during 2021 Sept 21, there are 2 type of 23:00:00 time

  • 23:00:00 Iran Daylight time (Epoch is 1632249000)
  • 23:00:00 Iran Standard time (Epoch is 1632252600)

23:00:00 Iran Daylight time (Epoch is 1632249000)

enter image description here

I can represent the above using

let date = date(year: 2021, month: 09, day: 21, hour: 23, minute: 00, second: 00)

23:00:00 Iran Standard time (Epoch is 1632252600)

enter image description here

I have no idea how to represent the above. As, I do not find a way in DateComponents, to enable us to specific whether the local time is belong to standard time, or daylight time.

1

There are 1 best solutions below

1
Duncan C On

DateComponents do not have a time zone. The time zone comes into it when you convert DateComponents to a Date, using the call Calendar.date(from:). It's the calendar's time zone that determines how those DateComponents are converted to a Date.

Instead of using Calendar.current, create a custom calendar and set it to the IRST time zone. (I couldn't figure out the time zone for Iran Daylight time. I would have expected it to have the abbreviation "IRDT", but that doesn't work.)

Let's say we have a calendar irstCalendar that's set to Iran Standard Time ("IRST").

If you use irstCalendar.date(from: dateComponents) you'll always get the Date based on standard time.

Consider this code:

func date(year: Int, month: Int, day: Int, hour: Int, minute: Int, second: Int, calendar: Calendar = Calendar.current) -> Date {
    var dateComponents = DateComponents()
    dateComponents.year = year
    dateComponents.month = month
    dateComponents.day = day
    dateComponents.hour = hour
    dateComponents.minute = minute
    dateComponents.second = second

    let date = calendar.date(from: dateComponents)!
    return date
}

guard let tehranStandardTimeZone = TimeZone(abbreviation: "IRST") else {
    fatalError("Can't create time zones")
}

var tehranSTCalendar = Calendar(identifier: .gregorian)
tehranSTCalendar.timeZone = tehranStandardTimeZone

let tehranDateFormatter = DateFormatter()
tehranDateFormatter.dateStyle = .medium
tehranDateFormatter.timeStyle = .medium

tehranDateFormatter.timeZone = tehranStandardTimeZone
let date1 = date(year: 2021, month: 09, day: 21, hour: 23, minute: 59, second: 59, calendar: tehranSTCalendar)
let date2 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 00, calendar: tehranSTCalendar)
let date3 = date(year: 2021, month: 09, day: 22, hour: 00, minute: 00, second: 01, calendar: tehranSTCalendar)

print("STEP 1: 2021 Sep 21 23:59:59 => \(date1.timeIntervalSince1970), \(tehranDateFormatter.string(from:date1))")
print("STEP 2: 2021 Sep 22 00:00:00 => \(date2.timeIntervalSince1970), \(tehranDateFormatter.string(from:date2))")
print("STEP 3: 2021 Sep 22 00:00:01 => \(date3.timeIntervalSince1970), \(tehranDateFormatter.string(from:date3))")

That outputs:

STEP 1: 2021 Sep 21 23:59:59 => 1632252599.0, Sep 21, 2021 at 11:59:59 PM
STEP 2: 2021 Sep 22 00:00:00 => 1632256200.0, Sep 22, 2021 at 12:00:00 AM
STEP 3: 2021 Sep 22 00:00:01 => 1632256201.0, Sep 22, 2021 at 12:00:01 AM