【问题标题】:iPhone: Convert date string to a relative time stampiPhone:将日期字符串转换为相对时间戳
【发布时间】:2010-10-28 12:37:33
【问题描述】:

我有一个像这样的字符串的时间戳:

09 年 5 月 21 日星期四 19:10:09 -0700

我想将其转换为相对时间戳,例如“20 分钟前”或“3 天前”。

在 iPhone 上使用 Objective-C 的最佳方法是什么?

【问题讨论】:

标签: iphone objective-c cocoa datetime time


【解决方案1】:

在 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"
        }
    }
}

【讨论】:

  • 我认为这种方法效果很好。唯一的事情,时间的转换,不应该也是函数的一部分吗?
  • 你说的是getTimeData吗?我从类作用域封装了它,并在函数中赋予它生命周期,因为在其他情况下它是无用的。
  • 不,我的意思是你的“使用”解释中的计算。不应该将这部分NSDate(timeIntervalSince1970: timestamp).timeIntervalSinceNow * -1 添加到主函数relativeTimeInString 中,因为您现在必须始终进行该计算。
  • 就像在独立的情况下,你需要封装这个转换。 改变了
【解决方案2】:

已经有很多答案可以得出相同的解决方案,但有选择也无妨。这是我想出的。

- (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];
}

【讨论】:

    【解决方案3】:

    我在 Stack Overflow 上看到好几次之前的代码 sn-ps 中的函数,我想要一个真正给出最清晰的时间感的函数(因为发生了一些动作)。对我来说,这意味着短时间间隔(5 分钟前,2 小时前)的“时间前”样式和较长时间段的特定日期(2011 年 4 月 15 日而不是 2 年前)。基本上,我认为 Facebook 在这方面做得非常好,我想以他们的例子为例(因为我确信他们对此进行了很多思考,从消费者的角度来看,这很容易理解)。

    在谷歌上搜索了很长时间后,我很惊讶地发现,据我所知,没有人实施过这个。决定我希望它足够糟糕以花时间写作并认为我会分享。

    希望你喜欢:)

    在此处获取代码:https://github.com/nikilster/NSDate-Time-Ago

    【讨论】:

    • 我不知道这个答案中的原始代码来源,但代码运行良好,并且 github 页面上的说明非常清晰。谢谢NV。
    • 谢谢@tmr!非常感谢说明清晰的评论 - 我尝试编写内容,使其逐步且 100% 清晰易懂,再次感谢! :) @Hot Licks - 我不知道你在说什么。我写了 100% 的代码。它基于 Facebook Mobile 如何呈现他们的日期。
    【解决方案4】:

    我采用了 Carl Coryell-Martin 的代码并制作了一个更简单的 NSDate 类别,它没有关于单数字符串格式的警告,并且还整理了一周前的单数:

    @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
    

    【讨论】:

    • 正是我想要的。
    【解决方案5】:

    我的解决方案:

    - (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;
    }
    

    【讨论】:

      【解决方案6】:

      为了完整起见,根据@Gilean 的回答,这里是 NSDate 上一个简单类别的完整代码,它模仿了 rails 的漂亮日期助手。对于类别的复习,这些是您可以在 NSDate 对象上调用的实例方法。所以,如果我有一个代表昨天的 NSDate,[myDate distanceOfTimeInWordsToNow] => "1 day"。

      希望有用!

      @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
      

      【讨论】:

      • 哦,另外:我在这个实现中省略了“之前”,以使其更有用。所以你可以打电话说@"自从你上次忏悔以来已经 %@ 了。", [confessionDate distanceOfTimeInWordsToNow]
      【解决方案7】:

      以下是来自 Cocoa 的方法,可帮助您获取相关信息(不确定它们是否都在 coca-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 执行此操作的首选方式(如下面的 cmets 建议)看起来也很简洁 - 我出于历史原因,我会保留我的答案,对于某些人来说看看使用的逻辑可能仍然有用。

      【讨论】:

      • 注意:旧的 NSCalendarDate/NSDateComponents 方式在 Mac OS X 上已被弃用。Apple 现在似乎建议只使用 NSDateFormatters。它们使处理任何组件变得非常容易。另请参阅 Gilean 的回答。
      • @andreb NSCalendarDate 已被弃用,但 NSDateComponents 肯定不会。从NSDate 获取这些组件的正确方法是使用NSCalendarNSDateComponents,如this answer所示。 NSDateFormatters 应该用于转换为/从字符串表示,而不是获取单个日期组件。
      【解决方案8】:

      我还不能编辑,但我采用了 Gilean 的代码并进行了一些调整,并将其设为 NSDateFormatter 的类别。

      它接受格式字符串,因此可以使用任意字符串,我添加了 if 子句以使单数事件在语法上是正确的。

      干杯,

      卡尔 C-M

      @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
      

      【讨论】:

        【解决方案9】:
        -(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";
            }   
        }
        

        【讨论】:

        • 它无法处理国际化。希望 Apple 将相对时间格式添加到 NSDateFormatter。 Android 有它。
        • 要添加国际化,只需用 NSLocalizedString() 包装字符串,大多数情况下都可以。
        • 这是一个方便的方法,仅供参考,用于包装到 NSDate 上的类别中。我有一个叫 NSDate+Relativity。方法是 -(NSString *)distanceOfTimeInWordsSinceDate:(NSDate *)aDate,它完成所有工作,以及便捷方法 -(NSString *)distanceOfTimeInWordsToNow,它调用前者,带有 [NSDate date]
        • 旧帖,但我只是用这个。你是我的英雄!
        • 要添加月份和年份,请添加以下代码: else if (ti
        【解决方案10】:

        不知道为什么这不是可可触摸,我很好的标准方法会很棒。

        设置一些类型来保存数据,如果您需要更多地本地化它会更容易。 (如果您需要更多时间段,显然可以扩展)

        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 是基于秒的。我还必须在两者之间进行转换。

        【讨论】:

          【解决方案11】:

          使用 NSDate 类:

          timeIntervalSinceDate
          

          以秒为单位返回间隔。

          在objective-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 点,这是我想到的第一件事......

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2011-05-05
            • 1970-01-01
            • 2011-03-16
            • 2016-09-15
            • 1970-01-01
            • 2019-07-30
            • 1970-01-01
            • 2012-05-16
            相关资源
            最近更新 更多