session-ios/Session/Signal/DateUtil.m

449 lines
15 KiB
Mathematica
Raw Normal View History

2017-07-21 17:49:38 +02:00
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2017-07-21 17:49:38 +02:00
//
2014-05-06 19:41:08 +02:00
2017-07-21 17:49:38 +02:00
#import "DateUtil.h"
2020-11-12 00:41:45 +01:00
#import <SignalCoreKit/NSDate+OWS.h>
2020-11-11 07:45:50 +01:00
#import <SignalUtilitiesKit/OWSFormat.h>
#import <SignalUtilitiesKit/NSString+SSK.h>
2014-05-06 19:41:08 +02:00
2018-02-13 19:06:08 +01:00
NS_ASSUME_NONNULL_BEGIN
2014-05-06 19:41:08 +02:00
static NSString *const DATE_FORMAT_WEEKDAY = @"EEEE";
@implementation DateUtil
+ (NSDateFormatter *)dateFormatter {
2017-09-27 22:38:41 +02:00
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setTimeStyle:NSDateFormatterNoStyle];
[formatter setDateStyle:NSDateFormatterShortStyle];
});
return formatter;
2014-05-06 19:41:08 +02:00
}
2018-07-09 21:20:58 +02:00
+ (NSDateFormatter *)dateBreakRelativeDateFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale];
formatter.dateStyle = NSDateFormatterShortStyle;
formatter.timeStyle = NSDateFormatterNoStyle;
formatter.doesRelativeDateFormatting = YES;
});
return formatter;
}
+ (NSDateFormatter *)dateBreakThisWeekDateFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale];
// "Monday", "Tuesday", etc.
formatter.dateFormat = @"EEEE";
});
return formatter;
}
+ (NSDateFormatter *)dateBreakThisYearDateFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale];
// Tue, Jun 6
formatter.dateFormat = @"EE, MMM d";
});
return formatter;
}
+ (NSDateFormatter *)dateBreakOldDateFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
formatter.locale = [NSLocale currentLocale];
formatter.dateStyle = NSDateFormatterMediumStyle;
formatter.timeStyle = NSDateFormatterNoStyle;
formatter.doesRelativeDateFormatting = YES;
});
return formatter;
}
2014-05-06 19:41:08 +02:00
+ (NSDateFormatter *)weekdayFormatter {
2017-09-27 22:38:41 +02:00
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:DATE_FORMAT_WEEKDAY];
});
return formatter;
2014-05-06 19:41:08 +02:00
}
+ (NSDateFormatter *)timeFormatter {
2017-09-27 22:38:41 +02:00
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setTimeStyle:NSDateFormatterShortStyle];
[formatter setDateStyle:NSDateFormatterNoStyle];
});
return formatter;
2014-05-06 19:41:08 +02:00
}
+ (NSDateFormatter *)monthAndDayFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
formatter.dateFormat = @"MMM d";
});
return formatter;
}
+ (NSDateFormatter *)shortDayOfWeekFormatter
{
static NSDateFormatter *formatter;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
formatter = [NSDateFormatter new];
[formatter setLocale:[NSLocale currentLocale]];
formatter.dateFormat = @"E";
});
return formatter;
}
2018-04-10 20:53:40 +02:00
+ (BOOL)dateIsOlderThanToday:(NSDate *)date
{
return [self dateIsOlderThanToday:date now:[NSDate date]];
}
+ (BOOL)dateIsOlderThanToday:(NSDate *)date now:(NSDate *)now
{
2018-04-17 16:15:22 +02:00
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
return dayDifference > 0;
2014-05-06 19:41:08 +02:00
}
2018-07-09 21:20:58 +02:00
+ (BOOL)dateIsOlderThanYesterday:(NSDate *)date
{
return [self dateIsOlderThanYesterday:date now:[NSDate date]];
}
+ (BOOL)dateIsOlderThanYesterday:(NSDate *)date now:(NSDate *)now
{
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
return dayDifference > 1;
}
2018-04-10 20:53:40 +02:00
+ (BOOL)dateIsOlderThanOneWeek:(NSDate *)date
{
return [self dateIsOlderThanOneWeek:date now:[NSDate date]];
}
+ (BOOL)dateIsOlderThanOneWeek:(NSDate *)date now:(NSDate *)now
{
2018-04-17 16:15:22 +02:00
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
return dayDifference > 6;
2014-05-06 19:41:08 +02:00
}
2018-04-10 20:53:40 +02:00
+ (BOOL)dateIsToday:(NSDate *)date
{
return [self dateIsToday:date now:[NSDate date]];
}
2018-04-10 20:53:40 +02:00
+ (BOOL)dateIsToday:(NSDate *)date now:(NSDate *)now
{
2018-04-17 16:15:22 +02:00
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
return dayDifference == 0;
}
+ (BOOL)dateIsThisYear:(NSDate *)date
{
2018-04-10 20:53:40 +02:00
return [self dateIsThisYear:date now:[NSDate date]];
}
+ (BOOL)dateIsThisYear:(NSDate *)date now:(NSDate *)now
{
NSCalendar *calendar = [NSCalendar currentCalendar];
return (
[calendar component:NSCalendarUnitYear fromDate:date] == [calendar component:NSCalendarUnitYear fromDate:now]);
}
2018-02-13 19:06:08 +01:00
+ (BOOL)dateIsYesterday:(NSDate *)date
{
2018-04-10 20:53:40 +02:00
return [self dateIsYesterday:date now:[NSDate date]];
}
+ (BOOL)dateIsYesterday:(NSDate *)date now:(NSDate *)now
2018-04-17 16:15:22 +02:00
{
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
return dayDifference == 1;
}
// Returns the difference in days, ignoring hours, minutes, seconds.
// If both dates are the same date, returns 0.
// If firstDate is a day before secondDate, returns 1.
//
// Note: Assumes both dates use the "current" calendar.
+ (NSInteger)daysFromFirstDate:(NSDate *)firstDate toSecondDate:(NSDate *)secondDate
2018-04-10 20:53:40 +02:00
{
NSCalendar *calendar = [NSCalendar currentCalendar];
2018-04-17 16:15:22 +02:00
NSCalendarUnit units = NSCalendarUnitEra | NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
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:NSCalendarUnitDay fromDate:date1 toDate:date2 options:0] day];
2018-02-13 19:06:08 +01:00
}
2018-07-09 19:55:25 +02:00
// 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];
}
2018-06-29 23:00:22 +02:00
+ (NSString *)formatPastTimestampRelativeToNow:(uint64_t)pastTimestamp
{
OWSCAssertDebug(pastTimestamp > 0);
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp];
BOOL isFutureTimestamp = pastTimestamp >= nowTimestamp;
NSDate *pastDate = [NSDate ows_dateWithMillisecondsSince1970:pastTimestamp];
2018-02-13 19:06:08 +01:00
NSString *dateString;
if (isFutureTimestamp || [self dateIsToday:pastDate]) {
2018-02-13 19:06:08 +01:00
dateString = NSLocalizedString(@"DATE_TODAY", @"The current day.");
} else if ([self dateIsYesterday:pastDate]) {
dateString = NSLocalizedString(@"DATE_YESTERDAY", @"The day before today.");
} else {
2018-02-13 19:06:08 +01:00
dateString = [[self dateFormatter] stringFromDate:pastDate];
}
2018-06-29 23:00:22 +02:00
return [[dateString rtlSafeAppend:@" "] rtlSafeAppend:[[self timeFormatter] stringFromDate:pastDate]];
}
2018-06-26 16:17:03 +02:00
+ (NSString *)formatTimestampShort:(uint64_t)timestamp
{
return [self formatDateShort:[NSDate ows_dateWithMillisecondsSince1970:timestamp]];
}
+ (NSString *)formatDateShort:(NSDate *)date
{
2018-08-03 16:25:22 +02:00
OWSAssertIsOnMainThread();
OWSAssertDebug(date);
2018-06-26 16:17:03 +02:00
2018-08-03 16:25:22 +02:00
NSDate *now = [NSDate date];
NSInteger dayDifference = [self daysFromFirstDate:date toSecondDate:now];
BOOL dateIsOlderThanToday = dayDifference > 0;
BOOL dateIsOlderThanOneWeek = dayDifference > 6;
2018-06-26 16:17:03 +02:00
NSString *dateTimeString;
if (![DateUtil dateIsThisYear:date]) {
dateTimeString = [[DateUtil dateFormatter] stringFromDate:date];
2018-08-03 16:25:22 +02:00
} else if (dateIsOlderThanOneWeek) {
2018-06-26 16:17:03 +02:00
dateTimeString = [[DateUtil monthAndDayFormatter] stringFromDate:date];
2018-08-03 16:25:22 +02:00
} else if (dateIsOlderThanToday) {
2018-06-26 16:17:03 +02:00
dateTimeString = [[DateUtil shortDayOfWeekFormatter] stringFromDate:date];
} else {
dateTimeString = [[DateUtil timeFormatter] stringFromDate:date];
}
return dateTimeString.localizedUppercaseString;
2018-06-26 16:17:03 +02:00
}
2018-07-09 21:20:58 +02:00
+ (NSString *)formatDateForConversationDateBreaks:(NSDate *)date
{
OWSAssertDebug(date);
2018-07-09 21:20:58 +02:00
if (![self dateIsThisYear:date]) {
// last year formatter: Nov 11, 2017
return [self.dateBreakOldDateFormatter stringFromDate:date];
} else if ([self dateIsOlderThanOneWeek:date]) {
// this year formatter: Tue, Jun 23
return [self.dateBreakThisYearDateFormatter stringFromDate:date];
} else if ([self dateIsOlderThanYesterday:date]) {
// day of week formatter: Thursday
return [self.dateBreakThisWeekDateFormatter stringFromDate:date];
} else {
// relative format: Today / Yesterday
return [self.dateBreakRelativeDateFormatter stringFromDate:date];
}
}
2018-07-05 17:56:46 +02:00
+ (NSString *)formatTimestampAsTime:(uint64_t)timestamp
{
2018-07-05 17:56:46 +02:00
return [self formatDateAsTime:[NSDate ows_dateWithMillisecondsSince1970:timestamp]];
}
2018-07-05 17:56:46 +02:00
+ (NSString *)formatDateAsTime:(NSDate *)date
{
OWSAssertDebug(date);
NSString *dateTimeString = [[DateUtil timeFormatter] stringFromDate:date];
return dateTimeString.localizedUppercaseString;
}
2018-07-09 19:55:25 +02:00
+ (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
2018-07-05 17:56:46 +02:00
{
NSDate *date = [NSDate ows_dateWithMillisecondsSince1970:timestamp];
2018-07-09 19:55:25 +02:00
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp];
NSDate *nowDate = [NSDate ows_dateWithMillisecondsSince1970:nowTimestamp];
2018-07-05 17:56:46 +02:00
NSCalendar *calendar = [NSCalendar currentCalendar];
2018-08-16 21:34:54 +02:00
NSDateComponents *relativeDiffComponents =
[calendar components:NSCalendarUnitMinute | NSCalendarUnitHour fromDate:date toDate:nowDate options:0];
NSInteger minutesDiff = MAX(0, [relativeDiffComponents minute]);
NSInteger hoursDiff = MAX(0, [relativeDiffComponents hour]);
if (hoursDiff < 1 && minutesDiff < 1) {
2018-08-16 21:34:54 +02:00
return NSLocalizedString(@"DATE_NOW", @"The present; the current time.");
}
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];
}
2018-07-09 22:39:48 +02:00
// Note: we are careful to treat "future" dates as "now".
2018-07-09 19:55:25 +02:00
NSInteger yearsDiff = [self yearsFromFirstDate:date toSecondDate:nowDate];
if (yearsDiff > 0) {
// "Long date" + locale-specific "short" time format.
NSString *dayOfWeek = [self.otherYearMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
2018-08-16 21:34:54 +02:00
return [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
}
NSInteger daysDiff = [self daysFromFirstDate:date toSecondDate:nowDate];
if (daysDiff >= 7) {
2018-07-09 19:55:25 +02:00
// "Short date" + locale-specific "short" time format.
NSString *dayOfWeek = [self.thisYearMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
2018-08-16 21:34:54 +02:00
return [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
2018-07-09 19:55:25 +02:00
} else if (daysDiff > 0) {
// "Day of week" + locale-specific "short" time format.
NSString *dayOfWeek = [self.thisWeekMessageFormatter stringFromDate:date];
NSString *formattedTime = [[self timeFormatter] stringFromDate:date];
2018-08-16 21:34:54 +02:00
return [[dayOfWeek rtlSafeAppend:@" "] rtlSafeAppend:formattedTime];
2018-07-05 17:56:46 +02:00
} else {
2018-07-09 19:55:25 +02:00
NSString *hoursString = [OWSFormat formatInt:(int)hoursDiff];
2018-08-16 21:34:54 +02:00
return [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];
2018-07-05 17:56:46 +02:00
}
}
2018-07-09 19:55:25 +02:00
+ (BOOL)isTimestampFromLastHour:(uint64_t)timestamp
2018-07-05 17:56:46 +02:00
{
NSDate *date = [NSDate ows_dateWithMillisecondsSince1970:timestamp];
2018-07-09 19:55:25 +02:00
uint64_t nowTimestamp = [NSDate ows_millisecondTimeStamp];
NSDate *nowDate = [NSDate ows_dateWithMillisecondsSince1970:nowTimestamp];
2018-07-05 17:56:46 +02:00
NSCalendar *calendar = [NSCalendar currentCalendar];
2018-07-09 19:55:25 +02:00
NSInteger hoursDiff
= MAX(0, [[calendar components:NSCalendarUnitHour fromDate:date toDate:nowDate options:0] hour]);
return hoursDiff < 1;
2018-07-05 17:56:46 +02:00
}
+ (NSString *)exemplaryNowTimeFormat
{
return NSLocalizedString(@"DATE_NOW", @"The present; the current time.").localizedUppercaseString;
2018-07-05 17:56:46 +02:00
}
2018-07-09 19:55:25 +02:00
+ (NSString *)exemplaryMinutesTimeFormat
2018-07-05 17:56:46 +02:00
{
2018-07-09 19:55:25 +02:00
NSString *minutesString = [OWSFormat formatInt:(int)59];
2018-07-05 17:56:46 +02:00
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}}."),
2018-07-09 19:55:25 +02:00
minutesString]
.uppercaseString;
2018-07-05 17:56:46 +02:00
}
+ (BOOL)isSameDayWithTimestamp:(uint64_t)timestamp1 timestamp:(uint64_t)timestamp2
{
return [self isSameDayWithDate:[NSDate ows_dateWithMillisecondsSince1970:timestamp1]
date:[NSDate ows_dateWithMillisecondsSince1970:timestamp2]];
}
+ (BOOL)isSameDayWithDate:(NSDate *)date1 date:(NSDate *)date2
{
NSInteger dayDifference = [self daysFromFirstDate:date1 toSecondDate:date2];
return dayDifference == 0;
}
2018-07-05 17:56:46 +02:00
2014-05-06 19:41:08 +02:00
@end
2018-02-13 19:06:08 +01:00
NS_ASSUME_NONNULL_END