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
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: "")
}
}
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.
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:
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:
.
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
}
}
}
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);
}
}
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.