import Foundation import Postbox import TelegramCore import TelegramPresentationData public func stringForTimestamp(day: Int32, month: Int32, year: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { let separator = dateTimeFormat.dateSeparator switch dateTimeFormat.dateFormat { case .monthFirst: return String(format: "%d%@%d%@%02d", month, separator, day, separator, year - 100) case .dayFirst: return String(format: "%d%@%02d%@%02d", day, separator, month, separator, year - 100) } } public func stringForTimestamp(day: Int32, month: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { let separator = dateTimeFormat.dateSeparator switch dateTimeFormat.dateFormat { case .monthFirst: return String(format: "%d%@%d", month, separator, day) case .dayFirst: return String(format: "%d%@%02d", day, separator, month) } } public func shortStringForDayOfWeek(strings: PresentationStrings, day: Int32) -> String { switch day { case 0: return strings.Weekday_ShortSunday case 1: return strings.Weekday_ShortMonday case 2: return strings.Weekday_ShortTuesday case 3: return strings.Weekday_ShortWednesday case 4: return strings.Weekday_ShortThursday case 5: return strings.Weekday_ShortFriday case 6: return strings.Weekday_ShortSaturday default: return "" } } public func stringForMonth(strings: PresentationStrings, month: Int32) -> String { switch month { case 0: return strings.Month_GenJanuary case 1: return strings.Month_GenFebruary case 2: return strings.Month_GenMarch case 3: return strings.Month_GenApril case 4: return strings.Month_GenMay case 5: return strings.Month_GenJune case 6: return strings.Month_GenJuly case 7: return strings.Month_GenAugust case 8: return strings.Month_GenSeptember case 9: return strings.Month_GenOctober case 10: return strings.Month_GenNovember case 11: return strings.Month_GenDecember default: return "" } } public func stringForMonth(strings: PresentationStrings, month: Int32, ofYear year: Int32) -> String { let yearString = "\(1900 + year)" switch month { case 0: return strings.Time_MonthOfYear_m1(yearString).0 case 1: return strings.Time_MonthOfYear_m2(yearString).0 case 2: return strings.Time_MonthOfYear_m3(yearString).0 case 3: return strings.Time_MonthOfYear_m4(yearString).0 case 4: return strings.Time_MonthOfYear_m5(yearString).0 case 5: return strings.Time_MonthOfYear_m6(yearString).0 case 6: return strings.Time_MonthOfYear_m7(yearString).0 case 7: return strings.Time_MonthOfYear_m8(yearString).0 case 8: return strings.Time_MonthOfYear_m9(yearString).0 case 9: return strings.Time_MonthOfYear_m10(yearString).0 case 10: return strings.Time_MonthOfYear_m11(yearString).0 default: return strings.Time_MonthOfYear_m12(yearString).0 } } public enum RelativeTimestampFormatDay { case today case yesterday } public func stringForUserPresence(strings: PresentationStrings, day: RelativeTimestampFormatDay, dateTimeFormat: PresentationDateTimeFormat, hours: Int32, minutes: Int32) -> String { let dayString: String switch day { case .today: dayString = strings.LastSeen_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 case .yesterday: dayString = strings.LastSeen_YesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 } return dayString } private func humanReadableStringForTimestamp(strings: PresentationStrings, day: RelativeTimestampFormatDay, dateTimeFormat: PresentationDateTimeFormat, hours: Int32, minutes: Int32) -> String { let dayString: String switch day { case .today: dayString = strings.Time_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 case .yesterday: dayString = strings.Time_YesterdayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 } return dayString } public func humanReadableStringForTimestamp(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, timestamp: Int32) -> String { var t: time_t = time_t(timestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) let timestampNow = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) var now: time_t = time_t(timestampNow) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) if timeinfo.tm_year != timeinfoNow.tm_year { return "\(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat))" } let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday if dayDifference == 0 || dayDifference == -1 { let day: RelativeTimestampFormatDay if dayDifference == 0 { day = .today } else { day = .yesterday } return humanReadableStringForTimestamp(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min) } else { return "\(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat))" } } public enum RelativeUserPresenceLastSeen { case justNow case minutesAgo(Int32) case hoursAgo(Int32) case todayAt(hours: Int32, minutes: Int32) case yesterdayAt(hours: Int32, minutes: Int32) case thisYear(month: Int32, day: Int32) case atDate(year: Int32, month: Int32) } public enum RelativeUserPresenceStatus { case offline case online(at: Int32) case lastSeen(at: Int32) case recently case lastWeek case lastMonth } public func relativeUserPresenceStatus(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> RelativeUserPresenceStatus { switch presence.status { case .none: return .offline case let .present(statusTimestamp): if statusTimestamp >= timestamp { return .online(at: statusTimestamp) } else { return .lastSeen(at: statusTimestamp) } case .recently: let activeUntil = presence.lastActivity + 30 if activeUntil >= timestamp { return .online(at: activeUntil) } else { return .recently } case .lastWeek: return .lastWeek case .lastMonth: return .lastMonth } } public func stringForRelativeTimestamp(strings: PresentationStrings, relativeTimestamp: Int32, relativeTo timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { var t: time_t = time_t(relativeTimestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) var now: time_t = time_t(timestamp) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) if timeinfo.tm_year != timeinfoNow.tm_year { return stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat) } let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday if dayDifference > -7 { if dayDifference == 0 { return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat) } else { return shortStringForDayOfWeek(strings: strings, day: timeinfo.tm_wday) } } else { return stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, dateTimeFormat: dateTimeFormat) } } public func stringForRelativeLiveLocationTimestamp(strings: PresentationStrings, relativeTimestamp: Int32, relativeTo timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { let difference = timestamp - relativeTimestamp if difference < 60 { return strings.LiveLocationUpdated_JustNow } else if difference < 60 * 60 { let minutes = difference / 60 return strings.LiveLocationUpdated_MinutesAgo(minutes) } else { var t: time_t = time_t(relativeTimestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) var now: time_t = time_t(timestamp) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday let hours = timeinfo.tm_hour let minutes = timeinfo.tm_min if dayDifference == 0 { return strings.LiveLocationUpdated_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 } else { return stringForFullDate(timestamp: relativeTimestamp, strings: strings, dateTimeFormat: dateTimeFormat) } } } public func stringForRelativeSymbolicTimestamp(strings: PresentationStrings, relativeTimestamp: Int32, relativeTo timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { var t: time_t = time_t(relativeTimestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) var now: time_t = time_t(timestamp) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday let hours = timeinfo.tm_hour let minutes = timeinfo.tm_min if dayDifference == 0 { return strings.Time_TodayAt(stringForShortTimestamp(hours: hours, minutes: minutes, dateTimeFormat: dateTimeFormat)).0 } else { return stringForFullDate(timestamp: relativeTimestamp, strings: strings, dateTimeFormat: dateTimeFormat) } } public func stringForRelativeLiveLocationUpdateTimestamp(strings: PresentationStrings, relativeTimestamp: Int32, relativeTo timestamp: Int32, dateTimeFormat: PresentationDateTimeFormat) -> String { var t: time_t = time_t(relativeTimestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) var now: time_t = time_t(timestamp) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) if timeinfo.tm_year != timeinfoNow.tm_year { return stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat) } let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday if dayDifference > -7 { if dayDifference == 0 { return stringForShortTimestamp(hours: timeinfo.tm_hour, minutes: timeinfo.tm_min, dateTimeFormat: dateTimeFormat) } else { return shortStringForDayOfWeek(strings: strings, day: timeinfo.tm_wday) } } else { return stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, dateTimeFormat: dateTimeFormat) } } public func stringAndActivityForUserPresence(strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, presence: TelegramUserPresence, relativeTo timestamp: Int32, expanded: Bool = false) -> (String, Bool) { switch presence.status { case .none: return (strings.LastSeen_Offline, false) case let .present(statusTimestamp): if statusTimestamp >= timestamp { return (strings.Presence_online, true) } else { let difference = timestamp - statusTimestamp if difference < 60 { return (strings.LastSeen_JustNow, false) } else if difference < 60 * 60 && !expanded { let minutes = difference / 60 return (strings.LastSeen_MinutesAgo(minutes), false) } else { var t: time_t = time_t(statusTimestamp) var timeinfo: tm = tm() localtime_r(&t, &timeinfo) var now: time_t = time_t(timestamp) var timeinfoNow: tm = tm() localtime_r(&now, &timeinfoNow) if timeinfo.tm_year != timeinfoNow.tm_year { return (strings.LastSeen_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0, false) } let dayDifference = timeinfo.tm_yday - timeinfoNow.tm_yday if dayDifference == 0 || dayDifference == -1 { let day: RelativeTimestampFormatDay if dayDifference == 0 { if expanded { day = .today } else { let minutes = difference / (60 * 60) return (strings.LastSeen_HoursAgo(minutes), false) } } else { day = .yesterday } return (stringForUserPresence(strings: strings, day: day, dateTimeFormat: dateTimeFormat, hours: timeinfo.tm_hour, minutes: timeinfo.tm_min), false) } else { return (strings.LastSeen_AtDate(stringForTimestamp(day: timeinfo.tm_mday, month: timeinfo.tm_mon + 1, year: timeinfo.tm_year, dateTimeFormat: dateTimeFormat)).0, false) } } } case .recently: let activeUntil = presence.lastActivity + 30 if activeUntil >= timestamp { return (strings.Presence_online, true) } else { return (strings.LastSeen_Lately, false) } case .lastWeek: return (strings.LastSeen_WithinAWeek, false) case .lastMonth: return (strings.LastSeen_WithinAMonth, false) } } public func userPresenceStringRefreshTimeout(_ presence: TelegramUserPresence, relativeTo timestamp: Int32) -> Double { switch presence.status { case let .present(statusTimestamp): if statusTimestamp >= timestamp { return Double(statusTimestamp - timestamp) } else { let difference = timestamp - statusTimestamp if difference < 30 { return Double((30 - difference) + 1) } else if difference < 60 * 60 { return Double((difference % 60) + 1) } else { return Double.infinity } } case .recently: let activeUntil = presence.lastActivity + 30 if activeUntil >= timestamp { return Double(activeUntil - timestamp + 1) } else { return Double.infinity } case .none, .lastWeek, .lastMonth: return Double.infinity } } public func stringForRemainingMuteInterval(strings: PresentationStrings, muteInterval value: Int32) -> String { let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) let value = max(1 * 60, value - timestamp) if value <= 1 * 60 * 60 { return strings.MuteExpires_Minutes(Int32(round(Float(value) / 60))) } else if value <= 24 * 60 * 60 { return strings.MuteExpires_Hours(Int32(round(Float(value) / (60 * 60)))) } else { return strings.MuteExpires_Days(Int32(round(Float(value) / (24 * 60 * 60)))) } }