Where the time coming from when converting date string without any time in to Date() in swift? - swift

I'm using this extension to convert a string containing date to Date() object:
extension String {
func toDate() -> Date?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/YYYY"
let date = dateFormatter.date(from: self)
return date
}
}
the result always containing a time in it. I'm curious where is the time coming from, why it is not all zero?
print("11/12/2021".toDate())
result is ->
2020-12-19 21:00:00 +0000
In the time that I run the code, it is showing 21:00:00, so why it is 21? I believe It is not related to my time because I run it at different times.

A Date object indicates an instant in time anywhere on the planet, independent of time zone.
A DateFormatter can convert a String to a Date (or a Date to a String, but ignore that for now). When it converts a String to a Date, it may make assumptions about the time of day if that is not included in the String. I believe it assumes that the time is midnight (00:00:00) in the date formatter's time zone. (And by the way, midnight is the starting point of a day, so midnight is zero hours/minutes/seconds into the day. Essentially midnight IS zeros for time.)
So when you call your String extension to convert "11/12/2021" to a Date, the extension creates a DateFormatter which defaults to the device time zone. It creates a Date assuming Midnight in the local time zone.
When you print that date, it gets displayed in GMT.
It looks like your format string has problems though. You're getting the wrong year and month. I think you must be using the wrong month or day string in your formatter. (I always have to look those up when I use them.)
Edit:
You likely want a format string of "MM-dd-yyyy"
(2-digit month, 2-digit day of month, and 4-digit year.)
Lower-case "m" or "mm" is minutes. Upper-case "Y" is for "week of year" based calendars, which you probably don't want.
Try this code:
func toDate() -> Date?{
let dateFormatter = DateFormatter()
let posixLocale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.locale = posixLocale
let date = dateFormatter.date(from: self)
return date
}
}
And to use it:
let dateString = "12/11/2021"
let date = dateString.toDate()
print(date)
if let date = date {
let convertedDateString = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .medium)
print(convertedDateString)
} else {
print("Can't convert \(dateString) to a date")
}
That displays "Dec 11, 2021 at 12:00:00 AM" in my US locale (US Date formatting.) Note that since I use the DateFormatter class method localizedString(from:dateStyle:timeStyle:) I see midnight as the displayed time (The time you get from a DateFormatter when you don't specify a time, but displayed in the local time zone.)

The answer is:
when we are converting a string to a Date Object the important part is the time zone that we are converting it to.
for example, if you convert your string date to a UTC time zone when you want to bring it back you have to set the time zone of the date to UTC.
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: "UTC")
so this is the reason why when we are printing the Date() object it is deferred from our string date.
extension String {
func toDate() -> Date?{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm-dd-yyyy"
let date = dateFormatter.date(from: self)
return date
}
}
extension Date {
func toString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "mm-dd-yyyy"
return dateFormatter.string(from: self)
}
}
let stringDate = "01-12-2021"
let date = "01-12-2021".toDate()
let convertBack = date?.toString()
print("(\(stringDate)) -> (\(date!)) -> (\(convertBack!))")
and the result is:
(01-12-2021) -> (2021-01-11 21:01:00 +0000) -> (01-12-2021)
so at the end when we convert back the Date object it will be the same. because that 2 dateFormatter in the extensions are using the default time zone. and if you want to specify a specific time zone you have to declare it in converting from and to string together.

Related

DateFormatter date from string returns nil when iPhone Date & Time 24-Hour Time is off [duplicate]

This question already has an answer here:
DateFormatter doesn't return date for "HH:mm:ss"
(1 answer)
Closed 2 years ago.
I am working on an app that initializes dates from strings returned from the backend. The dateString is returned using the following format: "2020-03-05T09:00:00+00:00"
The method I have to do the conversion is:
extension Date {
static func convertDate(_ dateString: String?) -> Date? {
guard let dateString = dateString else { return nil }
let dateFormatter = DateFormatter()
dateFormatter.setLocalizedDateFormatFromTemplate("yyyy-MM-dd'T'HH:mm:ssZZZZZ")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return dateFormatter.date(from: dateString)
}
}
Everything was working fine until someone reported that if the user switches off "24-Hour Time" in settings the method above returns nil.
What am I doing wrong?
Thank you
You're using a very standardized timestamp format, which allows you to take advantage of the ISO8601DateFormatter.
let dateString = "2020-03-05T09:00:00+00:00"
let df = ISO8601DateFormatter()
df.formatOptions = [.withInternetDateTime]
if let date = df.date(from: dateString) {
print(date) // 2020-03-05 09:00:00 +0000
}
If a machine (like your server) is generating the timestamp then it will (should) always be in zulu time (GMT) so you don't need to do anything beyond this. You could specify a time zone but there isn't a point since the string will always zero it out for you.
df.timeZone = TimeZone(secondsFromGMT: 0)
This string represents an absolute moment in time. If you need a relative moment in time, such as the local time from the source, you'll need to identify that time zone and apply it here, which is also very straighforward.

Swift - date formatter returns unwanted time

If I convert "2019-01-01T00:00:00+0000" to a date, I would like the date to be the date in the string - January 1, 2019. And if I calculate the number of seconds in the time portion, I would like the result to be zero.
The problem is, when I convert my string into a date, it is stored in the UTC timezone. My locale is set to "en_US_POSIX", and my time zone is set to current. My date formatter uses "yyyy-MM-dd'T'HH:mm:ssZ". I know that the final Z means UTC, but I can't seem to figure out the correct field symbol to get the results I want.
func convertToDate(_ dateString: String) -> Date {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = .current
let date = dateFormatter.date(from: dateString)
else {
print("DATE ERROR:", dateString)
return Date()
}
return date
}
If you know for sure that the date strings you wish to parse will always end with a timezone in the form +XXXX then you can trim off that timezone from the string and then parse the remaining string as local time.
func convertToDate(_ dateString: String) -> Date? {
let minusTZ = String(dateString.dropLast(5)) // Assume the string ends with a +9999 timezone
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
let date = dateFormatter.date(from: minusTZ)
return date
}
This will give you a local date with the same date and time in the original string regardless of the timezone of the original string.

Swift 4.1 couldn't not convert String to local Date, always return UTC Date

I want to transfer a date string to Date.
let a = DateFormatter()
a.dateFormat = "yyyy-MM-dd HH:mm:ss"
guard let datea = a.date(from: "2018-06-21 00:00:00") else {
fatalError("ERROR: Date conversion failed due to mismatched format.")
}
print("ans", datea)
But it always print "ans 2018-06-20 16:00:00 +0000"
Why it could not print the original string date "2018-06-21 00:00:00"?
What wrong with my code ?
A Date is not a string. A Date is a moment in time. It has no clock. It has no time zones. It has no calendar. It is just an instant in time, independent of location or localization.
As a debugging convenience, a Date can be easily converted to a string in a pre-defined format using its .description (which is what print calls). As with all .description methods, you should never use this string for anything but debugging (or possibly logging). There is no promise about what format this string is in.
If you need some specific string representation, then you should use the DateFormatter:
print("ans", a.string(from: datea))
You need to provide timeZone to get the time according to that provided timeZone so to convert UTC time to local time your code should be look like that.
let a = DateFormatter()
a.dateFormat = "yyyy-MM-dd HH:mm:ss"
a.timeZone = TimeZone(abbreviation: "UTC")
let dt = a.date(from: "2018-06-21 00:00:00")
a.timeZone = TimeZone.current
a.dateFormat = "yyyy-MM-dd HH:mm:ss" //change the dateFormat according to your need
let dateString = a.string(from: dt!)
print("now the dateString is \(dateString)")
//printed result (now the dateString is 2018-06-21 05:30:00 )
As Rob Napier says in his answer, a Date object does not have a time zone. It represents a moment in time anywhere on the planet.
If you want to display a Date as a month, day, year, and time, you need to specify a particular time zone.
If you just print a date, like print(Date()), you get the default description property of the date object, which shows the date expressed in UTC. That's probably not what you want.
I defined an extension to Date that lets me see dates expressed in the user's current locale and time zone:
extension Date {
func localString(dateStyle: DateFormatter.Style = .medium, timeStyle: DateFormatter.Style = .medium) -> String {
return DateFormatter.localizedString(from: self, dateStyle: dateStyle, timeStyle: timeStyle)
}
func timeString(timeStyle: DateFormatter.Style = .medium) -> String {
return localString(dateStyle: .none, timeStyle: timeStyle)
}
}
If you add that extension to your project you can use it like this:
print(print("ans", datea.localString())
And you'll see your Date in the device's current time zone. It's very useful for debugging.

Need simple way to compare a time string ONLY to the current dates time value

Say time string value is "7:00 AM" call it reminder time.
Now all I need to do is compare this time with the current dates time say its "9:00 AM" if reminder time is later than current time - return true else false. This is the format "h:mm a" for date formatters.
Simple right? It should be but I have burned too much time on this. I can get hour and minute values but when the AM/PM is considered it gets harder.
I just want to compare two time values and determine if the first is later or after the second one. The date is always today or current date so I only care about the time part of the date. Of course you have to convert to dates to do the comparison but current date is easy to get however date from "7:00 AM" string does not seem to work right in comparisons.
Anyone have a function to do this?
Thanks.
the approach would be lets date the Date() object from your current time object so you will get
default date + your time = 2000-01-01 00:00:00 +your time (7.00 AM or 9.00 PM)
now we will get the current time from today only, in same format. (Only time)
it will be something like 3.56 PM
now again we will convert this 3.56 PM to Date() with default date as prev. so now we will have two date time object with same Date(2000-01-01) and respective times.
2000-01-01 7:00:00 => this will your 7.00 AM with default date
2000-01-01 15:56:00 => this will be current time with default date
now we will compare two date object.
Check the fiddle Fiddle
func CompareMyTimeInString(myTime:String)->Bool
{
// create the formatter - we are expecting only "hh:mm a" format
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "hh:mm a"
dateFormatter.locale = Locale.init(identifier: "en_GB")
// default date with my time
var dt_MyTime = dateFormatter.date(from: yourTime)!
// current time in same format as string "hh:mm a"
var currentTimString = dateFormatter.string(from: Date());
print("Current Time is - "+currentTimString);
// current time with default date.
var dt_CurrentTime = dateFormatter.date(from: currentTimString)!
// now just compare two date objects :)
return dt_MyTime > dt_CurrentTime;
}
// then call it like
var yourTime = "7.00 AM"
var isDue = CompareMyTimeInString(myTime:yourTime);
print(isDue);
My solution was as follows.
private func ReminderAfterCurrentTime(reminderTimeString: String) -> Bool {
//Compare the two time strings and if reminderTimeString is later than current time string
//return true since Reminder is after current time.
//Get the current date and time
let currentDateTime = Date()
// Create calendar object
let calendar = NSCalendar.current
// Get current date hour and minute values for comparison.
let currentHourValue = Int(calendar.component(.hour, from: currentDateTime))
let currentMinuteValue = Int(calendar.component(.minute, from: currentDateTime))
//Now get a date from the time string passed in so we can get just the hours and minutes to compare
let dateformatter = DateFormatter()
dateformatter.dateStyle = DateFormatter.Style.none
dateformatter.timeStyle = DateFormatter.Style.short
//Now get the date using formatter.
let reminderDateTime = dateformatter.date(from: reminderTimeString)
print("reminderDateTime = \(reminderDateTime)")
//Get reminder hour and minute for comparison.
let reminderHourValue = Int(calendar.component(.hour, from: reminderDateTime!))
let reminderMinuteValue = Int(calendar.component(.minute, from: reminderDateTime!))
print("currentHourValue = \(currentHourValue)")
print("currentMinuteValue = \(currentMinuteValue)")
print("reminderHourValue = \(reminderHourValue)")
print("reminderMinuteValue = \(reminderMinuteValue)")
//This works due to 24 hour clock. Thus AM/PM is already taken into account.
if currentHourValue < reminderHourValue {
return true
}
//Check for same hour then use minutes
if currentHourValue == reminderHourValue {
if currentMinuteValue < reminderMinuteValue {
return true
}
}
//Otherwise return false
return false
}

How do I turn an arbitrary time zone into UTC?

I'm allowing my users to be able to select a random Date and time, using their local time zone. I want to be able to send this date string to the server in UTC Format so it can be read by anyone else around the world using their local time zone. I've read lots online on how to turn UTC to local time but not the other way around. How can I accomplish this?
Edit:
First I use the first function to take the concatenated string with a user selected date and time, turn it into a NSDate, and then I convert this NSDate into a UTC string. Is this the best method of achieving my goal?
public class func localTimeZoneStringToDate(string: String) -> NSDate {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
dateFormatter.timeZone = NSTimeZone.localTimeZone()
return dateFormatter.dateFromString(string)!
}
public class func UTCStringFromDate(date: NSDate) -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return dateFormatter.stringFromDate(date)
}
If you're using the UIDatePicker, getting the selected date and time in UTC is quite simple. The first line of code below will return the selected time in YYYY-MM-DD hh:mm:ss The second line of code will return the time interval in seconds since January 1, 1970 at 12:00am GMT.
// Returns Selecteed Date //
datePicker.date
// Returns Seconds Since Jan. 1, 1970 //
datePicker.date.timeIntervalSince1970
However, if you are not using the UIDatePicker, you can get the same information that the above code will return by simply using:
//** Variable "pastDate" must be NSDate **//
// Returns Selected Date //
pastDate
// Returns Seconds Since Jan.1, 1970 //
pastDate.timeIntervalSince1970
EDIT:
I think I understand what you're attempting to do now, and the following code should return the user's selected date in UTC form.
func UTCStringFromDate(date: NSDate) -> String {
// Get User's Time //
let calendar = NSCalendar.currentCalendar()
// Get User's TimeZone Difference //
let difference = calendar.timeZone.secondsFromGMT
// Get UTC Time //
let adjustedTime = calendar.dateByAddingUnit(.Second, value: -difference, toDate: date, options: [])!
// Format Date //
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return dateFormatter.stringFromDate(adjustedTime)
}