완료 방송 상대 시간 표시

최근 종료 방송 카드에 UTC 기준 상대 시간 문자열을 표시한다.
This commit is contained in:
Yu Sung
2025-12-19 23:52:43 +09:00
parent 7307e5b255
commit dd51a3fc2e
5 changed files with 111 additions and 56 deletions

View File

@@ -0,0 +1,60 @@
//
// 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
}
// : 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.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
}()
}
}

View File

@@ -67,55 +67,3 @@ extension GetCommunityPostListResponse {
}
}
}
// MARK: - :
private enum DateParser {
static func parse(_ text: String) -> Date? {
for parser in parsers {
if let d = parser(text) { return d }
}
return nil
}
// : 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.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
}()
}
}

View File

@@ -5,9 +5,53 @@
// Created by klaus on 7/22/25.
//
import Foundation
struct GetLatestFinishedLiveResponse: Decodable {
let memberId: Int
let nickname: String
let profileImageUrl: String
let timeAgo: String
let dateUtc: String
}
// MARK: -
extension GetLatestFinishedLiveResponse {
/// `dateUtc`(UTC )
/// ( ///// ) .
/// , `date` .
func relativeTimeText(now: Date = Date()) -> String {
guard let createdAt = DateParser.parse(dateUtc) else {
return timeAgo
}
let nowDate = now
let interval = max(0, nowDate.timeIntervalSince(createdAt))
// / (/ )
let calendar = Calendar.current
let ym = calendar.dateComponents([.year, .month],
from: createdAt,
to: nowDate)
if let years = ym.year, years >= 1 {
return I18n.Time.yearsAgo(years)
}
if let months = ym.month, months >= 1 {
return I18n.Time.monthsAgo(months)
}
// //
if interval < 60 {
return I18n.Time.justNow
} else if interval < 3600 {
let minutes = Int(interval / 60)
return I18n.Time.minutesAgo(max(1, minutes))
} else if interval < 86_400 {
let hours = Int(interval / 3600)
return I18n.Time.hoursAgo(max(1, hours))
} else {
let days = Int(interval / 86_400)
return I18n.Time.daysAgo(max(1, days))
}
}
}

View File

@@ -28,7 +28,7 @@ struct LatestFinishedLiveItemView: View {
Spacer()
Text(item.timeAgo)
Text(item.relativeTimeText())
.font(.custom(Font.preRegular.rawValue, size: 16))
.foregroundColor(Color(hex: "78909C"))
}
@@ -45,7 +45,8 @@ struct LatestFinishedLiveItemView: View {
memberId: 1,
nickname: "크리에이터 1",
profileImageUrl: "https://cf.sodalive.net/profile/34638/34638-profile-5bfc2bac-3278-48f8-b60c-1294b615f629-8832-1751707083877",
timeAgo: "5분전"
timeAgo: "5분전",
dateUtc: "2025-08-10T15:00:00"
)
)
}

View File

@@ -51,13 +51,15 @@ struct SectionLatestFinishedLiveView: View {
memberId: 1,
nickname: "크리에이터 1",
profileImageUrl: "https://cf.sodalive.net/profile/34638/34638-profile-5bfc2bac-3278-48f8-b60c-1294b615f629-8832-1751707083877",
timeAgo: "5분전"
timeAgo: "5분전",
dateUtc: "2025-08-10T15:00:00"
),
GetLatestFinishedLiveResponse(
memberId: 2,
nickname: "크리에이터 2",
profileImageUrl: "https://cf.sodalive.net/profile/34638/34638-profile-5bfc2bac-3278-48f8-b60c-1294b615f629-8832-1751707083877",
timeAgo: "1시간전"
timeAgo: "1시간전",
dateUtc: "2025-08-10T15:00:00"
)
]
)