Files
yattee/Yattee/Utilities/RelativeDateFormatter.swift
Arkadiusz Fal c0d38834a6 Let rows update their counts, drop fake dates, and own failed lookups
MAC6: a snapshot mutation carries its video count back to the list cache.
MAC8: playlist rows and cards are Buttons with the context menu attached,
so macOS accessibility can reach Edit and Delete.
MAC10: a published date stamped with the request's own time is dropped at
both API sentinels, and formattedPublishedDate refuses zero-interval output.
MAC11: a failed history hydration shows Details unavailable with a Try
Again action instead of keeping the raw video ID forever; an empty 200
body is a legible failure.
2026-09-11 20:28:35 +02:00

53 lines
2.1 KiB
Swift

//
// RelativeDateFormatter.swift
// Yattee
//
// Centralized utility for formatting relative dates with "just now" support.
//
import Foundation
/// Utility for formatting dates relative to now, with proper handling of very recent times.
enum RelativeDateFormatter {
/// Formats a date relative to now, showing "just now" for very recent dates.
/// - Parameters:
/// - date: The date to format
/// - justNowThreshold: Seconds within which to show "just now" (default: 10)
/// - unitsStyle: The style for the relative date formatter (default: .abbreviated)
/// - Returns: Formatted string (e.g., "just now", "5 min ago", "2 hours ago")
static func string(
for date: Date,
justNowThreshold: TimeInterval = 10,
unitsStyle: RelativeDateTimeFormatter.UnitsStyle = .abbreviated
) -> String {
let interval = Date().timeIntervalSince(date)
if interval < justNowThreshold {
return String(localized: "common.justNow")
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = unitsStyle
return formatter.localizedString(for: date, relativeTo: Date())
}
/// Whether a date is so close to now that a relative rendering of it reads
/// "in 0 seconds".
static func isEffectivelyNow(_ date: Date, tolerance: TimeInterval = 1) -> Bool {
abs(Date().timeIntervalSince(date)) < tolerance
}
/// Whether a publication date is the stamp an instance writes when it has
/// none to send.
///
/// Invidious answers `published` with the moment it replied — and
/// `publishedText` with "0 seconds ago" — for a video whose upload date it
/// could not read, which renders as "in 0 seconds"; a scraping Piped does
/// the same with `uploaded`. Read with a minute of slack, because the
/// clocks at the two ends of a request are not the same clock, and because
/// nothing reaches a listing within a minute of being published.
static func isStampedNow(_ date: Date, tolerance: TimeInterval = 60) -> Bool {
Date().timeIntervalSince(date) < tolerance
}
}