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

前端 未结 16 2050
天命终不由人
天命终不由人 2020-11-28 22:08

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\".

I have writ

相关标签:
16条回答
  • 2020-11-28 22:45

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

    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: "")
        }
    }
    
    0 讨论(0)
  • 2020-11-28 22:48

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

    - (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.

    0 讨论(0)
  • 2020-11-28 22:48

    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!)

    However, this should at least help you head in the right direction. Also, avoid using get in method names. Using get in 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:.

    0 讨论(0)
  • 2020-11-28 22:48

    Here is my solution in Swift 2 that avoid 24-hour problem by comparing two dates with a zero time.

    extension NSDate {
    
    private func dateWithZeroTime(date: NSDate) -> NSDate? {
        let calendar = NSCalendar.currentCalendar()
        let units: NSCalendarUnit = [.Day, .WeekOfYear, .Month, .Year]
        let components = calendar.components(units, fromDate: date)
        return calendar.dateFromComponents(components)
    }
    
    private func thisDay() -> NSDate? {
        return self.dateWithZeroTime(self)
    }
    
    private func today() -> NSDate? {
        return self.dateWithZeroTime(NSDate())
    }
    
    var relativeFormat: String? {
        let today = self.today()
        let thisDay = self.thisDay()
        
        let formatter = NSDateFormatter()
        formatter.dateStyle = NSDateFormatterStyle.LongStyle
        let dateString = formatter.stringFromDate(self)
        
        if nil != thisDay && nil != today {
            let units: NSCalendarUnit = [.Day, .WeekOfYear, .Month, .Year]
            let components = NSCalendar.currentCalendar().components(units, fromDate: thisDay!, toDate: today!, options: [])
            
            if (components.year > 0) {
                return components.year == 1 ? "A year ago, \(dateString)" : "\(components.year) years ago, \(dateString)"
            } else if (components.month > 0) {
                return components.month == 1 ? "A month ago, \(dateString)" : "\(components.month) months ago, \(dateString)"
            } else if (components.weekOfYear > 0) {
                return components.weekOfYear == 1 ? "A week ago, \(dateString)" : "\(components.weekOfYear) weeks ago, \(dateString)"
            } else if (components.day > 0) {
                return components.day == 1 ? "Yesterday, \(dateString)" : "\(self.dayOfTheWeek()), \(dateString)"
            } else {
                return "Today"
            }
        }
        
        return nil
    }
    
    func dayOfTheWeek() -> String {
        let weekdays = [
            "Sunday",
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday"
        ]
        
        let calendar: NSCalendar = NSCalendar.currentCalendar()
        let components: NSDateComponents = calendar.components(.Weekday, fromDate: self)
        return weekdays[components.weekday - 1]
    }
    }
    

    The Swift 5 solution:

    public extension Date {
    
      private func dateWithZeroTime(_ date: Date) -> Date? {
        let calendar = Calendar.current
        let units: Set<Calendar.Component> = Set( [.day, .weekOfYear, .month, .year])
        let components = calendar.dateComponents(units, from: date)
        return calendar.date(from: components)
      }
    
      private func thisDay() -> Date? {
        return self.dateWithZeroTime(self)
      }
    
      private func today() -> Date? {
        return self.dateWithZeroTime(Date())
      }
    
      var relativeFormat: String? {
        let formatter = DateFormatter()
        formatter.dateStyle = DateFormatter.Style.long
        let dateString = formatter.string(from: self)
    
        if let thisDay = self.thisDay(),
           let today = self.today() {
          let units: Set<Calendar.Component> = Set([.day, .weekOfYear, .month, .year])
          let components = Calendar.current.dateComponents(units, from: thisDay, to: today)
      
          if let year = components.year,
             year > 0 {
            return year == 1 ? "A year ago, \(dateString)" : "\(year) years ago, \(dateString)"
          } else if let month = components.month,
                    month > 0 {
            return month == 1 ? "A month ago, \(dateString)" : "\(month) months ago, \(dateString)"
          } else if let weekOfYear = components.weekOfYear,
                    weekOfYear > 0 {
            return weekOfYear == 1 ? "A week ago, \(dateString)" : "\(weekOfYear) weeks ago, \(dateString)"
          } else if let day = components.day,
                    day > 0 {
            return day == 1 ? "Yesterday, \(dateString)" : dayOfWeekWithDateString(dateString)
          } else {
            return "Today"
          }
        }
    
        return nil
      }
    
      func dayOfTheWeek() -> String? {
        let weekdays = [
          "Sunday",
          "Monday",
          "Tuesday",
          "Wednesday",
          "Thursday",
          "Friday",
          "Saturday"
        ]
    
        let calendar = Calendar.current
        let components: DateComponents = calendar.dateComponents(Set([.weekday]), from: self)
    
        guard let weekday = components.weekday else { return nil }
    
        return weekdays[weekday - 1]
      }
    
      func dayOfWeekWithDateString(_ dateString: String) -> String {
        if let dayOfWeek = dayOfTheWeek() {
          return "\(dayOfWeek), \(dateString)"
        } else {
          return dateString
        }
      }
    }
    
    0 讨论(0)
  • 2020-11-28 22:49

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

    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);
            }
        }
    
    0 讨论(0)
  • 2020-11-28 22:49

    The problem with doesRelativeDateFormatting is that it's pretty much limited to Yesterday, Today, Tomorrow. If you're looking for something more thorough, then take a look at the answers here.

    0 讨论(0)
提交回复
热议问题