iPhone: конвертировать строку даты в относительную отметку времени

У меня есть timestamp в виде строки, например:

Чт, 21 мая 09 19:10:09 -0700

и я хотел бы преобразовать его в относительную отметку времени, например, «20 минут назад» или «3 дня назад».

Каков наилучший способ сделать это с помощью Objective-C для iPhone?

-(NSString *)dateDiff:(NSString *)origDate { NSDateFormatter *df = [[NSDateFormatter alloc] init]; [df setFormatterBehavior:NSDateFormatterBehavior10_4]; [df setDateFormat:@"EEE, dd MMM yy HH:mm:ss VVVV"]; NSDate *convertedDate = [df dateFromString:origDate]; [df release]; NSDate *todayDate = [NSDate date]; double ti = [convertedDate timeIntervalSinceDate:todayDate]; ti = ti * -1; if(ti < 1) { return @"never"; } else if (ti < 60) { return @"less than a minute ago"; } else if (ti < 3600) { int diff = round(ti / 60); return [NSString stringWithFormat:@"%d minutes ago", diff]; } else if (ti < 86400) { int diff = round(ti / 60 / 60); return[NSString stringWithFormat:@"%d hours ago", diff]; } else if (ti < 2629743) { int diff = round(ti / 60 / 60 / 24); return[NSString stringWithFormat:@"%d days ago", diff]; } else { return @"never"; } } 

Вот методы из Cocoa, которые помогут вам получить релевантную информацию (не уверен, что все они доступны в кока-touch).

  NSDate * today = [NSDate date]; NSLog(@"today: %@", today); NSString * str = @"Thu, 21 May 09 19:10:09 -0700"; NSDate * past = [NSDate dateWithNaturalLanguageString:str locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]]; NSLog(@"str: %@", str); NSLog(@"past: %@", past); NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; unsigned int unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; NSDateComponents *components = [gregorian components:unitFlags fromDate:past toDate:today options:0]; NSLog(@"months: %d", [components month]); NSLog(@"days: %d", [components day]); NSLog(@"hours: %d", [components hour]); NSLog(@"seconds: %d", [components second]); 

Объект NSDateComponents, похоже, сохраняет разницу в соответствующих единицах (как указано). Если вы укажете все единицы, вы можете использовать этот метод:

 void dump(NSDateComponents * t) { if ([t year]) NSLog(@"%d years ago", [t year]); else if ([t month]) NSLog(@"%d months ago", [t month]); else if ([t day]) NSLog(@"%d days ago", [t day]); else if ([t minute]) NSLog(@"%d minutes ago", [t minute]); else if ([t second]) NSLog(@"%d seconds ago", [t second]); } 

Если вы хотите рассчитать себя, вы можете посмотреть:

 NSDate timeIntervalSinceDate 

А затем используйте секунды в алгоритме.

Отказ от ответственности : если этот интерфейс становится устаревшим (я еще не проверял), предпочтительный способ Apple сделать это через NSDateFormatters , как это предлагается в комментариях ниже, выглядит довольно аккуратно – я сохраню свой ответ по историческим причинам, он может все еще полезно для некоторых взглянуть на используемую логику.

Я пока не могу редактировать, но я взял код Gilean и сделал пару настроек и сделал его категорией NSDateFormatter.

Он принимает строку формата, так что она будет работать с произвольными строками, и я добавил, если условия, чтобы сингулярные события были грамматически правильными.

Ура,

Carl CM

 @interface NSDateFormatter (Extras) + (NSString *)dateDifferenceStringFromString:(NSString *)dateString withFormat:(NSString *)dateFormat; @end @implementation NSDateFormatter (Extras) + (NSString *)dateDifferenceStringFromString:(NSString *)dateString withFormat:(NSString *)dateFormat { NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setFormatterBehavior:NSDateFormatterBehavior10_4]; [dateFormatter setDateFormat:dateFormat]; NSDate *date = [dateFormatter dateFromString:dateString]; [dateFormatter release]; NSDate *now = [NSDate date]; double time = [date timeIntervalSinceDate:now]; time *= -1; if(time < 1) { return dateString; } else if (time < 60) { return @"less than a minute ago"; } else if (time < 3600) { int diff = round(time / 60); if (diff == 1) return [NSString stringWithFormat:@"1 minute ago", diff]; return [NSString stringWithFormat:@"%d minutes ago", diff]; } else if (time < 86400) { int diff = round(time / 60 / 60); if (diff == 1) return [NSString stringWithFormat:@"1 hour ago", diff]; return [NSString stringWithFormat:@"%d hours ago", diff]; } else if (time < 604800) { int diff = round(time / 60 / 60 / 24); if (diff == 1) return [NSString stringWithFormat:@"yesterday", diff]; if (diff == 7) return [NSString stringWithFormat:@"last week", diff]; return[NSString stringWithFormat:@"%d days ago", diff]; } else { int diff = round(time / 60 / 60 / 24 / 7); if (diff == 1) return [NSString stringWithFormat:@"last week", diff]; return [NSString stringWithFormat:@"%d weeks ago", diff]; } } @end 

В интересах полноты, основанной на ответе @ Gilean, вот полный код для простой категории в NSDate, который имитирует помощников датировки рельсов. Для переподготовки по категориям это методы экземпляра, которые вы вызываете для объектов NSDate. Итак, если у меня есть NSDate, который представляет вчера, [myDate distanceOfTimeInWordsToNow] => «1 день».

Надеюсь, это полезно!

 @interface NSDate (NSDate_Relativity) -(NSString *)distanceOfTimeInWordsSinceDate:(NSDate *)aDate; -(NSString *)distanceOfTimeInWordsToNow; @end @implementation NSDate (NSDate_Relativity) -(NSString *)distanceOfTimeInWordsToNow { return [self distanceOfTimeInWordsSinceDate:[NSDate date]]; } -(NSString *)distanceOfTimeInWordsSinceDate:(NSDate *)aDate { double interval = [self timeIntervalSinceDate:aDate]; NSString *timeUnit; int timeValue; if (interval < 0) { interval = interval * -1; } if (interval< 60) { return @"seconds"; } else if (interval< 3600) { // minutes timeValue = round(interval / 60); if (timeValue == 1) { timeUnit = @"minute"; } else { timeUnit = @"minutes"; } } else if (interval< 86400) { timeValue = round(interval / 60 / 60); if (timeValue == 1) { timeUnit = @"hour"; } else { timeUnit = @"hours"; } } else if (interval< 2629743) { int days = round(interval / 60 / 60 / 24); if (days < 7) { timeValue = days; if (timeValue == 1) { timeUnit = @"day"; } else { timeUnit = @"days"; } } else if (days < 30) { int weeks = days / 7; timeValue = weeks; if (timeValue == 1) { timeUnit = @"week"; } else { timeUnit = @"weeks"; } } else if (days < 365) { int months = days / 30; timeValue = months; if (timeValue == 1) { timeUnit = @"month"; } else { timeUnit = @"months"; } } else if (days < 30000) { // this is roughly 82 years. After that, we'll say 'forever' int years = days / 365; timeValue = years; if (timeValue == 1) { timeUnit = @"year"; } else { timeUnit = @"years"; } } else { return @"forever ago"; } } return [NSString stringWithFormat:@"%d %@", timeValue, timeUnit]; } @end 

Есть уже много ответов, которые приходят к одному и тому же решению, но это не может повредить выборам. Вот что я придумал.

 - (NSString *)stringForTimeIntervalSinceCreated:(NSDate *)dateTime { NSDictionary *timeScale = @{@"second":@1, @"minute":@60, @"hour":@3600, @"day":@86400, @"week":@605800, @"month":@2629743, @"year":@31556926}; NSString *scale; int timeAgo = 0-(int)[dateTime timeIntervalSinceNow]; if (timeAgo < 60) { scale = @"second"; } else if (timeAgo < 3600) { scale = @"minute"; } else if (timeAgo < 86400) { scale = @"hour"; } else if (timeAgo < 605800) { scale = @"day"; } else if (timeAgo < 2629743) { scale = @"week"; } else if (timeAgo < 31556926) { scale = @"month"; } else { scale = @"year"; } timeAgo = timeAgo/[[timeScale objectForKey:scale] integerValue]; NSString *s = @""; if (timeAgo > 1) { s = @"s"; } return [NSString stringWithFormat:@"%d %@%@ ago", timeAgo, scale, s]; } 

Я взял код Карла Кориелла-Мартина и создал более простую категорию NSDate, которая не имеет предупреждений о форматировании строк в единственном числе, а также tidys неделю назад единственная:

 @interface NSDate (Extras) - (NSString *)differenceString; @end @implementation NSDate (Extras) - (NSString *)differenceString{ NSDate* date = self; NSDate *now = [NSDate date]; double time = [date timeIntervalSinceDate:now]; time *= -1; if (time < 60) { int diff = round(time); if (diff == 1) return @"1 second ago"; return [NSString stringWithFormat:@"%d seconds ago", diff]; } else if (time < 3600) { int diff = round(time / 60); if (diff == 1) return @"1 minute ago"; return [NSString stringWithFormat:@"%d minutes ago", diff]; } else if (time < 86400) { int diff = round(time / 60 / 60); if (diff == 1) return @"1 hour ago"; return [NSString stringWithFormat:@"%d hours ago", diff]; } else if (time < 604800) { int diff = round(time / 60 / 60 / 24); if (diff == 1) return @"yesterday"; if (diff == 7) return @"a week ago"; return[NSString stringWithFormat:@"%d days ago", diff]; } else { int diff = round(time / 60 / 60 / 24 / 7); if (diff == 1) return @"a week ago"; return [NSString stringWithFormat:@"%d weeks ago", diff]; } } @end 

В Swift

Применение:

 let time = NSDate(timeIntervalSince1970: timestamp).timeIntervalSinceNow let relativeTimeString = NSDate.relativeTimeInString(time) println(relativeTimeString) 

Расширение:

 extension NSDate { class func relativeTimeInString(value: NSTimeInterval) -> String { func getTimeData(value: NSTimeInterval) -> (count: Int, suffix: String) { let count = Int(floor(value)) let suffix = count != 1 ? "s" : "" return (count: count, suffix: suffix) } let value = -value switch value { case 0...15: return "just now" case 0..<60: let timeData = getTimeData(value) return "\(timeData.count) second\(timeData.suffix) ago" case 0..<3600: let timeData = getTimeData(value/60) return "\(timeData.count) minute\(timeData.suffix) ago" case 0..<86400: let timeData = getTimeData(value/3600) return "\(timeData.count) hour\(timeData.suffix) ago" case 0..<604800: let timeData = getTimeData(value/86400) return "\(timeData.count) day\(timeData.suffix) ago" default: let timeData = getTimeData(value/604800) return "\(timeData.count) week\(timeData.suffix) ago" } } } 

Используйте class NSDate:

 timeIntervalSinceDate 

возвращает интервал в секундах.

Быстрые упражнения для реализации этого в объективном c:

  1. Получите время «сейчас» NSDate
  2. Получите NSDate, с которым хотите сравнить
  3. Получите интервал в секундах, используя timeIntervalSinceDate

Затем выполните этот псевдо-код:

 if (x < 60) // x seconds ago else if( x/60 < 60) // floor(x/60) minutes ago else if (x/(60*60) < 24) // floor(x/(60*60) hours ago else if (x/(24*60*60) < 7) // floor(x(24*60*60) days ago 

и так далее...

то вам нужно решить, составляет ли месяц 30,31 или 28 дней. Держите его просто - выберите 30.

Там может быть лучший способ, но его 2 утра, и это первое, что пришло в голову ...

Мое решение:

 - (NSString *) dateToName:(NSDate*)dt withSec:(BOOL)sec { NSLocale *locale = [NSLocale currentLocale]; NSTimeInterval tI = [[NSDate date] timeIntervalSinceDate:dt]; if (tI < 60) { if (sec == NO) { return NSLocalizedString(@"Just Now", @""); } return [NSString stringWithFormat: NSLocalizedString(@"%d seconds ago", @""),(int)tI]; } if (tI < 3600) { return [NSString stringWithFormat: NSLocalizedString(@"%d minutes ago", @""),(int)(tI/60)]; } if (tI < 86400) { return [NSString stringWithFormat: NSLocalizedString(@"%d hours ago", @""),(int)tI/3600]; } NSDateFormatter *relativeDateFormatter = [[NSDateFormatter alloc] init]; [relativeDateFormatter setTimeStyle:NSDateFormatterNoStyle]; [relativeDateFormatter setDateStyle:NSDateFormatterMediumStyle]; [relativeDateFormatter setDoesRelativeDateFormatting:YES]; [relativeDateFormatter setLocale:locale]; NSString * relativeFormattedString = [relativeDateFormatter stringForObjectValue:dt]; return relativeFormattedString; } 

Не уверен, почему это не в cocoa-touch, я хороший стандартный способ сделать это было бы здорово.

Настройте некоторые типы для хранения данных, это упростит, если вы когда-нибудь захотите локализовать его немного больше. (очевидно, если вам нужно больше времени)

 typedef struct DayHours { int Days; double Hours; } DayHours; + (DayHours) getHourBasedTimeInterval:(double) hourBased withHoursPerDay:(double) hpd { int NumberOfDays = (int)(fabs(hourBased) / hpd); float hoursegment = fabs(hourBased) - (NumberOfDays * hpd); DayHours dh; dh.Days = NumberOfDays; dh.Hours = hoursegment; return dh; } 

ПРИМЕЧАНИЕ. Я использую часовую калькуляцию, так как это то, что есть у моих данных. NSTimeInterval является вторым. Мне также пришлось преобразовать между ними.

Я видел, что несколько раз были функции в fragmentах кода в Stack Overflow, и я хотел, чтобы это действительно давало четкое представление о времени (поскольку произошло какое-то действие). Для меня это означает «время назад» стиль для коротких временных интервалов (5 мин. Назад, 2 часа назад) и конкретные даты для более длительных периодов времени (15 апреля 2011 года вместо 2 лет назад). В основном я думал, что Facebook действительно хорошо справился с этим, и я хотел просто пойти по их примеру (поскольку я уверен, что они много думают об этом, и это очень легко и понятно понять с точки зрения потребителя).

После долгого времени в Интернете я был очень удивлен, увидев, что никто не реализовал это, насколько я мог судить. Решил, что я хочу, чтобы это было достаточно плохо, чтобы тратить время на то, чтобы написать и подумать, что я поделюсь.

Надеемся, вам понравится 🙂

Получить код здесь: https://github.com/nikilster/NSDate-Time-Ago

  • Невозможно добавить элементы в NSMutableArray ivar
  • Как изменить определенный цвет в изображении?
  • UITextView с подсветкой синтаксиса
  • Преобразование миллисекунд в NSDate
  • Как добавить 1 день к NSDate?
  • Почему вы используете символ подчеркивания для переменной экземпляра, но не соответствующее ему свойство?
  • Сериализация и десериализация объектов Objective-C в JSON
  • iPhone - UILabel, содержащий текст с несколькими шрифтами одновременно
  • Как получить текущее название города?
  • Ошибка: протокол недоступен, сброс backtrace
  • MKAnnotation, простой пример
  • Давайте будем гением компьютера.