DateIntervalFormatter's dateTemplate not consistent with DateFormatter's dateFormat in Swift - ios

I am trying to display a date interval formatted using a custom format. The format which I am using works well for a date but do not so good when used with a date interval.
I pus as comments what is displayed and what I expect. Is there truly a problem or my expectations are wrong, in case they are wrong, why so? also, how can I achieve the expected part?
import UIKit
let now = Date()
let tomorrow = now.addingTimeInterval(24.0 * 3600.0)
let dateInterval = DateInterval(start: now, end: tomorrow)
// Initialize Date Interval Formatter
let dateIntervalFormatter = DateIntervalFormatter()
dateIntervalFormatter.dateTemplate = "dd-MM-yyyy"
dateIntervalFormatter.locale = Locale(identifier: "de")
dateIntervalFormatter.string(from: dateInterval)
// displays "12.–13.01.2022"
// expected "12-–13-01-2022"
dateIntervalFormatter.locale = Locale(identifier: "en")
dateIntervalFormatter.string(from: dateInterval)
// displays "1/12/2022 – 1/13/2022"
// expected "12-01-2022 – 13-01-2022"
dateIntervalFormatter.locale = Locale(identifier: "ro")
dateIntervalFormatter.string(from: dateInterval)
// displays "12.01.2022 – 13.01.2022"
// expected "12-01-2022 – 13-01-2022"
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
formatter.locale = Locale(identifier: "de")
let formatedTime = formatter.string(from: Date())
// displays "12-01-2022"
// expected "12-01-2022"

A locale/template based format means "I yield the details to you", so the DateIntervalFormatter does what it pleases depending on the locale. The DateFormatter example, on the other hand, is not locale/template based, so you get what you specified in the format (not template) you supplied.

Related

Timezone in swift [duplicate]

This question already has answers here:
How can I parse / create a date time stamp formatted with fractional seconds UTC timezone (ISO 8601, RFC 3339) in Swift?
(13 answers)
Swift - Get local date and time
(11 answers)
Closed 12 months ago.
I am struggling quite a bit with dates. I have the following code:
Current Date in Amsterdam: 22-Februari-2022 - 11:40
Current Date in New York: 22-Februari-2022 - 05:40
The dateBoughtString goes in as follows: 2022-02-18T19:50:47.081Z
The current date is just the current date.
let dateFormatterNew = DateFormatter()
dateFormatterNew.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
dateFormatterNew.timeZone = TimeZone(abbreviation: "GMT+1:00")
dateFormatterNew.locale = Locale(identifier: "nl-NL")
let dateBoughtTemp = dateFormatterNew.date(from: positionStatsString[0])!
print(dateBoughtTemp) // Prints: 2022-02-18 18:50:47 +0000
dateFormatterNew.timeZone = TimeZone(abbreviation: "GMT-5:00")
dateFormatterNew.locale = Locale(identifier: "en_US")
let dateNowTemp = dateFormatterNew.string(from: Date())
let dateBoughtTempTwo = dateFormatterNew.string(from: dateBoughtTemp)
print(dateNowTemp) // Prints: 2022-02-22T05:41:49.973Z
print(dateBoughtTempTwo) // Prints: 2022-02-18T13:50:47.081Z
let dateNow = dateFormatterNew.date(from: dateNowTemp)
let dateBought = dateFormatterNew.date(from: dateBoughtTempTwo)
print(dateNow!) // Prints: 2022-02-22 10:41:49 +0000 **INCORRECT**
print(dateBought!) // Prints: 2022-02-18 18:50:47 +0000 **INCORRECT**
When I convert to string all seems fine and it works as it should.
But when I convert those strings back to a date they just go back to Amsterdam time with the current date even being one hour off.
What am I missing here?
The problem is in your's parameter 'Z':
'' means that it's content doesn't involved in time formatting.
So when you apply timeZone parameter date is printed in particular time zone without correct timeZone suffix and when it's scanned it's scanned in particular time zone, just expecting that there will by Z character at the end. So when you are formatting to date and then to string you are accumulating error caused by timezone difference.
Correct format will be "yyyy-MM-dd'T'HH:mm:ss.SSSXXX" or better to use ISO8601DateFormatter because you can't set invalid format in it.
So your printed dates will have valid timezone suffix and timezone suffix will be considered in backward conversion.
Another moment: you shouldn't convert string back to date with localized formatter, if it's UI part, but for that you can use UIDatePicker instead of UITextField.
So full code will be:
let isoDateFormatter = ISO8601DateFormatter()
isoDateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let date = isoDateFormatter.date(from: "2022-02-18T19:50:47.081Z")!
let now = Date()
do {
let amsterdamDateFormatter = DateFormatter()
amsterdamDateFormatter.timeZone = .init(abbreviation: "GMT+1:00")
amsterdamDateFormatter.dateStyle = .long
amsterdamDateFormatter.timeStyle = .short
print("now in Amsterdam: \(amsterdamDateFormatter.string(from: now))")
print("time in Amsterdam: \(amsterdamDateFormatter.string(from: date))")
}
do {
let newYourkDateFormatter = DateFormatter()
newYourkDateFormatter.timeZone = .init(abbreviation: "GMT-5:00")
newYourkDateFormatter.dateStyle = .long
newYourkDateFormatter.timeStyle = .short
print("now in NY: \(newYourkDateFormatter.string(from: now))")
print("time in NY: \(newYourkDateFormatter.string(from: date))")
}
Use the below code for formatter, Change the timezone and dateFormat according to your need:
let formatter = DateFormatter()
formatter.calendar = Calendar(identifier: .iso8601)
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX"

How to convert yyyy-MM-dd'T'HH:mm:ss.SSS'Z' to MM-dd-yyyy in swift

Hello I need to change the date format
I getting a response from backend like
dob = "1989-03-06T00:00:00Z";
in my case, I write the following code but my app is crashed i think my current date format is wrong.
func DateFromWebtoApp(_ date: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
let date = dateFormatter.date(from: date)
dateFormatter.dateFormat = "MM-dd-yyyy"
return dateFormatter.string(from: date!)
}
Please look at the date string. There are no milliseconds (S) and the Z is a format specifier (no single quotes).
Further for an arbitrary date format add always a fixed Locale
func dateFromWebtoApp(_ dateString: String) -> String {
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = dateFormatter.date(from: dateString)
dateFormatter.dateFormat = "MM-dd-yyyy"
return dateFormatter.string(from: date!)
}
Try this, Its work on my side.
func DateFromWebtoApp(_ date: String) -> String
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date = dateFormatter.date(from: date)
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.timeZone = .current
return dateFormatter.string(from: date!)
}
Since that input date string is in a ISO complaint date format you can use ISO8601DateFormatter.
func dateFromWebtoApp(_ inDate: String) -> String? {
let inFormatter = ISO8601DateFormatter()
if let date = inFormatter.date(from: inDate) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM-dd-yyyy"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter.string(from: date)
}
return nil
}
Since constructing date formatters is somewhat expensive it might be worthwhile to declare them outside of the function and keep them around if possible
Date formatters are notoriously computationally intensive to create. They’re also computationally expensive to change the dateFormat. So I’d suggest you declare two date formatter properties up front, and instantiate them once and only once.
let isoDateFormatter = ISO8601DateFormatter()
let dobFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
The first, isoDateFormatter is for converting strings from your backend into Date objects. By using ISO8601DateFormatter, it gets you out of the business of manually configuring the locale for a DateFormatter.
The second, dobFormatter is for converting the date of birth Date object into a string to be presented in your UI. For this second formatter, please note that:
I don’t use dateFormat, but rather dateStyle (so the result honors the particular device’s date localization preferences, where UK users will see “6 Mar 1989”, but US users will see “Mar 6, 2019”). Use .short rather than .medium if you really want to see dd/MM/yy for UK users and MM/dd/yy for US users. But I personally like the medium format, as it is both reasonably concise but also completely unambiguous. Of, if I have space, I use the long format, which represents date most naturally (e.g. “March 6, 2019” rather than “Mar 6, 2019”). Do whatever your UI demands.
But if this is for presentation to the end user, you really should avoid hard-coded date format strings as users want to see dates presented in a manner consistent with their personal device-wide localization settings, not what one app or the other prefers. And there’s no reason to limit your app to US users, because some day you might want to reach a broader audience.
I set the time zone for this output formatter to be GMT because this string represents a date, not a combined date/time. E.g., 1989-03-06T00:00:00Z, which is midnight on March 6, 1989 GMT, translates to is 4pm on March 5th for us in California. But when you present the birthdate, you want to say March 6th, not March 5th. In short, you want to ignore the time and timezone information. You do this by using GMT/UTC/Zulu for your output date formatter.
Anyway, you’d then use it like so:
let input = "1989-03-06T00:00:00Z"
if let dob = isoDateFormatter.date(from: input) {
let output = dobFormatter.string(from: dob)
}

Swift - Convert string to date

On iOS 11+ only, I have this bug : When user has disabled 24h mode in phone hour settings, my string can't be parse to date using this code.
On other iOS version, there is no problems...
Is something missing to force 24h mode for the formatter ?
Variables date and beginHour came from webservice.
// input date = 20171201
// input beginHour = 2359
let dateTimeFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "fr_FR")
dateFormatter.timeZone = TimeZone.autoupdatingCurrent
dateTimeFormatter.dateFormat = "yyyyMMdd HHmm"
if let date = dateTimeFormatter.date(from: date+" "+beginHour) {
// ...
}
I think in your code, you are not using the right dateFormatter,
kindly check between dateTimeFormatter and dateFormatter,
let dateTimeFormatter = DateFormatter()
dateTimeFormatter.locale = Locale(identifier: "fr_FR")
dateTimeFormatter.timeZone = TimeZone.autoupdatingCurrent
dateTimeFormatter.dateFormat = "yyyyMMdd HHmm"
if let date = dateTimeFormatter.date(from: date+" "+beginHour) {
// ...
}

cast "1900-01-01T00:00:00" string value to date

I've watching trough stack overflow to find the answer and I can't find it I want to cast this string value "1900-01-01T00:00:00" to Date format, I was trying with some formats like those:
"yyyy-MM-dd HH:mm:ss"
"EEE, dd MMM yyyy hh:mm:ss +zzzz"
"YYYY-MM-dd HH:mm:ss.A"
"yyyy-MM-dd HH:mm:ss.S"
but anyone of those its working.
and I want the date format like this
"dd-mm-yyyy"
Hope you can help me!
Thanks.
It is a two step process, first converting 1900-01-01T00:00:00 (known as a RFC 3999 or ISO 8601 date, referred to the specifications that define this format) into a Date object, and then converting that Date object back to a string in the form of 01-01-1900:
To convert your string in the form of 1900-01-01T00:00:00 into a Date object, you can use ISO8601DateFormatter:
let formatter = ISO8601DateFormatter()
formatter.formatOptions.remove(.withTimeZone)
let date = formatter.date(from: string)!
That is equivalent to the following DateFormat, in which one has to manually set the locale to en_US_POSIX (because RFC 3999/ISO 8601 dates use a Gregorian calendar, regardless of what the device's default calendar type) and sets the timeZone to GMT/Zulu, because usually RFC 3999/ISO 8601 dates are representing GMT unless specified otherwise:
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
let date = formatter.date(from: string)!
For more information about the importance of timezones and locales in parsing RFC 3999 and ISO 8601 dates, see Apple's Technical Q&A 1480.
Then, to convert that Date object to a string into 01-01-1900 (day, month, and year), you'd use a format string of dd-MM-yyyy (note the uppercase MM for "month", to distinguish it from mm for "minute"):
let formatter2 = DateFormatter()
formatter2.dateFormat = "dd-MM-yyyy"
let string = formatter2.string(from: date)
Two observations regarding the dateFormat string:
If this string is for displaying to the user, you might use use dateStyle rather than dateFormat, e.g.:
formatter2.dateStyle = .short
While this will generate a slightly different format, e.g. dd/MM/yy, the virtue of this approach is that the string will be localized (e.g. UK users will see MM/dd/yyyy, their preferred way of seeing short dates).
It just depends upon the purpose of your dd-MM-yyyy format. If it's for internal purposes, go ahead and use dateFormat. But if it's for showing dates in your UI, use dateStyle instead, and enjoy the localization that DateFormatter does automatically for you. For more information, see "Working With User-Visible Representations of Dates and Times" section of the DateFormatter reference.
Note that in the absence of a timeZone specified for this second formatter, it assumes that while the ISO 8601 date was in GMT, that you want to see the date in your local timezone. For example, (1900-01-01T00:00:00 GMT was Dec 31, 1899 at 4pm in California). If you want to see the date string of the original ISO 8601 object, not corrected for timezones, you'd just set the timeZone of this second formatter to be GMT as well, e.g.
formatter2.timeZone = TimeZone(secondsFromGMT: 0)
As others have pointed out, you want to avoid unnecessarily re-instantiating DateFormatter objects. So you might put these formatters in properties that are instantiated only once, or use an extension:
extension DateFormatter {
static let customInputFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions.remove(.withTimeZone)
return formatter
}()
static let customOutputFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
formatter.timeZone = TimeZone(secondsFromGMT: 0) // if you want date in your local timezone, remove this line
return formatter
}()
}
And then:
let input = "1900-01-01T00:00:00"
let date = DateFormatter.customInputFormatter.date(from: input)!
let output = DateFormatter.customOutputFormatter.string(from: date)
print(output)
This is how I do custom date formatters:
extension DateFormatter {
static let inDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
return dateFormatter
}()
static let outDateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-mm-yyyy"
return dateFormatter
}()
}
And then use it like:
if let date = DateFormatter.inDateFormatter.date(from: "1900-01-01T00:00:00") {
let newDateString = DateFormatter.outDateFormatter.string(from: date);
print(newDateString) //prints 01-00-1900
}
This avoids any potential performance issues and is clear at the point of use, while still being concise.
Use this extension I created, where you can pass the format as a parameter.
extension String
{
func toDate( dateFormat format : String) -> Date
{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
if let date = dateFormatter.date(from: self)
{
return date
}
print("Invalid arguments ! Returning Current Date . ")
return Date()
}
}
"1900-01-01T00:00:00".toDate(dateFormat: "yyyy-MM-dd'T'HH:mm:ss") //Plyground call test

Why does this date formatting not work?

I am puzzled. I read the international spec for formats...yet it seems to return a nil in playgrounds and in code.
let dateString = "022018"
let formatter = NSDateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MMyyyy")
let date = formatter.dateFromString(dateString)
I can't change the stringDate to be 02/2018...I have to maintain that format..what is the right mask then to get some output?
The problem is the call to formatter.setLocalizedDateFormatFromTemplate. I don't think this means what you think it does. You are turning a string to a date, not a date to a string. Just set the formatter's dateFormat. This works fine (Swift 3, hope you don't mind):
let dateString = "022018"
let formatter = DateFormatter()
formatter.dateFormat = "MMyyyy"
let date = formatter.date(from:dateString)

Resources