Converting NSDate from UTC to local gives wrong result - swift

I am creating an NSDate from components of hour and minute. It is in GMT and prints correctly as:
0001-01-01 07:30:00 +0000
I then want to convert this to my local timezone (CET), so I set up the NSDateFormatter like so:
formatter.timeZone = NSTimeZone.localTimeZone()
This prints (using .Longstyle):
08.13.00 GMT+0.43
This is wrong—it is supposed to be GMT+1. Printing the .localTimeZone() gives the correct offset:
Local Time Zone (Europe/Oslo (CET) offset 3600)
Edit1: By adding this extension(from an answer linked in comments) to NSDate, I can offset the timezone by hand. But then I need to set the NSDateFormatter timezone to GMT, which I don't think is right.
extension NSDate {
func toLocalTime() -> NSDate {
let timeZone = NSTimeZone.localTimeZone()
let seconds : NSTimeInterval = Double(timeZone.secondsFromGMTForDate(self))
let localDate = NSDate(timeInterval: seconds, sinceDate: self)
return localDate
}
}
Edit2: I made a test project. The expected output is for the time printed to match the offset in the timezone. Instead it adds 43 minutes.
func applicationDidFinishLaunching(aNotification: NSNotification) {
let calendar = NSCalendar.currentCalendar()
let realDateComponents = calendar.components([.Hour, .Minute], fromDate: NSDate())
guard let realDate = calendar.dateFromComponents(realDateComponents)
else{fatalError("Unable to get real date.")}
let formatter = NSDateFormatter()
formatter.timeStyle = .ShortStyle
print(realDate)
print(formatter.stringFromDate(realDate))
print(NSTimeZone.localTimeZone())
}
// OUTPUT
0001-01-01 21:03:00 +0000
21.46
Local Time Zone (Europe/Oslo (CET) offset 3600)

NSDate objects encapsulate a single point in time, independent of any
particular calendrical system or time zone. Date objects are
immutable, representing an invariant time interval relative to an
absolute reference date (00:00:00 UTC on 1 January 2001).
Apparently you're creating the date from the .Hour and .Minute components, that sets the (indeterminate) year information to 01 (not 2001).
The point in time about 2000 years ago is a pretty large time interval which probably causes the weird behavior.

Related

Swift Date function do not work as Expected

I am trying to find out the difference between two date in seconds using Swift 4.1. This is the code I use,
func getDurationInSeconds(date1 :Date) -> Int{
guard let durationInSeconds = Calendar.current.dateComponents([.second], from: Date(), to: date1).second else {
return 0
}
return durationInSeconds
}
Function to generate date1 from 2018-10-09T18:19:00Z
func dateFromString(stringDate:String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") as Locale?
let date = dateFormatter.date(from: stringDate)
return date
}
The Date is always returning back an hour less than my current device time, so the calculation is not working as expected. If my current device time is 16:34 the Date() function returns it back as 15:34.
I have seen that Date() is returning back the time in UTC not based on my timezone.
At the moment if I pass in a Date 09/10/2018 14:25:00 and the current device time is 09/10/2018 14:20:00. I am expecting this function to return a value 300 which is 60 * 5 minute difference between two dates.
But I am getting back a value of 3900 which is because the date function returns the date as
09/10/2018 13:20:00 instead of 14:20
. So the duration will be 1 hour + the 300 second difference.
Including a sample output from Xcode console, my device time when I executed this code was 2018-10-09 17:56:28.565423
(lldb) po date1
▿ 2018-10-09 17:59:00 +0000
- timeIntervalSinceReferenceDate : 560800740.0
(lldb) po durationInSeconds
3731
(lldb) po Date()
▿ 2018-10-09 16:57:04 +0000
- timeIntervalSinceReferenceDate : 560797024.35021996
(lldb)
But I cant find a proper way to find the correct duration between two times based on my current time zone. How can I do it?
The issue is not with the Date() returning wrong time. Date() always returns the current time, which is not really based on your (any other) local timezone.
The problem seems to be with the dateFormatter that you are using to generate the Date object from the date string.
Please try using the following lines of code:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
// Not necessary as the dateFormatter would take the device's timeZone to be the default.
dateFormatter.timeZone = Calendar.current.timeZone
instead of:
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
The problem with the latter is that, you are specifying 'Z' to be the zero-offset time zone (UTC). So, the difference of 1 hour from UTC in your device is causing this offset.
And, while passing in the date string, please make sure that you skip the 'Z' at the end (For example, it should be like 2018-10-09T18:19:00).
The updated code should work good for you, and return the expected difference in seconds.
Since you are using a string that represents the current time in your time zone, try this instead:
func getDurationInSeconds(date1: Date) -> Int {
return Int(-date1.timeIntervalSinceNow + TimeZone.current.daylightSavingTimeOffset(for: date1))
}
It uses this property and this method.
Or if you'd like to account for the time zone difference from UTC in dateFromString(stringDate:):
func getDurationInSeconds(date1: Date) -> Int {
return Int(-date1.timeIntervalSinceNow)
}
func dateFromString(stringDate:String) -> Date? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let date = dateFormatter.date(from: stringDate)! //I am force-unwrapping for brevity
let adjustedDate = date.addingTimeInterval(-TimeZone.current.daylightSavingTimeOffset(for: date))
return adjustedDate
}
Test
I am in a UTC + 1h timezone:
let str = "2018-10-09T19:50:00Z" //"2018-10-09T19:50:00Z"
let date1 = dateFromString(stringDate: str)! //"Oct 9, 2018 at 7:50 PM"
Date() //At the time of testing this it is "Oct 9, 2018 at 7:53 PM"
getDurationInSeconds(date1: date1) //213, which is 3 minutes and 33 seconds

Swift - Date from components is incorrect when printed. Date has 2 values?

I'm trying to store a time of day in a Date:
let calendar = NSCalendar.init(identifier: .gregorian)
let components = NSDateComponents()
components.hour = 7
components.minute = 0
var newDate = calendar?.date(from: components as DateComponents)!
print(newDate!)
However, this yields some bizarre results when I try to print or otherwise use the value. Here's a Swift Playground of the results:
How can newDate be both 7:00AM and 11:56AM at the same time?
You didn't specify a time zone (by setting the timeZone property of either components or calendar). So the system used your local time zone to convert components to a date.
The playground system used your local time zone to convert newDate to the string “Jan 1, 1 at 7:00 AM”. (The playground system has special-case code for displaying Date objects. The special-case code uses your local time zone.)
The print function used the UTC time zone to convert newDate to the string “0001-01-01 11:56:02 +0000”. (The print function uses the CustomStringConvertible protocol. Date's CustomStringConvertible implementation uses the UTC time zone.)
I deduce that your local time zone is US/Eastern time, also known as America/New_York:
import Foundation
var calendar = Calendar(identifier: .gregorian)
let components = NSDateComponents()
components.hour = 7
components.minute = 0
for timeZoneIdentifier in TimeZone.knownTimeZoneIdentifiers {
calendar.timeZone = TimeZone(identifier: timeZoneIdentifier)!
let date = calendar.date(from: components as DateComponents)!
let dateString = "\(date)"
if dateString == "0001-01-01 11:56:02 +0000" {
print("\(timeZoneIdentifier) \(date)")
}
}
// Only one line of output: America/New_York 0001-01-01 11:56:02 +0000
So why the weird minutes and seconds in UTC? Because at noon on November 18, 1883, the US and Canada railway companies began using a new, standard time system, which is the time system we still use today. Observe:
import Foundation
let formatter = ISO8601DateFormatter()
var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "America/New_York")!
let components = NSDateComponents()
components.year = 1883
components.month = 11
components.day = 18
components.hour = 11
print(formatter.string(from: calendar.date(from: components as DateComponents)!))
// prints 1883-11-18T15:56:02Z
components.hour = 12
print(formatter.string(from: calendar.date(from: components as DateComponents)!))
// prints 1883-11-18T17:00:00Z
Prior to noon, the difference from UTC time is 4:56:02.
Before the advent of standard railway time, we typically defined local time based on local apparent noon (the moment when the sun is highest in the sky and shadows point exactly north or south or disappear entirely if the sun is directly overhead).
If we look at the definition of America/New_York in the tz Time Zone Database, we find this:
# From Paul Eggert (2014-09-06):
# Monthly Notices of the Royal Astronomical Society 44, 4 (1884-02-08), 208
# says that New York City Hall time was 3 minutes 58.4 seconds fast of
# Eastern time (i.e., -4:56:01.6) just before the 1883 switch. Round to the
# nearest second.
and a bit further down:
Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
This explains the difference we see above prior to noon on November 18, 1883.

Swift Calendar class returning wrong date for weekday

I have a question regarding the Calendar class in Swift 3:
Basically, I wanted to get the Date of the next Sunday. However, the following code gives me the wrong output:
let cal = Calendar.current //gregorian
let today = Date(timeIntervalSinceNow: 0)
let date_c = DateComponents(calendar: cal, weekday: 1)
let today_weekday = cal.component(Calendar.Component.weekday, from: today)
let next_sunday = cal.nextDate(after: today, matching: date_c, matchingPolicy: Calendar.MatchingPolicy.nextTime)!
print(today_weekday) //prints out: 1 (today is Sunday, that's true)
print(next_sunday) //prints out: 2017-10-28 which is a Saturday
Please note that I tested the code yesterday, that's why I wrote "today is Sunday".
I was expecting the second print output to be the 2017-10-29 (next Sunday) however, it is giving me Saturday. What am I doing wrong?
Dates are always printed as if they are in the GMT timezone.
Suppose your time zone is UTC+1, then next Sunday will be 29-10-2017 00:00:00 in your time zone. However, this Date instance is expressed as 28-10-2017 23:00:00 in GMT.
So nextDate does return the correct date, it's just not formatted in your time zone. To fix this, just use a DateFormatter:
let formatter = DateFormatter()
formatter.dateStyle = .short
print(formatter.string(from: next_sunday))

Extract date components from a NSDate ignoring local time and time zones

NSDate() gives me good Universal Time (NOT local time) which is perfect for my purpose. I need to extract date components (Hour, Minute etc) from this NSDate but below code converts the time to local time. How can I extract the given universal date's components without altering them to local time zone:
let date = NSDate()
print(date) // Prints 2016-10-20 20:10:20 +0000 (Good Universal Time)
let calendar = Calendar.current
let hour = calendar.component(.hour, from date as Date)
print(hour) // Prints 16 (Bad local hour)
You have to set the timezone on the calendar.
let date = NSDate()
print(date) // Prints 2016-10-20 20:10:20 +0000 (Good Universal Time)
var calendar = Calendar.current
calendar.timeZone = TimeZone(abbreviation: "UTC")!
let hour = calendar.component(.hour, from: date as Date)
print(hour) // Prints 20

Date output is different.So,can't compare correctly

Here is my code.Please take a look first
import UIKit
var expireDate : Double = 1472647093
var date = NSDate(timeIntervalSince1970: expireDate)
print("Date : \(date)")
let current_date = NSDate()
if date.compare(current_date) == NSComparisonResult.OrderedDescending{
print("is Greater than current date")
}else if date.compare(current_date) == NSComparisonResult.OrderedAscending{
print("is Less than current date")
}else{
print("Same")
}
Here is the output on playground :
I really don't know why "Date : " output is different than date variable.My server send me an expire date format in unix timestamp which is long format.And I really need to compare it with current date.Actually
Aug 31, 2016, 7:08 PM // It has correct day/month/year but incorrect time
2016-08-31 12:38:13 +0000\n // has all correct time and date that the server send which was 12:38PM
So,why I am having greater than current date?And why it was 7:08PM instead of 12:38PM
Any help?
UPDATE :
You don't have wrong dates since Aug 31, 2016, 7:08 PM is your current timestamp, whereas in the print you have with timezone UTC. When you compare the dates they are both in UTC so you don't have any problems.
My above question has nothing wrong with it. I'm just misunderstood with the timezone or standard that I am dealing with. The server already send me expire date with my timezone locale in UTC standard format.
So,I don't need to change that expire date according to my current time zone.
All I have to do is get my current time,change it to UTC format using my current timezone. And compare server expire_date and current_date. Mission Accomplish.
// Using Expire Date
var expireDate : NSTimeInterval = 1472689452 // 00:24 AM UTC
var date = NSDate(timeIntervalSince1970: expireDate) // Since January 1 1970 Convert to NSDate
// Then I change this time format to UTC String without adding any timezone because its already included
var df_utc = NSDateFormatter()
df_utc.timeZone = NSTimeZone(name: "UTC")
df_utc.dateFormat = "yyyy.MM.dd G 'at' HH:mm:ss zzz"
// Get the expire date string by formatting it in UTC
var ts_utc_string : NSString = df_utc.stringFromDate(date)
// Getting current time of mine
var local_date = NSDate()
var df_local = NSDateFormatter()
df_local.timeZone = NSTimeZone(name: "MMT") // then use my locale to add that current time UTC [MMT +6:30]
df_local.dateFormat = "yyyy.MM.dd G 'at' HH:mm:ss zzz"
var ts_local_string : NSString = df_local.stringFromDate(local_date)
// Compare
if ts_local_string.compare(ts_utc_string as String) == NSComparisonResult.OrderedDescending{
print("CurrentTime is Greater than Expire Time")
}else if ts_local_string.compare(ts_utc_string as String) == NSComparisonResult.OrderedAscending{
print("CurrentTime is Less than Expire Time")
}else{
print("Same")
}
Here is the output :
For those who are still seeking for such problem like me, UTC was just a standard for timezone. If you need to compare it with your timezone, you just need to change your current time according to your country timezone.