15

I want to output a date (that has relative dates, e.g. Today, Yesterday etc.) and time (respecting the 12/24 hours setting on the iOS device) E.g.

  • Today 5:48 PM (when in 12 hour mode); and
  • Today 17:48 (when in 24 hour mode)

Configuring an NSFormatter like this (and using stringFromDate:) does not work:

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setLocale: [NSLocale autoupdatingCurrentLocale]];
[dateFormatter setTimeStyle:NSDateFormatterShortStyle];  // doesn't respect am/pm setting on iOS device (grrr!)
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setDoesRelativeDateFormatting:YES]; // but does get relative dates

This gives you relative dates, but defaults to the 12/24 setting for the locale, e.g. 12 hours for US and 24 hours for UK. (Why Apple think UK people like 24 hours clocks, I don't know...)

A work around that I've adopted uses two NSDateFormatters, one for the date and one for the time. It appears that when you configure an NSDateFormatter with a dateStyle of NSDateFormatterNoStyle and a timeStyle of NSDateFormatterShortStyle, then it does respects the 12/24 hour setting. (So it's just when you have BOTH date and time styles set that there is trouble.)

I've included my work around below in case anyone else has a similar issue. Is there an easier way to do this? The work around seems somewhat awkward and it is not clear how confident I should be it will continue working in future revisions of iOS.

Work around

- (NSString*) humanRelativeDateAndTimeAlt: (NSDate*) date;
{

NSDateFormatter* relativeDateFormatter = [[NSDateFormatter alloc] init];
[relativeDateFormatter setLocale: [NSLocale autoupdatingCurrentLocale]];
[relativeDateFormatter setTimeStyle:NSDateFormatterNoStyle]; // no time for this formatter
[relativeDateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setDoesRelativeDateFormatting:YES]; // relative dates

NSString* datePart = [[self relativeDateFormatter] stringFromDate:date];

NSDateFormatter* timeDateFormatter = [[NSDateFormatter alloc] init];
[timeDateFormatter setLocale:[NSLocale autoupdatingCurrentLocale]];
[timeDateFormatter setDateStyle: NSDateFormatterNoStyle]; // no date for this formatter
[timeDateFormatter setTimeStyle: NSDateFormatterShortStyle]; // does respect 12/24 hour setting

NSString* timePart = [timeDateFormatter stringFromDate:date];

int skipZero = 0;  // extra code to remove leading zero if present 
if ([[timePart substringToIndex:1] isEqualToString:@"0"]) skipZero = 1;

[relativeDateFormater release];
[timeDateFormatter release];

return [NSString stringWithFormat:@"%@ %@", datePart, [timePart substringFromIndex:skipZero]];
}
Obliquely
  • 7,002
  • 2
  • 32
  • 51

5 Answers5

12

The reason for this behaviour is Locale, set the correct Locale, Set the local of your NSDateFormatter to en_US_POSIX will fix this. It works for both 24-hour and 12 hour format.

From Apple doc:

On iPhone OS, the user can override the default AM/PM versus 24-hour time setting (via Settings > General > Date & Time > 24-Hour Time), which causes NSDateFormatter to rewrite the format string you set.

Reference: What is the best way to deal with the NSDateFormatter locale “feature”?

shim
  • 9,289
  • 12
  • 69
  • 108
Toseef Khilji
  • 17,192
  • 12
  • 80
  • 121
3

I found this solution :

 NSDate *now = [[NSDate alloc] init];
 NSTimeZone *localTimeZone = [NSTimeZone systemTimeZone];
 NSDateFormatter *rfc3339DateFormatter = [[NSDateFormatter alloc] init];
 NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];

 [rfc3339DateFormatter setLocale:enUSPOSIXLocale];
 [rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"];
 [rfc3339DateFormatter setTimeZone:localTimeZone];

 NSString *dateString = [rfc3339DateFormatter stringFromDate:now];

 NSLog(@"RFC 3339 datetime: %@", dateString);
 // 2013-06-27T10:27:08-0600
 // 2013-07-22T15:09:30+0100

That fixed 2 of my problems :

  1. When user is setted to 12h display mode, output with this formatter is display as 24h
  2. When you manually set to Dublin timezone, output does not display dateTime with Z but +0000
DjimOnDev
  • 389
  • 3
  • 13
3

I have run into the same issue. I've raised it as a bug with Apple, and posted it on Open Radar:

http://openradar.appspot.com/9774877

Ben Challenor
  • 3,365
  • 1
  • 35
  • 35
  • I confirm this bug. Its still present in iOS 7. There is an extensive discussion on this on another thread: http://stackoverflow.com/questions/143075/nsdateformatter-am-i-doing-something-wrong-or-is-this-a-bug – Andres Canella Oct 01 '13 at 06:36
1

I had a similar issue, what I ended up doing was to ammend the dateFormat, rather than specifying the timeStyle.

    NSDateFormatter *formatter = [[ NSDateFormatter alloc ] init];

    if (self.datePickerMode == UIDatePickerModeDateAndTime){
        [formatter setDateFormat:[NSString stringWithFormat:@"%@ %@", f.dateFormat, @"HH:mm:ss"]];
    }

Worked well for me, hope it helps.

PaulB
  • 962
  • 7
  • 15
1

Use this format

NSDateFormatter* relativeDateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy-MM-dd hh:mm a";

NSString *stringDate = [dateFormatter stringFromDate:[NSDate date]];
  1. Instad of HH (Hour in day (0-23)) use hh (Hour in am/pm (1-12))
  2. Add a (Am/pm marker) in the end of the format.

For example: [NSDate date] is 2017-05-14 12:40:00

  • In 24-Hour time you will get 2017-05-14 12:40
  • In 12-Hour time you will get 2017-05-14 12:40 PM

For Today | Yesterday I use NSDateComponents for calculate and return right date.

NSCalendarUnit units = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute;
NSDateComponents *components = [[NSCalendar currentCalendar] components:units fromDate:yourDate toDate:[NSDate date] options:0];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];

if (components.year > 0) {
    dateFormatter.dateFormat = [NSString stringWithFormat:@"'%@' '%@' MMM d, YYYY", [@"last seen" localized], [@"at" localized]];
}
else if (components.day > 7) {
    dateFormatter.dateFormat = [NSString stringWithFormat:@"'%@' MMM d '%@' hh:mm a", [@"last seen" localized], [@"at" localized]];
}

NSString *relativeDateString = [dateFormatter stringFromDate: yourDate];
  • do note however that in modern iOS, the only way recommended by Apple to use a date formatter is: https://stackoverflow.com/a/42370648/294884 – Fattie Jul 28 '17 at 13:53
  • I think that the behaviour of how HH and hh are interpreted depend on the default 12/24 hour switch settings for the locale. If you're in a region that defaults to 12 hour, it changes hh a to HH but not the other way around, and if you're in a region that defaults to 24 hour, it changes HH to hh a but not the other way around… – shim Apr 19 '18 at 17:44