I am parsing some XML that is returned by a web service. The string: 2015-12-24T12:00:00 is found in the fromTimestamp with no timezone. The "geniuses" (I don't mean that) keeps the timezone information in a own field in the XML in minutes. So 300 means GMT+05:00.
I have been reading a lot about NSDate and all this timezone stuff and I know that NSDate don't care about timezones, thats NSDateFormatter job.
However, in order to "convert" the timestamp without the timezone so that the value in NSDate represents a GMT time I add the GMT+05:00 to the string so it becomes 2015-12-24T12:00:00 +05:00. This is how it must be done right? Without adding the timezone when you convert from string to date the NSDate thinks the value is the GMT time? Thats the part I don't understand about it. It would be wrong to just convert the string without that timezone information because NSDate wouldn't be able to subtract 5 hours from the value inside NSDate? Is that correct? I am having a hard time explaining it.
Your assessment is correct and your solution is one of two possible solutions.
To ensure the date string is properly converted to an NSDate, you can do one of two things:
You need to ensure the date string has timezone information included and the date formatter's format string includes the proper format specifier for the timezone. This is what you describe in your question.
You leave the date string as you have it, without timezone information. But you set a specific timezone on the date formatter based on the timezone field you get from the XML.
Either approach will give you the proper NSDate.
Update: My second approach is shown in the answer by Martin R.
It may be simpler to set the time zone of the date formatter
explicitly. Example (error checking omitted for brevity):
let fromTimestamp = "2015-12-24T12:00:00"
let timeZoneInfo = "300"
let fmt = NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
let secondsFromGMT = Int(timeZoneInfo)! * 60
fmt.timeZone = NSTimeZone(forSecondsFromGMT: secondsFromGMT)
let date = fmt.dateFromString(fromTimestamp)
I found it more convenient to wrap Martin's approach as an extension of the String class. It also prepends it with current timestamp and writes text ta a debugger output.
Swift 5:
"Hello world".log()
2019-09-05 12:18:10 Hello world
extension String {
func log() {
let fmt = DateFormatter()
fmt.dateFormat = "yyyy-MM-dd' 'HH:mm:ss"
let formattedString = "\(fmt.string(from: Date())) \(self)"
print(formattedString)
let log = URL(fileURLWithPath: "log.txt")
do {
let handle = try FileHandle(forWritingTo: log)
handle.seekToEndOfFile()
handle.write((formattedString+"\n").data(using: .utf8)!)
handle.closeFile()
} catch {
print(error.localizedDescription)
do {
try self.data(using: .utf8)?.write(to: log)
} catch {
print(error.localizedDescription)
}
}
}
}
Related
I am getting dates from server in below format
"endTime": "2022-12-12T16:20:00.000Z"
I am using Codable to parse json to Objects. I am using a custom decoder shown below
internal func getDecoder() -> JSONDecoder {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
decoder.dateDecodingStrategy = .formatted(dateFormatter)
return decoder
}
It works perfectly when device time format is set as 24 Hours format. But it returns null if device time is not set as 24 hours format. Is there any other property I have to set?
If you are using a custom JSONDecoder with a custom dateDecodingStrategy in Swift and you encounter issues with decoding dates when the device's time format is not set to 24-hour format, there are a few potential solutions.
One solution is to use the ISO8601DateFormatter class to parse the date string and convert it to a Date object. This class is designed to handle the different date and time formats defined in the ISO 8601 standard, so it is well-suited to handling the potential variations that can occur in date strings. Here is an example:
let dateFormatter = ISO8601DateFormatter()
// Set the timeZone property to the local time zone
dateFormatter.timeZone = TimeZone.current
// Parse the date string using the ISO8601DateFormatter
let date = dateFormatter.date(from: "2022-12-12T12:00:00")
Another solution is to use the DateFormatter class and explicitly set its dateFormat property to match the format of the date string you are trying to decode. This allows you to specify the exact format of the date string and ensures that the DateFormatter will be able to parse it correctly. Here is an example:
let dateFormatter = DateFormatter()
// Set the dateFormat property to match the format of the date string
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
// Set the timeZone property to the local time zone
dateFormatter.timeZone = TimeZone.current
// Parse the date string using the DateFormatter
let date = dateFormatter.date(from: "2022-12-12T12:00:00")
Once you have parsed the date string into a Date object, you can use this object when decoding the JSON data using your custom JSONDecoder and dateDecodingStrategy. This should allow you to properly decode the dates in the JSON data, regardless of the device's time format.
Note that in both of these examples, it is important to set the timeZone property of the DateFormatter or ISO8601DateFormatter to the local time zone. This ensures that the parsed Date objects will be correctly adjusted for the device's time zone, which is important for ensuring that the dates are decoded correctly.
Sometimes I am getting nil while converting date string to timestamp.
Here is my code:
class func createTimeStampFromDateString(dateString : String) -> Double? {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let convertedDate = dateFormatter.date(from: dateString)
let timeIntervalsince1970 = convertedDate?.timeIntervalSince1970
return timeIntervalsince1970
}
Could you please let me know what is wrong in this code.
Thanks in advance!
Based on your comments, the bug is not because of this function (it is working as expected). The bug (the crash) you're experiencing is because you're force unwrapping a date value that is nil, and that is because the string used to create that date isn't in the valid format. And this is the nature of working with dates and strings from sources outside of your app—the strings aren't guaranteed to be in the right format. And this is why your function, as it should be, returns an optional value.
By the way, you could simplify your method and take advantage of the ISO8601DateFormatter:
func timestamp(from string: String) -> Double? {
let df = ISO8601DateFormatter()
df.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return df.date(from: string)?.timeIntervalSince1970
}
And to use it properly, use it conditionally without force unwrapping:
if let interval = timestamp(from: "2020-06-02T13:38:31.814Z") {
print(interval)
}
To correct your bug:
Don't force unwrap when working with date/string conversions. If the string is ever not in the correct format, consider discarding the result, providing a default value, displaying something like "unknown"—anything but forcing the app to crash.
Determine why the string isn't in the right format and see if that's correctable.
When using the standard DateFormatter for an ISO8601 date, you should set the locale to en_US_POSIX (dateFormatter.locale = Locale(identifier: "en_US_POSIX")) before converting from string.
Also when converting a string to a date it can always happen that the string is in the wrong format (missing or wrong number/character). There are many things that can be wrong here.
But for ISO8601 dates there is a dedicated ISO8601DateFormatter. It can handle multiple variants of ISO8601 formatted dates. But it is only available since iOS 10.
I am not sure this has been answered before.
I need to know if its possible to handle two different timezones that are not +0000.
What I mean is that my iphone app calls data from the server such creation date and time of certain objects. But the dates and time is in danish time, meaning timezone the date was created in is something like +0100
But the iphone app thinks the initial timezone is +0000 of course, and if I use dateformatter to set the timezone, it adds an hour to the initial datetime which is incorrect.
Is there a sensible way to handle timezones in Swift, or will I have to subtract an hour manually each time I convert from danish time to whatever is on the phone of the user?
Ideally you need to ask the server side to use GMT +00 so that if they will want to work with some other country it will work fine for everyone..
Or, another way is to store time in Timestamp. And it will be automatically GMT +00
And then the system will handle it for you considering user's time zone on the phone.
Hope it helps
You need to ask Web Service developer if dates which are returning, are those in GMT or not? If not, then ask them to store dates in GMT only.
When api returns data, convert your date to your desired format.
I did this in swift 2.2. Created a String extension.
extension String {
func getFormattedDate(currentFormat : String,convertFormat : String) -> String {
let dateStr = self
let dateFormate = NSDateFormatter()
dateFormate.timeZone = NSTimeZone(abbreviation: "GMT")
dateFormate.dateFormat = currentFormat
if let date = dateFormate.dateFromString(dateStr) {
dateFormate.dateFormat = convertFormat
dateFormate.timeZone = NSTimeZone.localTimeZone()
return dateFormate.stringFromDate(date)
}
return ""
}
func getFormattedDateForDefaultTimeZone(currentFormat : String,convertFormat : String) -> String {
let dateStr = self
let dateFormate = NSDateFormatter()
dateFormate.timeZone = NSTimeZone.localTimeZone()
dateFormate.dateFormat = currentFormat
if let date = dateFormate.dateFromString(dateStr) {
dateFormate.dateFormat = convertFormat
dateFormate.timeZone = NSTimeZone.localTimeZone()
return dateFormate.stringFromDate(date)
}
return ""
}
}
Use like below:
let dateStr = btnDate.currentTitle!.getFormattedDate("dd/MMM/yyyy", convertFormat: "dd-MM-yyyy")
i could not convert string date into NSDate object.
Please check below code
let stringDate = "06:30 AM"
let formatter = NSDateFormatter()
formatter.dateFormat="hh:mm a"
let local = NSLocale(localeIdentifier: "en_US")
formatter.locale=local
let date = formatter.dateFromString(stringDate)
The output is as expected, and depending on what you're trying to achieve, you haven't really done anything wrong.
Your stringDate instance contains only information about a time of the day, not a date (the prior is also the only format your NSDateFormatter formatter is "interested" in). Hence, the following snippet produces the expected 06:30 AM output:
let stringDate = "06:30 AM"
let formatter = NSDateFormatter()
formatter.dateFormat="hh:mm a"
let local = NSLocale(localeIdentifier: "en_US")
formatter.locale=local
if let date = formatter.dateFromString(stringDate) {
print(formatter.stringFromDate(date)) // 06:30 AM
}
NSDate instances are defined, however, as single point in time (date and hour of the day), with reference to an absolute reference date:
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).
From the language reference for NSDate.
Hence, in addition to a time of day, NSDate instances include also a date (even if this is not, in your case, used or displayed). When you assign a value to date above, the Swift playground displays the time of day of the correct date; the latter offset by 06:30 from the absolute reference date, 2000-01-01 00:00:00. If we modify the example above to print all details in the final print statement, we see this more clearly:
// ...
if let date = formatter.dateFromString(stringDate) {
formatter.dateStyle = .FullStyle
formatter.timeStyle = .FullStyle
print(formatter.stringFromDate(date))
/* Saturday, January 1, 2000 at 6:30:00 AM Central European Standard Time */
}
(Addition with regard to your comments below)
Note the difference of printing the date object itself (e.g. print(date) above) and printing a ** formatted string representation** of the date using your formatter (e.g. print(formatter.stringFromDate(date))). The prior just prints the .description property of your date, which is an default-formatted string representation of the contents of object itself rather than a controlled formatted output of the date:
Declaration
var description: String { get }
Description
A string representation of the date object. (read-only)
The representation is useful for debugging only.
There are a number of options to acquire a formatted string for a date
including: date formatters (see NSDateFormatter and Data Formatting
Guide), and the NSDate methods descriptionWithLocale:,
dateWithCalendarFormat:timeZone:, and
descriptionWithCalendarFormat:timeZone:locale:
Refer to my code blocks above to see how you can print the formatted date using your NSFormatter.
I receive a string in Json and first of all I have to do is to convert it into NSDate. The problem is, none of string formats I used is valid. the code goes as follows:
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd hh:mm:ss"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
let timeString = formatter.stringFromDate(output)
as far as I know, if I want to retrieve hours from NSData, I have to call formatter once more
formatter.dateFormat = "hh"
and call it on NSDate obtained from string. Am I right?
My first question is: how to make the determine proper date format so the output will not be evaluated to nil? The second question is: Do I get it right or there is a simpler method or generally way to retrieve the hours from the following string: "2000-01-01T00:00:00.000Z" ? I know I can do it via dealing with mere string without involving dateFormatter and NSDate, but won't such solution be vulnerable? Please advice me what's the simplest(and robust) way to deal with this.
Thanks in advance
First of all, you have your formatter wrong...
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'hh:mm:ss.SSS'Z'"
var output = formatter.dateFromString("2000-01-01T00:00:00.000Z")
After that, you can get the hour component with
if let date = output {
var hours = NSCalendar.currentCalendar().component(.HourCalendarUnit, fromDate: date)
}