커뮤니티 게시글 상대 시간 표기 다국어 지원
This commit is contained in:
@@ -58,7 +58,7 @@ struct CreatorCommunityAllItemView: View {
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(item.date)
|
||||
Text(item.relativeTimeText())
|
||||
.font(.custom(Font.light.rawValue, size: 13.3))
|
||||
.foregroundColor(Color.gray77)
|
||||
}
|
||||
@@ -175,6 +175,7 @@ struct CreatorCommunityAllItemView_Previews: PreviewProvider {
|
||||
content: "너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!너무 조하유 앞으로도 좋은 라이브 많이 들려주세요!",
|
||||
price: 10,
|
||||
date: "3일전",
|
||||
dateUtc: "2025-08-10T15:00:00",
|
||||
isCommentAvailable: false,
|
||||
isAdult: false,
|
||||
isLike: true,
|
||||
|
||||
@@ -26,7 +26,7 @@ struct CreatorCommunityItemView: View {
|
||||
.font(.custom(Font.preBold.rawValue, size: 18))
|
||||
.foregroundColor(Color.white)
|
||||
|
||||
Text(item.date)
|
||||
Text(item.relativeTimeText())
|
||||
.font(.custom(Font.preRegular.rawValue, size: 14))
|
||||
.foregroundColor(Color(hex: "78909C"))
|
||||
}
|
||||
@@ -100,6 +100,7 @@ struct CreatorCommunityItemView_Previews: PreviewProvider {
|
||||
content: "안녕하세요",
|
||||
price: 10,
|
||||
date: "3일전",
|
||||
dateUtc: "2025-08-10T15:00:00",
|
||||
isCommentAvailable: false,
|
||||
isAdult: false,
|
||||
isLike: false,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
// Created by klaus on 2023/12/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct GetCommunityPostListResponse: Decodable {
|
||||
let postId: Int
|
||||
let creatorId: Int
|
||||
@@ -15,6 +17,7 @@ struct GetCommunityPostListResponse: Decodable {
|
||||
let content: String
|
||||
let price: Int
|
||||
let date: String
|
||||
let dateUtc: String
|
||||
let isCommentAvailable: Bool
|
||||
let isAdult: Bool
|
||||
let isLike: Bool
|
||||
@@ -23,3 +26,96 @@ struct GetCommunityPostListResponse: Decodable {
|
||||
let commentCount: Int
|
||||
let firstComment: GetCommunityPostCommentListItem?
|
||||
}
|
||||
|
||||
// MARK: - 상대 시간 문자열
|
||||
extension GetCommunityPostListResponse {
|
||||
/// `dateUtc`(UTC 문자열)을 파싱해 디바이스 타임존 기준
|
||||
/// 상대 시간(방금 전/분/시간/일/개월/년 전)을 반환합니다.
|
||||
/// 파싱 실패 시, `date`를 그대로 반환합니다.
|
||||
func relativeTimeText(now: Date = Date()) -> String {
|
||||
guard let createdAt = DateParser.parse(dateUtc) else {
|
||||
return date
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user