Tweak relative timestamps.

pull/1/head
Matthew Chen 7 years ago
parent 41e505fb6f
commit d4fa7e5e68

@ -168,16 +168,10 @@ NS_ASSUME_NONNULL_BEGIN
self.timestampLabel.text
= NSLocalizedString(@"MESSAGE_STATUS_SEND_FAILED", @"Label indicating that a message failed to send.");
} else {
self.timestampLabel.text = [DateUtil formatTimestampAsTime:viewItem.interaction.timestamp
maxRelativeDurationMinutes:self.maxRelativeDurationMinutes];
self.timestampLabel.text = [DateUtil formatMessageTimestamp:viewItem.interaction.timestamp];
}
}
- (NSInteger)maxRelativeDurationMinutes
{
return 59;
}
- (CGSize)measureWithConversationViewItem:(ConversationViewItem *)viewItem
{
OWSAssert(viewItem);
@ -194,9 +188,9 @@ NS_ASSUME_NONNULL_BEGIN
// contents can be relative the current time. We avoid having
// message bubbles' "visually vibrate" as their timestamp labels
// vary in width. So we try to leave enough space for all possible
// contents of this label.
if ([DateUtil isTimestampRelative:viewItem.interaction.timestamp
maxRelativeDurationMinutes:self.maxRelativeDurationMinutes]) {
// contents of this label _for the first hour of its lifetime_, when
// the timestamp is particularly volatile.
if ([DateUtil isTimestampFromLastHour:viewItem.interaction.timestamp]) {
// Measure the "now" case.
self.timestampLabel.text = [DateUtil exemplaryNowTimeFormat];
timestampLabelWidth = MAX(timestampLabelWidth, [self.timestampLabel sizeThatFits:CGSizeZero].width);
@ -204,8 +198,7 @@ NS_ASSUME_NONNULL_BEGIN
// Since this case varies with time, we multiply to leave
// space for the worst case (whose exact value, due to localization,
// is unpredictable).
self.timestampLabel.text =
[DateUtil exemplaryRelativeTimeFormatWithMaxRelativeDurationMinutes:self.maxRelativeDurationMinutes];
self.timestampLabel.text = [DateUtil exemplaryMinutesTimeFormat];
timestampLabelWidth = MAX(timestampLabelWidth,
[self.timestampLabel sizeThatFits:CGSizeZero].width + self.timestampLabel.font.lineHeight * 0.5f);

@ -26,12 +26,11 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)formatTimestampAsTime:(uint64_t)timestamp;
+ (NSString *)formatDateAsTime:(NSDate *)date;
+ (NSString *)formatTimestampAsTime:(uint64_t)timestamp
maxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes;
+ (NSString *)formatMessageTimestamp:(uint64_t)timestamp;
+ (BOOL)isTimestampRelative:(uint64_t)timestamp maxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes;
+ (BOOL)isTimestampFromLastHour:(uint64_t)timestamp;
+ (NSString *)exemplaryNowTimeFormat;
+ (NSString *)exemplaryRelativeTimeFormatWithMaxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes;
+ (NSString *)exemplaryMinutesTimeFormat;
+ (BOOL)isSameDayWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2;
+ (BOOL)isSameDayWithDate:(NSDate *)date1 date:(NSDate *)date2;

@ -146,6 +146,24 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return [[calendar components:NSCalendarUnitDay fromDate:date1 toDate:date2 options:0] day];
}
// Returns the difference in years, ignoring shorter units of time.
// If both dates fall in the same year, returns 0.
// If firstDate is from the year before secondDate, returns 1.
//
// Note: Assumes both dates use the "current" calendar.
+ (NSInteger)yearsFromFirstDate:(NSDate *)firstDate toSecondDate:(NSDate *)secondDate
{
NSCalendar *calendar = [NSCalendar currentCalendar];
NSCalendarUnit units = NSCalendarUnitEra | NSCalendarUnitYear;
NSDateComponents *comp1 = [calendar components:units fromDate:firstDate];
NSDateComponents *comp2 = [calendar components:units fromDate:secondDate];
[comp1 setHour:12];
[comp2 setHour:12];
NSDate *date1 = [calendar dateFromComponents:comp1];
NSDate *date2 = [calendar dateFromComponents:comp2];
return [[calendar components:NSCalendarUnitYear fromDate:date1 toDate:date2 options:0] year];
}
+ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp
{
OWSCAssert(pastTimestamp > 0);
@ -201,54 +219,117 @@ static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
return dateTimeString.uppercaseString;
}
+ (NSString *)formatTimestampAsTime:(uint64_t)timestamp maxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes
+ (NSDateFormatter *)otherYearMessageFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"MMM d, yyyy"];
});
return formatter;
}
+ (NSDateFormatter *)thisYearMessageFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"MMM d"];
});
return formatter;
}
+ (NSDateFormatter *)thisWeekMessageFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"E"];
});
return formatter;
}
+ (NSString *)formatMessageTimestamp:(uint64_t)timestamp
{
NSDate *date = [NSDate ows_dateWithMillisecondsSince1970:timestamp];
NSDate *now = [NSDate new];
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp];
NSDate *nowDate = [NSDate ows_dateWithMillisecondsSince1970:nowTimestamp];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger minutesDiff
= MAX(0, [[calendar components:NSCalendarUnitMinute fromDate:date toDate:now options:0] minute]);
// Treat anything in the last two minutes as "now", so that we
// don't have to worry about pluralization while formatting.
if (minutesDiff <= 1) {
return NSLocalizedString(@"DATE_NOW", @"The present; the current time.");
} else if (minutesDiff <= maxRelativeDurationMinutes) {
NSInteger yearsDiff = [self yearsFromFirstDate:date toSecondDate:nowDate];
NSInteger daysDiff = [self daysFromFirstDate:date toSecondDate:nowDate];
NSInteger minutesDiff
= MAX(0, [[calendar components:NSCalendarUnitMinute fromDate:date toDate:nowDate options:0] minute]);
NSInteger hoursDiff
= MAX(0, [[calendar components:NSCalendarUnitHour fromDate:date toDate:nowDate options:0] hour]);
NSString *result;
if (yearsDiff > 0) {
// "Long date" + locale-specific "short" time format.
NSString *dayOfWeek = [self.otherYearMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
result = [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
} else if (daysDiff >= 7) {
// "Short date" + locale-specific "short" time format.
NSString *dayOfWeek = [self.thisYearMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
result = [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
} else if (daysDiff > 0) {
// "Day of week" + locale-specific "short" time format.
NSString *dayOfWeek = [self.thisWeekMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
result = [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
} else if (minutesDiff < 1) {
result = NSLocalizedString(@"DATE_NOW", @"The present; the current time.");
} else if (hoursDiff < 1) {
NSString *minutesString = [OWSFormat formatInt:(int)minutesDiff];
return [NSString stringWithFormat:NSLocalizedString(@"DATE_MINUTES_AGO_FORMAT",
@"Format string for a relative time, expressed as a certain number of "
@"minutes in the past. Embeds {{The number of minutes}}."),
minutesString];
result = [NSString stringWithFormat:NSLocalizedString(@"DATE_MINUTES_AGO_FORMAT",
@"Format string for a relative time, expressed as a certain number of "
@"minutes in the past. Embeds {{The number of minutes}}."),
minutesString];
} else {
return [self formatDateAsTime:date];
NSString *hoursString = [OWSFormat formatInt:(int)hoursDiff];
result = [NSString stringWithFormat:NSLocalizedString(@"DATE_HOURS_AGO_FORMAT",
@"Format string for a relative time, expressed as a certain number of "
@"hours in the past. Embeds {{The number of hours}}."),
hoursString];
}
return result.uppercaseString;
}
+ (BOOL)isTimestampRelative:(uint64_t)timestamp maxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes
+ (BOOL)isTimestampFromLastHour:(uint64_t)timestamp
{
NSDate *date = [NSDate ows_dateWithMillisecondsSince1970:timestamp];
NSDate *now = [NSDate new];
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp];
NSDate *nowDate = [NSDate ows_dateWithMillisecondsSince1970:nowTimestamp];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSInteger minutesDiff
= MAX(0, [[calendar components:NSCalendarUnitMinute fromDate:date toDate:now options:0] minute]);
return minutesDiff <= maxRelativeDurationMinutes;
NSInteger hoursDiff
= MAX(0, [[calendar components:NSCalendarUnitHour fromDate:date toDate:nowDate options:0] hour]);
return hoursDiff < 1;
}
+ (NSString *)exemplaryNowTimeFormat
{
return NSLocalizedString(@"DATE_NOW", @"The present; the current time.");
return NSLocalizedString(@"DATE_NOW", @"The present; the current time.").uppercaseString;
}
+ (NSString *)exemplaryRelativeTimeFormatWithMaxRelativeDurationMinutes:(NSInteger)maxRelativeDurationMinutes
+ (NSString *)exemplaryMinutesTimeFormat
{
NSString *minutesString = [OWSFormat formatInt:(int)maxRelativeDurationMinutes];
NSString *minutesString = [OWSFormat formatInt:(int)59];
return [NSString stringWithFormat:NSLocalizedString(@"DATE_MINUTES_AGO_FORMAT",
@"Format string for a relative time, expressed as a certain number of "
@"minutes in the past. Embeds {{The number of minutes}}."),
minutesString];
minutesString]
.uppercaseString;
}
+ (BOOL)isSameDayWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2

@ -611,7 +611,10 @@
/* Title shown while the app is updating its database. */
"DATABASE_VIEW_OVERLAY_TITLE" = "Optimizing Database";
/* Format string for a duration of time, expressed as a certain number of minutes. Embeds {{The number of minutes}}. */
/* Format string for a relative time, expressed as a certain number of hours in the past. Embeds {{The number of hours}}. */
"DATE_HOURS_AGO_FORMAT" = "%@ Hr Ago";
/* Format string for a relative time, expressed as a certain number of minutes in the past. Embeds {{The number of minutes}}. */
"DATE_MINUTES_AGO_FORMAT" = "%@ Min Ago";
/* The present; the current time. */

Loading…
Cancel
Save