ios 如何将 NSDate 转换为相对格式,如“今天”、“昨天”、“一周前”、“一个月前”、“一年前”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20487465/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 22:42:43  来源:igfitidea点击:

How to convert NSDate in to relative format as "Today","Yesterday","a week ago","a month ago","a year ago"?

iosobjective-cnsdatensdateformatterrelative-date

提问by User 1531343

I want to convert nsdate in to relative format like "Today","Yesterday","a week ago","a month ago","a year ago","date as it is".

我想将 nsdate 转换为相对格式,如"Today","Yesterday","a week ago","a month ago","a year ago","date as it is".

I have written following method for it.. but some how its just printing as it is date.. can you please tell me what should be the problem?

我已经为它编写了以下方法..但有些方法只是按日期打印..你能告诉我应该是什么问题吗?

//Following is my function which converts the date into relative string

//以下是我将日期转换为相对字符串的函数

+(NSString *)getDateDiffrence:(NSDate *)strDate{
    NSDateFormatter *df = [[NSDateFormatter alloc] init];

    df.timeStyle = NSDateFormatterMediumStyle;
    df.dateStyle = NSDateFormatterShortStyle;
    df.doesRelativeDateFormatting = YES;
    NSLog(@"STRING DATEEE : %@ REAL DATE TODAY %@",[df stringFromDate:strDate],[NSDate date]);
      return [df stringFromDate:strDate];

}

I have date string with the following format "2013-10-29T09:38:00"

我有以下格式的日期字符串 "2013-10-29T09:38:00"

When I tried to give the NSDate object then its always return me null date.
so I tried to convert that date in to yyyy-MM-dd HH:mm:ssZZZZthen I pass this date to function then it's just printing whole date..

当我尝试给 NSDate 对象时,它总是返回空日期。
所以我试图将该日期转换为yyyy-MM-dd HH:mm:ssZZZZ然后我将此日期传递给函数然后它只是打印整个日期..

How to solve this problem?

如何解决这个问题呢?

//Following is the code I call the above function

//以下是我调用上面函数的代码

NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss"];
NSDate *date = [formatter dateFromString:[threadDict objectForKey:@"lastMessageDate"]];
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZZ"];

NSString *date1 = [formatter stringFromDate:date];
NSDate *date_d = [formatter dateFromString:date1];
NSString *resultstr=[UserManager getDateDiffrence:date];

self.dateLabel.text=resultstr;

回答by David R?nnqvist

For simplicity I'm assuming that the dates you are formatting are all in the past (no "tomorrow" or "next week"). It's not that it can't be done but it would be more cases to deal with and more strings to return.

为简单起见,我假设您正在格式化的日期都是过去的(没有“明天”或“下周”)。不是做不到,而是要处理更多的情况,返回更多的字符串。



You can use components:fromDate:toDate:options:with whatever combination of date components you are looking for to get the number of years, months, weeks, days, hours, etc. between two dates. By then going though them in order from most significant (e.g. year) to least significant (e.g. day), you can format a string based only on the most significant component.

您可以使用components:fromDate:toDate:options:您正在寻找的任何日期组件组合来获取两个日期之间的年数、月数、周数、天数、小时数等。然后按照从最重要(例如年)到最不重要(例如日)的顺序浏览它们,您可以仅根据最重要的部分来格式化字符串。

For example: a date that is 1 week, 2 days and 7 hours ago would be formatted as "1 week".

例如:1 周 2 天 7 小时前的日期将被格式化为“1 周”。

If you want to create special strings for a special number of a unit, like "tomorrow" for "1 day ago" then you can check the value of that component after you have determined that it is the most significant component.

如果您想为单位的特殊数字创建特殊字符串,例如“明天”代表“1 天前”,那么您可以在确定它是最重要的组成部分后检查该组成部分的值。

The code would look something like this:

代码看起来像这样:

- (NSString *)relativeDateStringForDate:(NSDate *)date
{
    NSCalendarUnit units = NSCalendarUnitDay | NSCalendarUnitWeekOfYear | 
                           NSCalendarUnitMonth | NSCalendarUnitYear;

    // if `date` is before "now" (i.e. in the past) then the components will be positive
    NSDateComponents *components = [[NSCalendar currentCalendar] components:units
                                                                   fromDate:date
                                                                     toDate:[NSDate date]
                                                                    options:0];

    if (components.year > 0) {
        return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
    } else if (components.month > 0) {
        return [NSString stringWithFormat:@"%ld months ago", (long)components.month];
    } else if (components.weekOfYear > 0) {
        return [NSString stringWithFormat:@"%ld weeks ago", (long)components.weekOfYear];
    } else if (components.day > 0) {
        if (components.day > 1) {
            return [NSString stringWithFormat:@"%ld days ago", (long)components.day];
        } else {
            return @"Yesterday";
        }
    } else {
        return @"Today";
    }
}


If your dates could also be in the future then you can check the absolute value of the components in the same order and then check if it's positive or negative to return the appropriate strings. I'me only showing the year below:

如果您的日期也可能在未来,那么您可以按相同的顺序检查组件的绝对值,然后检查它是正数还是负数以返回适当的字符串。我只显示以下年份:

if ( abs(components.year > 0) ) { 
    // year is most significant component
    if (components.year > 0) {
        // in the past
        return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
    } else {
        // in the future
        return [NSString stringWithFormat:@"In %ld years", (long)components.year];
    }
} 

回答by ChrisJF

Here's my answer (in Swift 3!) and why it's better.

这是我的答案(在Swift 3 中!)以及为什么它更好。

Answer:

回答:

func datePhraseRelativeToToday(from date: Date) -> String {

    // Don't use the current date/time. Use the end of the current day 
    // (technically 0h00 the next day). Apple's calculation of 
    // doesRelativeDateFormatting niavely depends on this start date.
    guard let todayEnd = dateEndOfToday() else {
        return ""
    }

    let calendar = Calendar.autoupdatingCurrent

    let units = Set([Calendar.Component.year,
                 Calendar.Component.month,
                 Calendar.Component.weekOfMonth,
                 Calendar.Component.day])

    let difference = calendar.dateComponents(units, from: date, to: todayEnd)

    guard let year = difference.year,
        let month = difference.month,
        let week = difference.weekOfMonth,
        let day = difference.day else {
            return ""
    }

    let timeAgo = NSLocalizedString("%@ ago", comment: "x days ago")

    let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.locale = Locale.autoupdatingCurrent
        formatter.dateStyle = .medium
        formatter.doesRelativeDateFormatting = true
        return formatter
    }()

    if year > 0 {
        // sample output: "Jan 23, 2014"
        return dateFormatter.string(from: date)
    } else if month > 0 {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .brief // sample output: "1mth"
        formatter.allowedUnits = .month
        guard let timePhrase = formatter.string(from: difference) else {
            return ""
        }
        return String(format: timeAgo, timePhrase)
    } else if week > 0 {
        let formatter = DateComponentsFormatter()
        formatter.unitsStyle = .brief; // sample output: "2wks"
        formatter.allowedUnits = .weekOfMonth
        guard let timePhrase = formatter.string(from: difference) else {
            return ""
        }
        return String(format: timeAgo, timePhrase)
    } else if day > 1 {
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .abbreviated; // sample output: "3d"
            formatter.allowedUnits = .day
            guard let timePhrase = formatter.string(from: difference) else {
                return ""
            }
            return String(format: timeAgo, timePhrase)
    } else {
        // sample output: "Yesterday" or "Today"
        return dateFormatter.string(from: date)
    }
}

func dateEndOfToday() -> Date? {
    let calendar = Calendar.autoupdatingCurrent
    let now = Date()
    let todayStart = calendar.startOfDay(for: now)
    var components = DateComponents()
    components.day = 1
    let todayEnd = calendar.date(byAdding: components, to: todayStart)
    return todayEnd
}

Remember to reuse your formattersto avoid any performance hit! Hint: extensions on DateFormatter and DateComponentsFormatter are good ideas.

请记住重复使用您的格式化程序以避免任何性能下降!提示: DateFormatter 和 DateComponentsFormatter 上的扩展是好主意。

Why it's better:

为什么更好:

  • Utilizes DateFormatter's "Yesterday" and "Today". This is already translated by Apple, which saves you work!
  • Uses DateComponentsFormatter's already translated "1 week" string. (Again less work for you, courtesy of Apple.) All you have to do is translate the "%@ ago" string.
  • The other answers incorrectly calculate the time when the day switches from "today" to "yesterday" to etc. Fixed constants are a big NO-NO because reasons. Also, the other answers use the current date/time when they should use the end of the current day'sdate/time.
  • Uses autoupdatingCurrent for Calendar & Locale which ensures your app is immediately up to date with the user's calendar and language preferences in Settings.app
  • 利用 DateFormatter 的“昨天”和“今天”。这已经由Apple翻译了,可以节省您的工作!
  • 使用 DateComponentsFormatter 已翻译的“1 周”字符串。(再次为您减少工作量,由 Apple 提供。)您所要做的就是翻译“%@ ago”字符串。
  • 其他答案错误地计算了一天从“今天”切换到“昨天”的时间等。固定常量是一个很大的NO-NO,因为原因。此外,其他答案在他们应该使用当天日期/时间的结束时使用当前日期/时间。
  • 将 autoupdatingCurrent 用于日历和区域设置,以确保您的应用立即与 Settings.app 中用户的日历和语言首选项保持同步

This answer was inspired by DateToolson GitHub.

这个答案的灵感来自GitHub 上的DateTools

回答by Saurabh Yadav

Swift update, thanks to objective-c answer of David R?nnqvist, it will work for the past dates.

Swift 更新,感谢 David R?nnqvist 的 Objective-c 回答,它将适用于过去的日期。

func relativeDateStringForDate(date : NSDate) -> NSString {

        let todayDate = NSDate()
        let units: NSCalendarUnit = [.Hour, .Day, .Month, .Year, .WeekOfYear]
        let components = NSCalendar.currentCalendar().components(units, fromDate: date , toDate: todayDate, options: NSCalendarOptions.MatchFirst )

        let year =  components.year
        let month = components.month
        let day = components.day
        let hour = components.hour
        let weeks = components.weekOfYear
        // if `date` is before "now" (i.e. in the past) then the components will be positive

        if components.year > 0 {
            return NSString.init(format: "%d years ago", year);
        } else if components.month > 0 {
            return NSString.init(format: "%d months ago", month);
        } else if components.weekOfYear > 0 {
            return NSString.init(format: "%d weeks ago", weeks);
        } else if (components.day > 0) {
            if components.day > 1 {
                return NSString.init(format: "%d days ago", day);
            } else {
                return "Yesterday";
            }
        } else {
            return NSString.init(format: "%d hours ago", hour);
        }
    }

回答by jeff-ziligy

FOR: SWIFT 3

适用于:SWIFT 3

Here's a Swift 3 version, for past dates, that handles all units and singular or plural in the returned String.

这是一个 Swift 3 版本,适用于过去的日期,它处理返回的字符串中的所有单位和单数或复数。

Example Use:

使用示例:

let oneWeekAgo = Calendar.current.date(byAdding: .weekOfYear, value: -1, to: Date())!

print(relativePast(for: oneWeekAgo)) // output: "1 week ago"

I based it on a riff off of Saurabh Yadav's. Thanks.

我是根据 Saurabh Yadav 的即兴演奏改编的。谢谢。

func relativePast(for date : Date) -> String {

    let units = Set<Calendar.Component>([.year, .month, .day, .hour, .minute, .second, .weekOfYear])
    let components = Calendar.current.dateComponents(units, from: date, to: Date())

    if components.year! > 0 {
        return "\(components.year!) " + (components.year! > 1 ? "years ago" : "year ago")

    } else if components.month! > 0 {
        return "\(components.month!) " + (components.month! > 1 ? "months ago" : "month ago")

    } else if components.weekOfYear! > 0 {
        return "\(components.weekOfYear!) " + (components.weekOfYear! > 1 ? "weeks ago" : "week ago")

    } else if (components.day! > 0) {
        return (components.day! > 1 ? "\(components.day!) days ago" : "Yesterday")

    } else if components.hour! > 0 {
        return "\(components.hour!) " + (components.hour! > 1 ? "hours ago" : "hour ago")

    } else if components.minute! > 0 {
        return "\(components.minute!) " + (components.minute! > 1 ? "minutes ago" : "minute ago")

    } else {
        return "\(components.second!) " + (components.second! > 1 ? "seconds ago" : "second ago")
    }
}

回答by user3034970

To avoid the 24-hour problem mentioned by Budidino to David's answer, I altered it to like this below -

为了避免 Budidino 对 David 的回答提到的 24 小时问题,我将其更改为如下所示 -

- (NSString *)relativeDateStringForDate:(NSDate *)date
{

NSCalendarUnit units = NSDayCalendarUnit | NSWeekOfYearCalendarUnit |
NSMonthCalendarUnit | NSYearCalendarUnit ;
NSCalendar *cal = [NSCalendar currentCalendar];
NSDateComponents *components1 = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:[NSDate date]];
NSDate *today = [cal dateFromComponents:components1];

components1 = [cal components:(NSCalendarUnitEra|NSCalendarUnitYear|NSCalendarUnitMonth|NSCalendarUnitDay) fromDate:date];
NSDate *thatdate = [cal dateFromComponents:components1];

// if `date` is before "now" (i.e. in the past) then the components will be positive
NSDateComponents *components = [[NSCalendar currentCalendar] components:units
                                                               fromDate:thatdate
                                                                 toDate:today
                                                                options:0];

if (components.year > 0) {
    return [NSString stringWithFormat:@"%ld years ago", (long)components.year];
} else if (components.month > 0) {
    return [NSString stringWithFormat:@"%ld months ago", (long)components.month];
} else if (components.weekOfYear > 0) {
    return [NSString stringWithFormat:@"%ld weeks ago", (long)components.weekOfYear];
} else if (components.day > 0) {
    if (components.day > 1) {
        return [NSString stringWithFormat:@"%ld days ago", (long)components.day];
    } else {
        return @"Yesterday";
    }
} else {
    return @"Today";
}
}

Basically, it creates 2 new dates without time pieces included.Then the comparison is done for "days" difference.

基本上,它会创建 2 个不包含时间段的新日期。然后对“天”差异进行比较。

回答by tassar

check NSDate-TimeAgo, it also supports multiple languages.

检查NSDate-TimeAgo,它也支持多种语言。

回答by Chigs79

NSString* AgoStringFromTime(NSDate* dateTime)
{
    NSDictionary *timeScale = @{@"sec"  :@1,
                                @"min"  :@60,
                                @"hr"   :@3600,
                                @"day"  :@86400,
                                @"week" :@605800,
                                @"month":@2629743,
                                @"year" :@31556926};
    NSString *scale;
    int timeAgo = 0-(int)[dateTime timeIntervalSinceNow];
    if (timeAgo < 60) {
        scale = @"sec";
    } else if (timeAgo < 3600) {
        scale = @"min";
    } else if (timeAgo < 86400) {
        scale = @"hr";
    } 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 %@%@", timeAgo, scale, s];
}

回答by dreamlax

You will need to work out this logic yourself. You will need to determine the number of days in between those two dates.

你需要自己解决这个逻辑。您需要确定这两个日期之间的天数。

Here is a relatively naive approach:

这是一个相对幼稚的方法:

+ (NSString *) dateDifference:(NSDate *)date
{
    const NSTimeInterval secondsPerDay = 60 * 60 * 24;
    NSTimeInterval diff = [date timeIntervalSinceNow] * -1.0;

    // if the difference is negative, then the given date/time is in the future
    // (because we multiplied by -1.0 to make it easier to follow later)
    if (diff < 0)
        return @"In the future";

    diff /= secondsPerDay; // get the number of days

    // if the difference is less than 1, the date occurred today, etc.
    if (diff < 1)
        return @"Today";
    else if (diff < 2)
        return @"Yesterday";
    else if (diff < 8)
        return @"Last week";
    else
        return [date description]; // use a date formatter if necessary
}

It is naive for a number of reasons:

出于多种原因,它是幼稚的:

  1. It doesn't take into account leap days
  2. It assumes there are 86400 seconds in a day (there is such a thing as leap seconds!)
  1. 它不考虑闰日
  2. 它假设一天有 86400 秒(有闰秒之类的东西!)

However, this should at least help you head in the right direction. Also, avoid using getin method names. Using getin a method name typically indicates that the caller must provide their own output buffer. Consider NSArray's method, getItems:range:, and NSString's method, getCharacters:range:.

但是,这至少应该可以帮助您朝着正确的方向前进。另外,避免get在方法名称中使用。使用get的方法名称通常指示调用方必须提供自己的输出缓冲区。考虑NSArray的方法getItems:range:NSString的方法getCharacters:range:

回答by n00bProgrammer

Here is code I created for my use:

这是我创建的供我使用的代码:

+ (NSString*) getTimestampForDate:(NSDate*)date {

    NSDate* sourceDate = date;

    // Timezone Offset compensation (optional, if your target users are limited to a single time zone.)

    NSTimeZone* sourceTimeZone = [NSTimeZone timeZoneWithName:@"America/New_York"];
    NSTimeZone* destinationTimeZone = [NSTimeZone systemTimeZone];

    NSInteger sourceGMTOffset = [sourceTimeZone secondsFromGMTForDate:sourceDate];
    NSInteger destinationGMTOffset = [destinationTimeZone secondsFromGMTForDate:sourceDate];

    NSTimeInterval interval = destinationGMTOffset - sourceGMTOffset;

    NSDate* destinationDate = [[NSDate alloc] initWithTimeInterval:interval sinceDate:sourceDate];

    // Timestamp calculation (based on compensation)

    NSCalendar* currentCalendar = [NSCalendar currentCalendar];
    NSCalendarUnit unitFlags = NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit;

    NSDateComponents *differenceComponents = [currentCalendar components:unitFlags fromDate:destinationDate toDate:[NSDate date] options:0];//Use `date` instead of `destinationDate` if you are not using Timezone offset correction

    NSInteger yearDifference = [differenceComponents year];
    NSInteger monthDifference = [differenceComponents month];
    NSInteger dayDifference = [differenceComponents day];
    NSInteger hourDifference = [differenceComponents hour];
    NSInteger minuteDifference = [differenceComponents minute];

    NSString* timestamp;

    if (yearDifference == 0
        && monthDifference == 0
        && dayDifference == 0
        && hourDifference == 0
        && minuteDifference <= 2) {

        //"Just Now"

        timestamp = @"Just Now";

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference == 0
               && minuteDifference < 60) {

        //"13 minutes ago"

        timestamp = [NSString stringWithFormat:@"%ld minutes ago", (long)minuteDifference];

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference == 1) {

        //"1 hour ago" EXACT

        timestamp = @"1 hour ago";

    } else if (yearDifference == 0
               && monthDifference == 0
               && dayDifference == 0
               && hourDifference < 24) {

        timestamp = [NSString stringWithFormat:@"%ld hours ago", (long)hourDifference];

    } else {

        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setLocale:[NSLocale currentLocale]];

        NSString* strDate, *strDate2 = @"";

        if (yearDifference == 0
            && monthDifference == 0
            && dayDifference == 1) {

            //"Yesterday at 10:23 AM", "Yesterday at 5:08 PM"

            [formatter setDateFormat:@"hh:mm a"];
            strDate = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"Yesterday at %@", strDate];

        } else if (yearDifference == 0
                   && monthDifference == 0
                   && dayDifference < 7) {

            //"Tuesday at 7:13 PM"

            [formatter setDateFormat:@"EEEE"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];

        } else if (yearDifference == 0) {

            //"July 4 at 7:36 AM"

            [formatter setDateFormat:@"MMMM d"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];

        } else {

            //"March 24 2010 at 4:50 AM"

            [formatter setDateFormat:@"d MMMM yyyy"];
            strDate = [formatter stringFromDate:date];
            [formatter setDateFormat:@"hh:mm a"];
            strDate2 = [formatter stringFromDate:date];

            timestamp = [NSString stringWithFormat:@"%@ at %@", strDate, strDate2];
        }
    }

    return timestamp;
}

回答by Stephen Jesse

This is just a copy of a previous answer but it returns Just nowif it is less than five seconds.

这只是先前答案的副本,但Just now如果它少于五秒,它就会返回。

func relativePast(for date : Date) -> String {

    let units = Set<Calendar.Component>([.year, .month, .day, .hour, .minute, .second, .weekOfYear])
    let components = Calendar.current.dateComponents(units, from: date, to: Date())

    if components.year! > 0 {
        return "\(components.year!) " + (components.year! > 1 ? "years ago" : "year ago")

    } else if components.month! > 0 {
        return "\(components.month!) " + (components.month! > 1 ? "months ago" : "month ago")

    } else if components.weekOfYear! > 0 {
        return "\(components.weekOfYear!) " + (components.weekOfYear! > 1 ? "weeks ago" : "week ago")

    } else if (components.day! > 0) {
        return (components.day! > 1 ? "\(components.day!) days ago" : "Yesterday")

    } else if components.hour! > 0 {
        return "\(components.hour!) " + (components.hour! > 1 ? "hours ago" : "hour ago")

    } else if components.minute! > 0 {
        return "\(components.minute!) " + (components.minute! > 1 ? "minutes ago" : "minute ago")

    } else {
        return "\(components.second!) " + (components.second! > 5 ? "seconds ago" : "Just Now".replacingOccurrences(of: "0", with: "")
    }
}