Date format Convertion issue in swift - ios

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.

Related

Swift Codable with Custom JSONDecoder with Custom dateDecodingStrategy failing when Device is not using 24 hours format

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.

Remove time component from TimeInterval in Swift?

I have a time component coming from a Json. I am trying to group objects according to their time posted.
I have a time interval like 1540432146 which is a double.
I am converting it to Date type using
guard let timeInMilliseconds = lastUpdateOn as? Double else {return NSDate()}
let timeInSeconds = timeInMilliseconds / 100000
print(timeInSeconds)
// get the Date
let dateTime = NSDate(timeIntervalSince1970: timeInSeconds)
Hence I am getting 2018-10-25 10:54:20.325076+0900 as the output.
Is it possible to remove the Time component of this and again converting the Date type back to TimeIntervalSincel1970 as I will using it later to sort my elements in an array and it would be easier to compare Double
func getTime(lastUpdateOn:Any) -> NSDate{
guard let timeInMilliseconds = lastUpdateOn as? Double else {return NSDate()}
let timeInSeconds = timeInMilliseconds / 100000
// get the Date
let dateTime = NSDate(timeIntervalSince1970: timeInSeconds)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
let datenew = dateFormatter.string(from: dateTime as Date)
let newDate = dateFormatter.date(from: datenew)
print(newDate)
return dateTime
}
This is the function I tried but it returns value (1970-06-27 15:00:00 +0000) always. What am I doing wrong ?
If your only goal is to compare different dates to see which one comes first, simply convert the Int Unix Timestamps from your JSON to Date objects with:
let aDate = Date(timeIntervalSince1970: Double(timestamp)/1000.0)
You can compare Date objects directly using >, =, <, etc, and the result will compare the dates down to the millisecond.
You can also compare your Unix timestamp values directly and the results will be identical to comparing timestamps that you've converted to Date objects.
If you want to compare Date objects just by month/day/year, its more complicated.
A Cocoa Date ALWAYS represents an instant in time, all over the world. Internally, it's represented as a number of seconds since the iOS "epoch date" (midnight on January 1st, 2001 in UTC) Unix uses a different epoch date, but using "timeIntervalSince1970" and it's variants lets you work with Cocoa Date objects and Unix epoch dates.
There is no such thing as a Date "without a time component". There are various ways you could "normalize" your dates: You can convert a date to a date string that only has the month/day/year; you can force all Date values to exactly midnight on their month/day/year in a given timezone, or you could extract month/day/year DateComponents from your Date. (Search on "DateComponents" in the Xcode help system, and also search for "DateComponents" in the Calendar class reference, as many of the functions that let you do calculations on Dates and DateComponents are provided by the Calendar class.)
Note that the day/month/year that a Unix numeric timestamp falls on is often different depending on your time zone. Right now it's 22:47 EDT on 24 October here in the DC suburbs. I just computed the Unix timestamp for the current time and got 1540435644000. In Japan, 1540435644000 is 25 October. (From my perspective, it's already tomorrow in Japan). So 2 Unix timestamps might be on the same day/month/year in Japan but on different day/month/years where I live, or visa-versa.

Convert UTC NSDate into Local NSDate not working

I have a string getting from server as "yyyy-MM-dd'T'HH:mm:ss.SSS'Z"
I convert this string into NSDate by this formate.
class func convertUTCDateToLocateDate(dateStr:String) -> NSDate{
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateFormatter.timeZone = NSTimeZone(name: "UTC")
let date = dateFormatter.dateFromString(dateStr)
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
let timeStamp = dateFormatter.stringFromDate(date!)
let dateForm = NSDateFormatter()
dateForm.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateForm.timeZone = NSTimeZone.localTimeZone()
let dateObj = dateForm.dateFromString(timeStamp)
return dateObj!
}
Suppose the parameter string is "2016-11-05T12:00:00.000Z" but when i convert this string and return a NSDate object it doesn't change the time according to my local time. I get my correct time in the timeStamp string (in above code). But when i try to convert that timeStamp string into NSDate it again shows that date and time which i got as a parameter.
You shouldnt change a NSDate's time. NSDates are just a point in time, counted by seconds. They have no clue about timezones, days, month, years, hours, minutes, seconds,… If printed directly they will always output the time in UTC.
If you change the date to show you the time of your timezone you are actually altering the time in UTC — hence your date becomes representing another point in time, no matter of the timezone.
Keep them intact by not altering them, instead when you need to display them do it via a date formatter.
If you need to do time calculations that are independent of timezones you also can work with NSDateComponents instead.
NSDate doesn't have a timezone. It's a point in time, independent of anything, especially timezones. You cannot "convert a UTC NSDate to a local NSDate", the statement itself doesn't make any sense.

NSDateFormatter decreases the day of date

I need to store Date variable in CoreData in iOS
I need to store the Date only without the Time, So I made a formatter that discard the time partition from the NSDate variable.
But I have a strange result:
This is my code:
let dateStr = "2016-02-14 11:27:01"
let df2 = NSDateFormatter()
df2.timeZone = NSTimeZone.defaultTimeZone()
df2.dateFormat = "yyyy-MM-dd HH:mm:ss"
print(dateStr)
if let date = df2.dateFromString(dateStr) {
df2.dateFormat = "yyyy-MM-dd"
print("-> \(df2.dateFromString(df2.stringFromDate(date)))")
}
and this is the output:
2016-02-14 11:27:01
-> Optional(2016-02-13 20:00:00 +0000)
Why does the formatter decrease the day by one ?
I tried many dates with same issue
Your time zone is obviously UTC+4.
To get UTC set the time zone accordingly.
df2.timeZone = NSTimeZone(forSecondsFromGMT: 0)
But although you see a date 4 hours ago the NSDate object is treated correctly depending on your time zone. The print command displays always UTC ignoring the time zone information, because NSDate is just a wrapper for a Double number.

NSDate and string without timezone information

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)
}
}
}
}

Resources