140 lines
4.3 KiB
Swift
140 lines
4.3 KiB
Swift
//
|
|
// DateParser.swift
|
|
// SodaLive
|
|
//
|
|
// Created by klaus on 12/19/25.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// MARK: - 내부: 다양한 포맷 파서를 시도
|
|
enum DateParser {
|
|
static func parse(_ text: String) -> Date? {
|
|
for parser in parsers {
|
|
if let d = parser(text) { return d }
|
|
}
|
|
return nil
|
|
}
|
|
|
|
static func relativeTimeText(fromUTC text: String, fallback: String, now: Date = Date()) -> String {
|
|
guard let date = parse(text) else {
|
|
return fallback
|
|
}
|
|
|
|
let interval = max(0, now.timeIntervalSince(date))
|
|
let calendar = Calendar.current
|
|
let yearMonth = calendar.dateComponents([.year, .month], from: date, to: now)
|
|
|
|
if let years = yearMonth.year, years >= 1 {
|
|
return I18n.Time.yearsAgo(years)
|
|
}
|
|
|
|
if let months = yearMonth.month, months >= 1 {
|
|
return I18n.Time.monthsAgo(months)
|
|
}
|
|
|
|
if interval < 60 {
|
|
return I18n.Time.justNow
|
|
}
|
|
|
|
if interval < 3600 {
|
|
return I18n.Time.minutesAgo(max(1, Int(interval / 60)))
|
|
}
|
|
|
|
if interval < 86_400 {
|
|
return I18n.Time.hoursAgo(max(1, Int(interval / 3600)))
|
|
}
|
|
|
|
return I18n.Time.daysAgo(max(1, Int(interval / 86_400)))
|
|
}
|
|
|
|
static func chatListDateText(fromUTC text: String, fallback: String, now: Date = Date()) -> String {
|
|
guard let date = parse(text) else {
|
|
return fallback
|
|
}
|
|
|
|
let interval = max(0, now.timeIntervalSince(date))
|
|
if interval < 60 {
|
|
return I18n.Time.justNow
|
|
}
|
|
|
|
if interval < 3600 {
|
|
return I18n.Time.minutesAgo(max(1, Int(interval / 60)))
|
|
}
|
|
|
|
if interval < 86_400 {
|
|
return I18n.Time.hoursAgo(max(1, Int(interval / 3600)))
|
|
}
|
|
|
|
if interval < 604_800 {
|
|
return I18n.Time.daysAgo(max(1, Int(interval / 86_400)))
|
|
}
|
|
|
|
let calendar = Calendar.current
|
|
if calendar.component(.year, from: date) == calendar.component(.year, from: now) {
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: LanguageHeaderProvider.current)
|
|
formatter.timeZone = calendar.timeZone
|
|
formatter.dateFormat = I18n.MainChat.monthDayDateFormat
|
|
return formatter.string(from: date)
|
|
}
|
|
|
|
let formatter = DateFormatter()
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.timeZone = calendar.timeZone
|
|
formatter.dateFormat = "yyyy.MM.dd"
|
|
return formatter.string(from: date)
|
|
}
|
|
|
|
// 시도 순서: ISO8601(소수초 포함) → ISO8601 → RFC3339 유사 → 공백 구분 기본
|
|
private static let parsers: [(String) -> Date?] = [
|
|
{ ISO8601.fractional.date(from: $0) },
|
|
{ ISO8601.basic.date(from: $0) },
|
|
{ DF.rfc3339.date(from: $0) },
|
|
{ DF.isoLocalDateTime.date(from: $0) },
|
|
{ DF.basic.date(from: $0) }
|
|
]
|
|
|
|
private enum ISO8601 {
|
|
static let fractional: ISO8601DateFormatter = {
|
|
let f = ISO8601DateFormatter()
|
|
f.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
return f
|
|
}()
|
|
|
|
static let basic: ISO8601DateFormatter = {
|
|
let f = ISO8601DateFormatter()
|
|
f.formatOptions = [.withInternetDateTime]
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
return f
|
|
}()
|
|
}
|
|
|
|
private enum DF {
|
|
static let rfc3339: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.locale = Locale(identifier: "en_US_POSIX")
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
|
|
return f
|
|
}()
|
|
|
|
static let basic: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.locale = Locale(identifier: "en_US_POSIX")
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
f.dateFormat = "yyyy-MM-dd HH:mm:ss"
|
|
return f
|
|
}()
|
|
|
|
static let isoLocalDateTime: DateFormatter = {
|
|
let f = DateFormatter()
|
|
f.locale = Locale(identifier: "en_US_POSIX")
|
|
f.timeZone = TimeZone(secondsFromGMT: 0)
|
|
f.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
|
|
return f
|
|
}()
|
|
}
|
|
}
|