CLAUDE.md v2.0 — 기획서(v1.0)를 폐기하고 실제 구현 기준의 현행 명세로 전면 재작성: 프로젝트 구조·빌드/검증 루틴·도메인 모델(CloudKit 규칙)·집계/판정 규칙·데이터 계층 (LocalPrefs/DataChange)·화면별 상세·내보내기·프리미엄/StoreKit·위젯 6종·워치·시리· DEBUG 런치 인자·작업 컨벤션까지 새 세션이 이 문서만으로 파악 가능하게 정리. 동작 보존 범위의 안전 수정 3건: - Color.hexString: 와이드 컬러(P3) 성분을 0...1로 클램프 — 음수/1 초과 값이 %02X를 거치며 자릿수 깨진 hex가 태그·목표 색으로 저장되던 잠재 버그 방지 - 일기 타임테이블 필터: 선택 즉시 명시적 save — 캘린더 일정 선택 경로와 동일하게 영속을 보장 (autosave 의존 제거) - 통계 평균 "%.1f회"가 String(format:)이라 영어·일본어에서도 '회'로 노출되던 것을 Format.countAverage로 지역화 (en "times" / ja "回", 카탈로그 동기화 포함) Debug·Store(Release) 스킴 빌드 검증 완료. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
71 lines
2.5 KiB
Swift
71 lines
2.5 KiB
Swift
//
|
|
// Formatters.swift
|
|
// Haru_Danim
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum Format {
|
|
/// 1:23:45 또는 23:45 형식 (진행 중 타이머용)
|
|
static func timer(_ interval: TimeInterval) -> String {
|
|
let total = Int(interval.rounded(.down))
|
|
let h = total / 3600
|
|
let m = (total % 3600) / 60
|
|
let s = total % 60
|
|
if h > 0 { return String(format: "%d:%02d:%02d", h, m, s) }
|
|
return String(format: "%d:%02d", m, s)
|
|
}
|
|
|
|
/// "2시간 30분" / "45분" / "30초" 형식
|
|
static func durationShort(_ interval: TimeInterval) -> String {
|
|
let total = Int(interval.rounded())
|
|
let h = total / 3600
|
|
let m = (total % 3600) / 60
|
|
if h > 0 {
|
|
return m > 0
|
|
? String(localized: "\(h)시간 \(m)분")
|
|
: String(localized: "\(h)시간")
|
|
}
|
|
if m > 0 { return String(localized: "\(m)분") }
|
|
return String(localized: "\(total)초")
|
|
}
|
|
|
|
static func weekdayShort(_ weekday: Int) -> String {
|
|
let names = [
|
|
String(localized: "일", comment: "요일 축약(일요일)"),
|
|
String(localized: "월", comment: "요일 축약(월요일)"),
|
|
String(localized: "화", comment: "요일 축약(화요일)"),
|
|
String(localized: "수", comment: "요일 축약(수요일)"),
|
|
String(localized: "목", comment: "요일 축약(목요일)"),
|
|
String(localized: "금", comment: "요일 축약(금요일)"),
|
|
String(localized: "토", comment: "요일 축약(토요일)"),
|
|
]
|
|
let index = (weekday - 1) % 7
|
|
return names[max(0, index)]
|
|
}
|
|
|
|
static func shortDate(_ date: Date) -> String {
|
|
date.formatted(.dateTime.month(.defaultDigits).day())
|
|
}
|
|
|
|
static func fullDate(_ date: Date) -> String {
|
|
date.formatted(.dateTime.year().month().day().weekday(.short))
|
|
}
|
|
|
|
static func time(_ date: Date) -> String {
|
|
date.formatted(.dateTime.hour().minute())
|
|
}
|
|
|
|
static func percent(_ ratio: Double) -> String {
|
|
"\(Int((ratio * 100).rounded()))%"
|
|
}
|
|
|
|
/// 횟수 평균 표기 ("3.5회"). String(format:)은 카탈로그로 추출되지 않아
|
|
/// 영어·일본어에서 '회'가 그대로 노출되던 것을 지역화한다.
|
|
static func countAverage(_ value: Double) -> String {
|
|
let rounded = (value * 10).rounded() / 10
|
|
return String(localized: "\(rounded.formatted(.number.precision(.fractionLength(1))))회",
|
|
comment: "횟수 평균 (소수 1자리)")
|
|
}
|
|
}
|