일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- Concurrency
- SwiftUI
- 알고리즘
- 달력
- dataflow
- CS
- state
- stateobject
- Algorithm
- UIKit
- firebase
- RxSwift
- withAnimation
- swift
- auth
- 최적화
- view
- gesture
- WWDC
- date
- GCD
- authentication
- combine
- Animation
- ios
- 네트워크
- arkit
- iphone
- Performance
- Network
Archives
- Today
- Total
XLOG
Swift에서 시간, 날짜 다루기 본문
한번씩 하려다보면 항상 검색을 해야해서 정리해보았다...
1. Date - 기본 날짜/시간 타입
Date
는 Swift의 핵심 날짜/시간 타입으로, Unix 타임스탬프(1970년 1월 1일 00:00:00 UTC부터의 초)를 기반으로 한다.
// 현재 시간
let now = Date()
// 특정 시간 생성
let specificDate = Date(timeIntervalSince1970: 1640995200) // 2022-01-01 00:00:00 UTC
// 상대적 시간 생성
let oneHourAgo = Date(timeIntervalSinceNow: -3600)
let tomorrow = Date(timeIntervalSinceNow: 86400)
2. TimeInterval - 시간 간격
TimeInterval
은 Double
타입의 별칭으로, 초 단위의 시간 간격을 나타낸다. 마치 두 지점 사이의 거리를 측정하는 자처럼 작동한다.
let interval: TimeInterval = 3600 // 1시간 = 3600초
let start = Date()
let end = Date(timeIntervalSinceNow: 7200) // 2시간 후
// 두 날짜 간의 차이 계산
let difference = end.timeIntervalSince(start) // 7200.0초
3. DateComponents - 날짜 구성 요소
DateComponents
는 날짜를 년, 월, 일, 시, 분, 초 등의 개별 구성요소로 분해하거나 조합할 때 사용한다.
var components = DateComponents()
components.year = 2024
components.month = 7
components.day = 9
components.hour = 14
components.minute = 30
// Calendar를 사용해 Date로 변환
let calendar = Calendar.current
let date = calendar.date(from: components)
4. Calendar - 달력 시스템
Calendar
는 날짜 계산의 핵심 엔진
let calendar = Calendar.current
// Date를 DateComponents로 분해
let components = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: Date())
// 날짜 계산
let nextWeek = calendar.date(byAdding: .day, value: 7, to: Date())
let startOfDay = calendar.startOfDay(for: Date())
// 두 날짜 사이의 차이
let difference = calendar.dateComponents([.day, .hour], from: Date(), to: nextWeek!)
5. DateFormatter - 문자열 변환
DateFormatter
는 Date와 String 사이의 번역사 역할
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
formatter.locale = Locale(identifier: "ko_KR")
formatter.timeZone = TimeZone(identifier: "Asia/Seoul")
// Date -> String
let dateString = formatter.string(from: Date())
// String -> Date
let dateFromString = formatter.date(from: "2024-07-09 14:30:00")
// 미리 정의된 스타일 사용
formatter.dateStyle = .medium
formatter.timeStyle = .short
let readableString = formatter.string(from: Date()) // "2024. 7. 9. 오후 2:30"
6. ISO 8601 형식 다루기
6.1 ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
// 기본 형식 (UTC 기준)
let basicISO = isoFormatter.string(from: Date())
// "2024-07-09T05:30:00Z"
// 다양한 형식 옵션
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let preciseISO = isoFormatter.string(from: Date())
// "2024-07-09T14:30:00.123Z"
// 시간대 지정
isoFormatter.timeZone = TimeZone(identifier: "Asia/Seoul")
isoFormatter.formatOptions = [.withInternetDateTime]
let localISO = isoFormatter.string(from: Date())
// "2024-07-09T23:30:00+09:00"
// String -> Date 변환
let dateFromISO = isoFormatter.date(from: "2024-07-09T14:30:00Z")
6.2 DateFormatter로 ISO 8601 다루기
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX") // 필수!
dateFormatter.timeZone = TimeZone(identifier: "UTC")
let isoString = dateFormatter.string(from: Date())
// "2024-07-09T14:30:00+0000"
6.3 ISO8601DateFormatter vs DateFormatter 비교
구분 | ISO8601DateFormatter | DateFormatter |
---|---|---|
로케일 안전성 | ✅ 자동으로 안전 | ⚠️ 수동 설정 필요 |
표준 준수 | ✅ 완벽한 표준 준수 | ⚠️ 개발자 실수 가능 |
파싱 유연성 | ✅ 다양한 형식 자동 지원 | ❌ 정확한 형식만 파싱 |
성능 | ✅ 최적화된 구현 | ⚠️ 설정 오버헤드 |
커스터마이징 | ❌ 제한적 | ✅ 자유로운 형식 |
로케일 안전성 문제
// ❌ 위험한 방식 - 사용자 로케일에 영향받을 수 있음
let unsafeFormatter = DateFormatter()
unsafeFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
// 아랍어나 힌디어 로케일에서 아라비아 숫자 대신 다른 숫자 체계 사용 가능
// ✅ 안전한 DateFormatter 방식
let safeFormatter = DateFormatter()
safeFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
safeFormatter.locale = Locale(identifier: "en_US_POSIX") // 필수!
// ✅ 가장 안전한 방식
let isoFormatter = ISO8601DateFormatter()
파싱 유연성 비교
// DateFormatter는 정확한 형식만 파싱 가능
let strictFormatter = DateFormatter()
strictFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
strictFormatter.locale = Locale(identifier: "en_US_POSIX")
// ❌ 이런 변형들은 파싱 실패
let variations = [
"2024-07-09T14:30:00Z", // Z 표기
"2024-07-09T14:30:00+00:00", // 오프셋 표기
"2024-07-09T14:30:00.123Z" // 밀리초 포함
]
// ✅ ISO8601DateFormatter는 다양한 변형 자동 처리
let flexibleISO = ISO8601DateFormatter()
for variation in variations {
if let date = flexibleISO.date(from: variation) {
print("파싱 성공: \(date)")
}
}
성능 및 메모리 효율성
// DateFormatter - 더 많은 설정 필요
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(identifier: "UTC")
// ISO8601DateFormatter - 최적화된 내부 구현
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
// 성능 테스트 예제
func performanceTest() {
let dates = (0..<1000).map { _ in Date() }
// DateFormatter 성능
let start1 = Date()
for date in dates {
let _ = dateFormatter.string(from: date)
}
let dateFormatterTime = Date().timeIntervalSince(start1)
// ISO8601DateFormatter 성능
let start2 = Date()
for date in dates {
let _ = isoFormatter.string(from: date)
}
let isoFormatterTime = Date().timeIntervalSince(start2)
print("DateFormatter: \(dateFormatterTime)s")
print("ISO8601DateFormatter: \(isoFormatterTime)s")
}
언제 어떤 것을 사용할까?
ISO8601DateFormatter 사용 권장:
- API 통신
- 로그 시스템
- 데이터베이스 저장
- 국제화된 앱
- 시스템 간 데이터 교환
DateFormatter 사용 권장:
- 커스텀 형식이 필요한 경우
- 사용자 인터페이스 표시
- 레거시 시스템 호환성
// ✅ ISO8601DateFormatter - API 통신
let apiFormatter = ISO8601DateFormatter()
let apiString = apiFormatter.string(from: Date())
// ✅ DateFormatter - 사용자 표시용
let displayFormatter = DateFormatter()
displayFormatter.dateStyle = .medium
displayFormatter.timeStyle = .short
displayFormatter.locale = Locale.current
let userString = displayFormatter.string(from: Date())
7. 실용적인 확장 예제
extension Date {
// 사용자 친화적인 상대 시간
var timeAgoDisplay: String {
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .full
return formatter.localizedString(for: self, relativeTo: Date())
}
// ISO 8601 문자열 (권장 방식)
var iso8601String: String {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return formatter.string(from: self)
}
// 특정 시간대의 문자열 반환
func string(format: String, timeZone: TimeZone = .current) -> String {
let formatter = DateFormatter()
formatter.dateFormat = format
formatter.timeZone = timeZone
formatter.locale = Locale(identifier: "en_US_POSIX")
return formatter.string(from: self)
}
// 하루의 시작/끝
var startOfDay: Date {
Calendar.current.startOfDay(for: self)
}
var endOfDay: Date {
Calendar.current.date(bySettingHour: 23, minute: 59, second: 59, of: self) ?? self
}
}
extension TimeInterval {
// TimeInterval을 읽기 쉬운 형태로
var readableTime: String {
let hours = Int(self) / 3600
let minutes = Int(self) % 3600 / 60
let seconds = Int(self) % 60
if hours > 0 {
return String(format: "%d:%02d:%02d", hours, minutes, seconds)
} else {
return String(format: "%02d:%02d", minutes, seconds)
}
}
}
8. 성능 최적화
Formatter들은 생성 비용이 높으므로 재사용하는 것이 좋습니다:
https://velog.io/@qwerty3345/Swift-DateFormatter는-당신의-생각보다-비싸다
핵심 원리
- Date: 절대적인 시점 (Unix 타임스탬프 기반)
- Calendar: 달력 시스템과 계산 엔진
- DateComponents: 인간이 이해하는 날짜 구성요소
- DateFormatter/ISO8601DateFormatter: 표현 계층 (문자열 변환)
ISO 8601 처리에서는 ISO8601DateFormatter
를 우선적으로 사용하되, 특별한 커스터마이징이 필요한 경우에만 DateFormatter
를 신중하게 사용하는 것이 좋습니다. 또한 함수 내부에서 DateFormatter를 생성 후 변환을 하게 되면 매번 Formatter 생성 비용이 발생하므로, 날짜를 자주 다룬다면 전역적으로 관리하는 것에 대한 고민을 해보는 것이 좋습니다.
'Swift' 카테고리의 다른 글
실시간 STT (Sound to Text) 구현하기 (1) | 2024.12.22 |
---|---|
Fatal error: Duplicate keys of type 'Node' were found in a Dictionary 해결 (0) | 2024.08.16 |
[Swift] CoreData (0) | 2024.06.08 |
[Swift] IAP, 인앱결제 StoreKit2 (2) | 2024.05.19 |
[Swift] Concurrency (0) | 2024.05.19 |