Swift - Dates from server keep getting converted into my local timezone - swift

I am parsing JSON data from a server, which I can set to 'UK' or 'Asia'
Function:
func dateToStringConverter(jsonDate: String) -> String {
let isoDate = jsonDate
let dateFormatter = ISO8601DateFormatter()
let processedDate = dateFormatter.date(from:isoDate)!
return processedDate.asString()}
Extension:
extension Date {
func asString() -> String {
let template = "EEEEd MMMM, h:mm a"
let formatter = DateFormatter()
let format = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: NSLocale.current)
formatter.dateFormat = format
return formatter.string(from: self)}}
print(dateToStringConverter(jsonDate: jsonDate))
Then, the following attribute from the server differs based on if I set 'UK or 'Asia':
let jsonDate = "2020-10-05T02:15:00+08:00" // server set to Asia
let jsonDate = "2020-10-04T19:15:00+01:00" // server set to UK
However, BOTH these values result in the SAME OUTPUT from the print statement!
Sunday, October 4, 7:15 PM
I believe what is happening here is, even if I set the server to Asia, Swift knows I am in the UK, and therefore prints Sunday, October 4, 7:15 PM
Is this correct?
If so, is there any way to mimic what print statement from Asia would see? I have tried changing the region in the simulator, but this didn't seem to make a difference
Thanks

The good thing about zoned ISO 8601 timestamps is that they represent a specific moment in UTC time. They do not contain information about which time zone they're from, as this is highly ambiguous. If you aim for easier human comparability, understandability and a reduction in ambiguity, I suggest going for UTC time.
In stead of
2020-01-01T02:00:00+01:00
you'd have
2020-01-01T01:00:00Z
Z is a shortcut for +00:00
When it ends in Z, it's always UTC time and thus easily comparable.

Related

DateFormatter returns previous day

I've looked around and people have had problems with different years, random and changing results, and nil dates, but nothing like what I have, so I am asking here. Note I am in Playground right now.
I am taking strings in the format of "yyyy-mm-dd" and converting them to a different date format. Here is the code:
let example = "2001-11-03"
let dateFormatterInput = ISO8601DateFormatter()
dateFormatterInput.formatOptions = [.withFullDate, .withDashSeparatorInDate]
let date = dateFormatterInput.date(from: example)
let dateFormatterOutput = DateFormatter()
dateFormatterOutput.dateFormat = "MMMM dd, yyyy"
let output = dateFormatterOutput.string(from: date!)
The sidebar in Playground shows that the first reference to the previous day's date happens on the let date line. Also, this behavior happens on every date I've tried. In this example, it returns "November 2, 2001." I've tried different months, days, and years (1900s and 2000s) and it gives me the same result every time.
What am I doing wrong?
The key thing here is that ISO8601DateFormatter by default thinks that the time zone of your date string is GMT:
ISO8601DateFormatter.timeZone:
The time zone used to create and parse date representations. When unspecified, GMT is used.
However, the timeZone of DateFormatter by default (and also the side bar of the playground) assumes your device's local time zone:
DateFormatter.timeZone
The time zone for the receiver. If unspecified, the system time zone is used.
If your system time zone has a negative UTC offset on the start of the day 2001-11-03 UTC, then when seen from your time zone, that moment is actually in the day 2001-11-02. Hence the output you see.
Assuming you don't care about the actual value of date, and just care about the final string output, you can just set the timeZone of DateFormatter to GMT:
dateFormatterOutput.timeZone = TimeZone(identifier: "GMT")
Side note: You should also set locale when using a fixed format to avoid localisation issues:
dateFormatterOutput.locale = Locale(identifier: "en_US_POSIX")
Or better, just use one of the built-in, locale sensitive formats instead:
dateFormatterOutput.dateStyle = .medium
dateFormatterOutput.timeStyle = .none

DateFormatter giving me incorrect result while I am converting String into Date in UK region and Timezone is Muscat

I want "yyyy-MM-dd HH:mm:ss", with time in 24 hour format, but as i m having 12 hrs date format setting in my phone and Timezone is set to the Muscat ,the date which I am getting is always 12 hrs format, while i m checking with UK region. I am able to change Date() into string in 24 hrs format but while I am changing 24 hrs string into 24 hrs Date , It always give me 12 hrs Date format
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let dateToString = dateFormatter.string(from: Date()) //2020-04-06 15:47:16
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let dateFromString = dateFormatter.date(from: dateToString)
print(dateFromString) // 2020-04-06 1:47:25 pm +0000
A Date object just represents a point in time. The “should I show it in 12 hour clock or 24 hour clock” is not something that Date objects know about. This is a feature of strings generated by (or consumed by) a DateFormatter.
So, a few thoughts:
A date formatter’s dateFormat (and its locale) dictates what a string will look like when you call string(from:). (It will also dictate how to interpret a string and create a Date object when you call date(from:), but that’s not relevant here.)
So, if you’re looking for a string representation of a date using a 24 hour clock, look at the string generated by the date formatter’s string(from:) method. This is the dateToString string in your example.
But, if you subsequently generate a Date object from the formatter’s date(from:) method, that resulting Date will not capture whether to use 12 vs 24 hour clock. If you print this Date object, it won’t reflect your 12/24 hour clock preference.
Bottom line, only concern yourself with am/pm vs 24-hour clock when looking at String objects generated by (or passed to) the DateFormatter. Don’t worry about the format of the output when you print a Date object, as that’s for debugging purposes only and won’t capture this am/pm vs 24-hour clock dimension.
You said:
Final statement is giving me “2020-04-09 4:23:27 am +0000” format.
If you’re seeing the +0000, that suggests that you are printing the Date object, itself. The dateFormat and locale of a DateFormatter only controls the format of the string generated by string(from:) (and how strings are parsed).
So, print the string generated by DateFormatter, not Date objects. The print of a Date object will always be in this predefined format. Within the app, if you need the output in a given format, use the String generated by DateFormatter, not Date objects.
Consider a more obvious example of the issue where the DateFormatter is used to create a string in a very different format:
let now = Date()
print(now) // 2020-04-07 11:54:58 +0000
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .long
let string = formatter.string(from: Date())
print(string) // April 7, 2020 at 4:54:58 AM PDT
if let date = formatter.date(from: string) {
print(date) // 2020-04-07 11:54:58 +0000
}
So, even though that formatter successfully converted a Date to a String, and back, that final print statement uses the same fixed format that the first print statement did, because I’m just printing a Date object. I’m not concerned that the that last print statement didn’t honor the configuration of my DateFormatter. I wouldn’t expect it to. When I print a Date, it’s always in that fixed, predefined format. I only worry about the format of the strings generated by the DateFormatter (the second print statement).
As an aside, there are secondary questions about your "yyyy-MM-dd HH:mm:ss" format. What timezone does this represent? Your local timezone? Your server’s timezone? GMT/UTC/Zulu? We often use ISO8601/RFC3339 date strings (like 2020-04-07T11:54:58Z) to remove this ambiguity. You should get your arms around your original question first, but when you have that behind you, you’ll want to take a hard look at why you’re storing it in this format, and how you want to deal with timezones correctly. But first things first.

Issue Converting String to User Local Time using DateFormatter() Swift 4

I am trying to convert a string to a Date type. I am trying to convert to users local time, but I am getting a strange output. Here is the function that I am using and the results I am getting. The string input I am using looks like "10:15 AM:
The function below is returning a string, but that is just to look at the output, it will eventually return a Date.
func convertTime(date: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a"
dateFormatter.locale = Locale.current
print(Locale.current.identifier)
print(TimeZone.current.identifier)
dateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
if let formattedTime = dateFormatter.date(from: date) {
let userFormatter = DateFormatter()
userFormatter.timeStyle = .short // Set as desired
return userFormatter.string(from: formattedTime)
} else {
return "Unknown date"
}
}
the first part of the output is the input string, and the second part is the converted time. I expected to get the converted time in the America/Denver timezone, but tht is not the case.
en_US
America/Denver
Input String: 10:58 AM, Output String: 3:58 AM
en_US
America/Denver
Input String: 7:43 PM, Output String: 12:43 PM
en_US
America/Denver
Input String: 12:14 PM, Output String: 5:14 AM
Your output is correct for the code you posted. You treat the string "10:58 AM" as UTC time in the year 1. This is because the time string has no date and because you specific set the UTC timezone on the date formatter.
And you get the output of "3:58 AM" in local time (which is Denver for you). Denver is UTC-7 normally (UTC-6 during daylight saving time).
Your code is working correctly if your goal is to convert a UTC time to user's local time (Denver in this case).
If you want to treat the original string as local time and your goal is to just change the format, don't set the timeZone on the firs date formatter.
Unrelated but there's no need to use the line:
dateFormatter.locale = Locale.current
since the date formatter defaults to the current locale.

How to parse date of this format [duplicate]

so in my app, I need to deal with date like \/Date(1440156888750-0700)\/, i think the first part stands for the seconds from Jan 1st, 1970 and the second part is the timezone.
I don't know how to handle data like this and display it the way we all understand in Xcode 7 using Swift 2?
(The previous version of this answer was actually wrong, it did not handle the time zone correctly.)
According to Stand-Alone JSON Serialization:
DateTime values appear as JSON strings in the form of "/Date(700000+0500)/", where the first number (700000 in the example provided) is the number of milliseconds in the GMT time zone, regular (non-daylight savings) time since midnight, January 1, 1970. The number may be negative to represent earlier times. The part that consists of "+0500" in the example is optional and indicates that the time is of the Local kind - that is, should be converted to the local time zone on deserialization. If it is absent, the time is deserialized as Utc. The actual number ("0500" in this example) and its sign (+ or -) are ignored.
and Use JSON.NET to parse json date of format Date(epochTime-offset)
... In this [screwy format][1], the timestamp portion is still based solely on UTC. The offset is extra information. It doesn't change the timestamp. You can give a different offset, or omit it entirely and it's still the same moment in time.
the first number in \/Date(1440156888750-0700)\/ is the number of
milliseconds since the "epoch" January 1, 1970 GMT, and the time
zone part -0700 must simply be ignored.
Here is a Swift 5 extension method for Date which checks
the validity of the string with a regular expression
(accepting both \/Date(...)\/ and /Date(...)/, with or without
a time zone specification) and converts the given number of
milliseconds to a Date:
extension Date {
init?(jsonDate: String) {
let pattern = #"\\?/Date\((\d+)([+-]\d{4})?\)\\?/"#
let regex = try! NSRegularExpression(pattern: pattern)
guard let match = regex.firstMatch(in: jsonDate, range: NSRange(jsonDate.startIndex..., in: jsonDate)) else {
return nil
}
// Extract milliseconds:
let dateString = jsonDate[Range(match.range(at: 1), in: jsonDate)!]
// Convert to UNIX timestamp in seconds:
let timeStamp = Double(dateString)! / 1000.0
// Create Date from timestamp:
self.init(timeIntervalSince1970: timeStamp)
}
}
Example:
let jsonDate = "\\/Date(1440156888750-0700)\\/"
print("JSON Date:", jsonDate)
if let theDate = Date(jsonDate: jsonDate) {
print("Date:", theDate)
} else {
print("wrong format")
}
Output:
JSON Date: \/Date(1440156888750-0700)\/
Date: 2015-08-21 11:34:48 +0000
(Versions for Swift 3 and Swift 4 can be found in the edit history.)
After a bit of experimenting around I came up with the following solution:
let dx = "/Date(1440156888750-0700)/"
let timestamp = (dx as NSString).substringWithRange(NSRange(location: 6,length: 13))
let timezone = (dx as NSString).substringWithRange(NSRange(location: 19,length: 5))
let dateIntermediate = NSDate(timeIntervalSince1970: Double(timestamp)! / 1000)
let outp = NSDateFormatter()
outp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS"
outp.timeZone = NSTimeZone(forSecondsFromGMT: 0)
let input = outp.stringFromDate(dateIntermediate) + " " + timezone
let inp = NSDateFormatter()
inp.dateFormat = "dd.MM.yyyy hh:mm::ssSSS Z"
let finalDate = inp.dateFromString(input)
print(finalDate)
Let me explain:
we extract the millisecond timestamp and the timezone from the original string
we create a date from the timestamp to be able to split it into its different components
we output that date in a more standard way (not as timestamp) and append the previously extracted timezone to that string
we then read that string and parse a date from it again
Note
As #Phoen1xUK mentioned the timestamp might have a different length than 13 digits. You can handle that situation by stripping the /Date( and )/ and then splitting the string before the - (or +).

NSDate set timezone in swift

how can i return a NSDate in a predefined time zone from a string
let responseString = "2015-8-17 GMT+05:30"
var dFormatter = NSDateFormatter()
dFormatter.dateFormat = "yyyy-M-dd ZZZZ"
var serverTime = dFormatter.dateFromString(responseString)
println("NSDate : \(serverTime!)")
the above code returns the time as
2015-08-16 18:30:00 +0000
The date format has to be assigned to the dateFormat property of the date formatter instead.
let date = NSDate.date()
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let str = dateFormatter.stringFromDate(date)
println(str)
This prints the date using the default time zone on the device. Only if you want the output according to a different time zone then you would add for example
Swift 3.*
dateFormatter.timeZone = NSTimeZone(name: "UTC")
Swift 4.*
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
also refer link http://www.brianjcoleman.com/tutorial-nsdate-in-swift/
how can i return a NSDate in a predefined time zone?
You can't.
An instance of NSDate does not carry any information about timezone or calendar. It just simply identifies one point in universal time.
You can interpret this NSDate object in whatever calendar you want. Swift's string interpolation (the last line of your example code) uses an NSDateFormatter that uses UTC (that's the "+0000" in the output).
If you want the NSDate's value as a string in the current user's calendar you have to explicitly set up a date formatter for that.
Swift 4.0
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
If you always have the same time zone for the input string, you can create two date formatters to output the local time zone (or a specified one):
let timeFormatterGet = DateFormatter()
timeFormatterGet.dateFormat = "h:mm a"
timeFormatterGet.timeZone = TimeZone(abbreviation: "PST")
let timeFormatterPrint = DateFormatter()
timeFormatterPrint.dateFormat = "h:mm a"
// timeFormatterPrint.timeZone = TimeZone(abbreviation: "EST") // if you want to specify timezone for output, otherwise leave this line blank and it will default to devices timezone
if let date = timeFormatterGet.date(from: "3:30 PM") {
print(timeFormatterPrint.string(from: date)). // "6:30 PM" if device in EST
} else {
print("There was an error decoding the string")
}
The number 1 means 1 regardless of language. Yet in English it's spelled as one, in Spanish it's una, in Arabic it wahid, etc.
Similarly 123982373 seconds pass 1970 is going to reflect differently in different timezones or calendar formats, but's all still 123982373 seconds passed 1970
The difference between 3 seconds and 7 seconds is 4 seconds. That doesn't require a calendar. Neither you need a calendar/timezone to know the difference in time between these two Epoch times 1585420200 and 1584729000
Dates are just a timeInterval from January 1, 1970 (midnight UTC/GMT). Dates also happen to have a string representation.
Repeating Nikolia's answer, Swift's default string interpolation (2015-08-16 18:30:00 +0000) uses a DateFormatter that uses UTC (that's the "+0000" in the output).
Calendars with the use of timezones give us a contextual representation that is just easier to understand than trying to calculate the difference between two gigantic numbers.
Meaning a single date (think of a single timeInterval since 1970) will have a different string interpretations per calendar. On top of that a calendar will itself vary based on time zones
I highly recommend that you go and play around with this Epoch converter site and see how selecting a different timezone will cause the string representations for the same moment/date/timeInterval to change
I also recommend to see this answer. Mainly this part:
Timezone is just an amendment to the timestamp string, it's not considered by the date formatter.
To consider the time zone you have to set the timeZone of the formatter
dateFormatter.timeZone = TimeZone(secondsFromGMT: -14400)