Is it possible to add custom text in NSDateFormatter's format string? - ios

Suppose I want the date to look like this:
|1988|December|30|
How can add these to the dateFormatter or for that matter let the format be something like this:
30 in the month of December in the year of 1998
now for 1988,December and 30, I would like to use standard formats but I want the text I put to also accompany them.
specially in case of the above one where, in the format and pipes just come adjacent to the date format for date or month where there is no space between the format and the pipe.
Is any of this possible by just setting the format ?

You can insert arbitrary text (enclosed in single quotes) in the date format, for example.
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
[fmt setDateFormat:#"dd' in the month of 'MMMM' in the year of 'yyyy"];
NSString *s = [fmt stringFromDate:[NSDate date]];
Result:
09 in the month of July in the year of 2013

Swift Version:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE' text here 'h:mm' and there 'a"

Updated for iOS 13, Swift 5, Xcode 11 and building on Martin R's answer
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone.current
dateFormatter.locale = Locale.current
dateFormatter.dateFormat = "dd' in the month of 'MMMM' in the year of 'yyyy"
let stringDate = dateFormatter.string(from: Date())
print(stringDate)
// printed:
// 7 in the month of October in the year of 2019
P.S. If you wanted an apostrophe, then use: '' directly inside the string. For example "MMM d, ''yy" -> Nov 10, '19
Extension:
If you wanted to add ordinal indicators too (ex. the 'th' after '13th'), you can actually do it inside the date formatter string.
So if you wanted Nov 10th, the code would be:
/// Get date.
let date = Date()
/// Get just the day of the date.
let dayAsInt = Calendar.current.component(.day, from: date)
/// Init the formatter.
let dateFormatter = DateFormatter()
/// Set the format string.
/// Notice we include the 'MMM' to extract the month from the date, but we use a variable to get the 'th' part.
dateFormatter.dateFormat = "MMM '\(dayAsInt.getStringWithOrdinalIndicatorIfPossible)'"
let formattedDate = dateFormatter.string(from: date)
/// Will print out Nov 10th or Apr 1st or whatever.
Here's the extension I made to help:
/// This variable only adds the ordinal indicator if the Int that is calling this function can be converted to an NSNumber.
/// An ordinal indicator is the `rd` after `3rd` or the `st` after `1st`.
var getStringWithOrdinalIndicatorIfPossible: String {
let formatter = NumberFormatter()
formatter.numberStyle = .ordinal
return formatter.string(from: NSNumber(value: self)) ?? "\(self)"
}

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"

Swift date format returning wrong date

I need to convert my date to string and then string to date. date is "2020-10-17 1:22:01 PM +0000"
Here is my date to string conversion code:
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
let string = formatter.string(from: "2020-10-17 1:22:01 PM +0000")
let createdAT = string
its returning "2020-10-17 18:51:30+05:30"
Here is my string to date conversion code:
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
let date = dateFormatter.date(from:date)!
its returning "2020-10-17 1:21:30 PM +0000 - timeIntervalSinceReferenceDate : 624633690.0"
its returning the wrong date after i convert string to date. i need "2020-10-17 18:51:30+05:30" this time to be return when i convert string to date.....
The code in your question is muddled up. You try to convert a string into a string in the first example and something unspecified into a Date in the second example.
Here's how to convert a Date into a String:
import Foundation
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssXXXXX"
let string: String = formatter.string(from: Date())
print(string) // prints for example 2020-10-18T10:54:07+01:00
Here's how to convert a string into a date
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd' 'HH:mm:ssZ"
let date: Date = formatter.date(from: "2020-10-18 10:59:56+0100")! // In real life, handle the optional properly
print(date) // prints 2020-10-18 09:59:56 +0000
When you print a Date directly, it automatically uses UTC as the time zone. This is why it changed it in the code above.
In the examples, I explicitly specified the type of string and date to show what type they are. Type inference means you can omit these in normal code.
As a general rule when handling dates:
always use Date in your code. Date is a type that stores the number of seconds since Jan 1st 1970 UTC.
Only convert dates to strings when displaying them to the user or communicating with an external system.
When calculating periods etc, always use a Calendar to get things like date components and intervals in units other than seconds. You might think to get "the same time tomorrow" you could just add 24 * 60 * 60 to a Date but in many countries, like mine, that will work on only 363 days in the year. Calendar will correctly handle things like daylight saving and leap years.

dateFromString() returning wrong date swift 3.0

i am passing "01/12/2017" in the fromDate.text(textfield), but receiving unexpected output.
let formatter = DateFormatter.init()
formatter.dateFormat = "dd/mm/yyyy"
startDate = formatter.date(from: fromDate.text!)
print("startDate = \(startDate)")
output is : 31/12/2016
The format of date should be dd/MM/yyyy not dd/mm/yyyy. The mm indicates the minutes and MM indicates the month.
And also add the below line in your code
formatter.timeZone = TimeZone(abbreviation: "GMT+0:00")
This line of code set time zone. If you not, then you get 30/11/2017 in output.
The reason behind this is when string date not contain time then formatter assume that it is midnight and you also not given the timezone so it will take current timezone.
It has to be dd/MM/yyyy dateformat. MM in capital.
func convertToString(of dateTo: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy" //Your New Date format as per requirement change it own
let newDate: String = dateFormatter.string(from: dateTo) //pass Date here
print(newDate) //New formatted Date string
return newDate
}

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 can I not format my NSDate with the specified time zone with NSDateFormatter?

I want to get a date that is represented in a String with a time zone GMT+1 and display it on screen with the local time zone GMT+10.
I have 2 methods, one is for create a date from a String (with GMT+1 timeZone), the other one is to format the date into a String (with localTimeZone GMT+10):
func dateFromString(dateString: String) -> NSDate? {
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT+1")
dateFormatter.dateFormat = "M/d/yyyy hh:mma"
return dateFormatter.dateFromString(dateString)
}
func stringFromDate(date: NSDate) -> String {
let dateFormatter = NSDateFormatter()
dateFormatter.timeZone = NSTimeZone.localTimeZone()
dateFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
return dateFormatter.stringFromDate(date)
}
In the playground, when I do this:
let date = dateFromString("4/8/2015 1:29am")!
println(date)
println(stringFromDate(date))
I get the following output on the right side:
"Apr 8, 2015, 1:29 AM"
"2015-04-07 15:29:00 +0000"
"1:29 AM"
I don't understand why I don't get what I am expecting and looking for:
"Apr 8, 2015, 1:29 AM"
"2015-04-08 10:29:00 +0000"
"10:29 AM"
What's wrong?
Input formatters need the time zone in their string. Like so:
func dateFromString(dateString: String) -> NSDate? {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "M/d/yyyy hh:mma z"
return dateFormatter.dateFromString(dateString)
}
let date = dateFromString("4/8/2015 1:29am GMT+01")!
Also note that NSTimeZone names have a two digit offset. Compare in the playground
var oops = NSTimeZone(abbreviation: "GMT+1")
var righteous = NSTimeZone(abbreviation: "GMT+01")
The first is nil, the second is not.
I wouldn't trust the formatting of a time stamp that the debugger prints. In my experience dates are always in UTC regardless of the time zone you set according to the log. Try adding the date as a string to a label on the project and see if it's right.
The reasoning behind this as far as I know is that when you print to the log, all it's doing is calling -description. In the case of NSDate this will return in UTC by definition.

Resources