NSDateFormatter doesn't support long-form day periods ('b' or 'B') - ios

NSDateFormatter doesn't seem able to use the 'B' or 'b' format specifiers. 'B' is a little like 'a' (am/pm), but it outputs things like "at night" or "in the morning".
For example, this code:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale* locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en-US"];
NSString *dateTemplate = [NSDateFormatter dateFormatFromTemplate:#"h:mmB"
options:0
locale:locale];
[dateFormatter setDateFormat:dateTemplate];
[dateFormatter setLocale:locale];
NSString* now = [dateFormatter stringFromDate:[NSDate date]];
NSLog(#"String:\t%#\ndateTemplate:\t%#",
now,
dateFormatter.dateFormat);
...prints this:
String: 10:23 PM // Should be "10:23 at night"
dateTemplate: h:mm a // Note that 'B' turned into 'a' (am/pm)
Skipping dateFormatFromTemplate and putting the format directly into the formatter has the same effect.
The docs say that iOS 7 and later use tr35-31. It's unclear whether that spec supports 'B'. The formatting table for version 31 mentions 'a', but does not mention 'B'. On the other hand, the spec for all of tr35 does indeed mention 'B'.
If you download the actual CLDR data for version 31, you can find dayPeriod entries for "in the evening" etc.
The data exists; is there another formatting string I can use to get longer "day period" strings?

You are correct that B is used for a "natural language" description of the day period. However, the problem you're experiencing is arising from using it in a date format template.
It would seem that the CLDR does not recognize that it should keep B in a date format string when you ask it to localize a template format. Thus, it's substituting a back in to the final format string.
Incidentally, if you run through +[NSLocale availableLocaleIdentifiers], you'll see that there isn't any locale that support B in a template string.
However, if you use B in a date format string directly, then it works as expected:
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"h:mm B";
NSLog(#"%#", [dateFormatter stringFromDate:[NSDate date]]);
Outputs:
12:57 in the afternoon

I'm able to get it to work in a iOS playground in Xcode 9. Sorry, the code below is in Swift, but it does seem to be working for me.
let loc = Locale(identifier: "en-US")
let testDF = DateFormatter()
testDF.dateFormat = "MM-dd-yyyy HH:mm"
testDF.locale = loc
let df = DateFormatter()
df.dateFormat = "h:mm B"
df.locale = loc
df.string(from: testDF.date(from: "09-21-2018 17:22")!) // "5:22 in the afternoon"
df.string(from: testDF.date(from: "09-21-2018 19:22")!) // "7:22 in the evening"
df.string(from: testDF.date(from: "09-21-2018 22:22")!) // "10:22 at night"
df.dateFormat // "h:mm B"

Related

Date formatter for a certain format of date

I might missing something, but I can't compose proper date formatter string for the date:
2016-01-14T10:24:26+0000
What is 'T' here? and how to include timezone?
My string does not work: #"yyyy-MM-dd'T'hh:mm:ss Z"
You have to use 'T' in single quotes like below format to retrive string ftom date:
yyyy-MM-dd'T'hh:mm:ssZ
Let me know.
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = #"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'";
NSDate *yourDate = [dateFormatter dateFromString:myString];

Weekday symbol in MO TU WE ... format?

I need weekday symbol in MO TU WE TH FR SA SU
I am using setDateFormat:#"EEEEE"]; with shortWeekdaySymbols
But it only return Sun Mon etc. Let me know if it is possible ?
According to the Unicode specification you should be able to use the EEEEEE (six E) format to get a two-letter weekday symbol.
As rmaddy has mentioned, you can use six E format. That will give you Mo, Tu, We etc. But if you want them in captitals specifically (MO, TU, WE, etc), you can set the veryShortWeekdaySymbols property of the NSDateFormatter object and use the same format mentioned in the question. You can use any Weekday Symbol you wish. As follows:
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.veryShortWeekdaySymbols = #[#"SU", #"MO", #"TU", #"WE", #"TH", #"FR", #"SA"];
formatter.dateFormat = #"EEEEE";
NSLog(#"%#", [formatter stringFromDate:date]);

Converting NSString into NSDAte once again

i have a label which is :
_labelCell.text = [2014-06-22 20:27:48 +0000];
What i want to do is to convert this string into NSDate so i can format it into something like : EEEE dd MM yyyy
i try :
// convert to date
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"YYYY-MM-dd'T'HH:mm:ss'+0000'"];
NSDate *dte = [dateFormat dateFromString:str];
NSLog(#"Date: %#", dte);
but it always give me a NULL NSDate
Can someone help me on this little thing ?
Thank you very much.
Your date format needs to resemble the format of the date. See http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns for the format patterns. For your date 2014-06-22 20:27:48 +0000 you need to use "yyyy-MM-dd HH:mm:ss ZZZ". Note that it must be "yyyy", not "YYYY", and the zone field should be parsed rather than treated as a literal. There is no "T" separating date and time.
Your date formatter is expecting a T in between the date and time. It returns null because there the string has a space instead of a T.
You're also missing a space before the timezone.
Fix those two issues, and it should work:
dateFormat.dateFormat = #"YYYY-MM-dd HH:mm:ss '+0000'";
Beware this might give you the wrong date, because of time zone issues. Test that out, and if it doesn't work adjust accordingly with dateFormat.timeZone = ...

How to insert an NSDate into a column in an Azure managed database?

If you have an Azure back-end, with a column that is a DateTime or DateTimeOffset, the example code is rather sparse about how you send a timestamp value as part of an insert.
You can pass along an NSDate in the dictionary of values to insert, and the library will translate it for you and insert is as a UTC/GMT timezone value. However, my client specifically wanted this value to be in the timezone of the device which generated the data, which means I need to insert the value as a string, since NSDate has no inherent knowledge of timezones.
So...any suggestions on how to write the NSDate-to-string method?
How about this:
I changed my Azure Sql column datatype to datetimeoffset (which internally stores values as UTC as well as the timezone offset).
On client side I then used a date format with five Z's:
let date = NSDate() // local date time: Jun 27, 2014, 9:32 AM
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" // Note 5 Z's...
let strNow = formatter.stringFromDate(date) // "2014-06-27T09:32:49+02:00"
I was then able to insert the date string "2014-06-27T09:32:49+02:00" to my Azure table in that format.
Querying my Azure table for that same inserted date I received back:
2014/06/27 09:32:49 +02:00
There are two "gotchas":
The format is very specific for Azure to recognize and parse it correctly.
The required format is non-standard: the 'Z' specifier produces a timezone offset such as -0700 or +0800 but Azure will reject it if there isn't a colon between the hours and minutes, ie, -07:00 or +08:00. In the ARC solution below the colon is inserted after the string is generated.
(weird - format is a little off?)
+(NSString*)azureDateTimeString:(NSDate *)date
{
static NSDateFormatter *df = nil;
if(df == nil)
{
df = [[NSDateFormatter alloc] init];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"] ;
[df setLocale:enUSPOSIXLocale];
df.timeZone = [NSTimeZone localTimeZone];
[df setDateFormat:#"yyyy-MM-dd'T'HH:mm:ssZ"];
}
NSString *dateString = [df stringFromDate:date];
// insert a colon in the third position from the right of the string...
NSString *newString = [NSString stringWithFormat:#"%#:%#", [dateString substringToIndex:[dateString length]-2], [dateString substringFromIndex:[dateString length]-2]];
return newString;
}
In my case I found I had to append "Z" as a literal suffix rather than a timezone type indicator as part of the format string:
(My Azure column is DateTime and I'm using Xcode Swift)
let date = NSDate()
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
let strNow = formatter.stringFromDate(date)
= "2014-06-27T00:40:52Z"
Note that I not only included 'T' as a literal but also 'Z'.
I could then successfully insert that date string into my Azure table and then, as a test, I could read it back and reverse it like this:
var creationDate = "2014-06-27T00:40:52Z"; //read back from azure table
let dtCreation = formatter.dateFromString(creationDate)
= Jun 27, 2014, 12:40 AM"

NSDateFormatter: Date according to currentLocale, without Year

This can't be too difficult..
I want to display a date without the year. For example: "Aug, 2nd" (USA) or "02.08." (GERMANY)
It must work for a bunch of other locales as well.
My only idea so far is to do a normal format with year, and then remove the year-portion from the generated string.
I think you need to take a look at:
+ (NSString *)dateFormatFromTemplate:(NSString *)template options:(NSUInteger)opts locale:(NSLocale *)locale
As per the docs:
Returns a localized date format string representing the given date format components arranged appropriately for the specified locale.
Return Value
A localized date format string representing the date format components given in template, arranged appropriately for the locale specified by locale.
The returned string may not contain exactly those components given in template, but may—for example—have locale-specific adjustments applied.
Discussion
Different locales have different conventions for the ordering of date components. You use this method to get an appropriate format string for a given set of components for a specified locale (typically you use the current locale—see currentLocale).
The following example shows the difference between the date formats for British and American English:
NSLocale *usLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US"];
NSLocale *gbLocale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_GB"];
NSString *dateFormat;
// NOTE!!! I removed the 'y' from the example
NSString *dateComponents = #"MMMMd"; //#"yMMMMd";
dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:usLocale];
NSLog(#"Date format for %#: %#",
[usLocale displayNameForKey:NSLocaleIdentifier value:[usLocale localeIdentifier]], dateFormat);
dateFormat = [NSDateFormatter dateFormatFromTemplate:dateComponents options:0 locale:gbLocale];
NSLog(#"Date format for %#: %#",
[gbLocale displayNameForKey:NSLocaleIdentifier value:[gbLocale localeIdentifier]], dateFormat);
// Output:
// Date format for English (United States): MMMM d, y
// Date format for English (United Kingdom): d MMMM y
Extra code (add this to the code above):
//
NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
formatter.locale = gbLocale;
formatter.dateFormat = dateFormat;
NSLog(#"date: %#", [formatter stringFromDate: [NSDate date]]);
See here:
NSDateFormatter Class Reference
The two examples you give are very different from each other. One uses the abbreviated month name while the other uses the 2-digit month number. One uses a day ordinal ("2nd") while the other simply uses the 2-digit day number.
If you can accept using the same general format for all locales then make use of NSDateFormatter dateFormatFromTemplate:options:locale:.
NSString *localFormat = [NSDateFormatter dateFormatFromTemplate:#"MMM dd" options:0 locale:[NSLocale currentLocale]];
The result of this call will give back a format string you can use with NSDateFormatter setDateFormat:. The order of the month and day will be appropriate for the locale as well as any additional punctuation that should be added.
But again, this won't solve your exact needs because of the completely different formats you appear to want for each locale.
Swift 3
let template = "EEEEdMMM"
let locale = NSLocale.current // the device current locale
let format = DateFormatter.dateFormat(fromTemplate: template, options: 0, locale: locale)
let formatter = DateFormatter()
formatter.dateFormat = format
let now = Date()
let whatYouWant = formatter.string(from: now) // Sunday, Mar 5
Play with template to match your needs.
Doc and examples here to help you determine the template you want.
All the above answers are using an older api released in iOS 4. A newer api that was introduced in iOS 8 is available.
In the apple documentation for DateFormatter it provides the below sample code for only showing the day and month in a date. You can just pass the Locale.current to the DateFormatter if you don't want to specify a specific locale (I haven't been able to find out if this is the default implementation so would advise that you always set locale before using setLocalizedDateFormatFromTemplate(_:) which is suggested in the docs).
let dateFormatter = DateFormatter()
let date = Date(timeIntervalSinceReferenceDate: 410220000)
// US English Locale (en_US)
dateFormatter.locale = Locale(identifier: "en_US")
dateFormatter.setLocalizedDateFormatFromTemplate("MMMMd") // set template after setting locale
print(dateFormatter.string(from: date)) // December 31
// British English Locale (en_GB)
dateFormatter.locale = Locale(identifier: "en_GB")
dateFormatter.setLocalizedDateFormatFromTemplate("MMMMd") // // set template after setting locale
print(dateFormatter.string(from: date)) // 31 December

Resources