DateFormatter는 Date 객체와 문자열 표현 간의 양방향 변환을 위해 설계된 Foundation 클래스입니다. 이 클래스는 사용자의 로케일, 시간대 및 달력을 고려하여 세계 모든 지역에서 날짜를 올바르게 표시합니다. Apple Developer Documentation(2025)에 따르면 DateFormatter는 네 가지 사전 설정된 날짜 및 시간 스타일과 템플릿 문자열을 통한 완전 사용자 정의 형식을 지원합니다. DateFormatter 없이는 국제화된 애플리케이션에서 사용자에게 날짜를 올바르게 표시하는 것이 불가능합니다.
주요 내용
DateFormatter는 Foundation 프레임워크의 클래스로, Date와 문자열 간의 양방향 변환을 구현합니다. OpenStep에서 NSDateFormatter로 처음 등장했으며 그 이후로 모든 Apple 플랫폼에서 날짜 형식 지정의 주요 도구로 남아 있습니다. 이 클래스는 Formatter에서 상속되며 지역화된 날짜 표시를 위한 편리한 API를 제공합니다.
DateFormatter는 Unicode LDML 패턴을 기반으로 작동합니다 — ICU(International Components for Unicode)에서 사용되는 것과 동일합니다. 패턴은 dateFormat 속성을 통해 설정되며, 기호 y, M, d, H, m, s는 각각 연도, 월, 일, 시간, 분, 초에 해당합니다. 기호의 반복이 형식을 결정합니다: “y”(두 자리 연도), “yyyy”(네 자리 연도).
DateFormatter 생성은 비용이 많이 드는 작업입니다. 초기화 중에 로케일 및 달력 데이터가 로드되기 때문입니다. Apple은 각 형식 지정 유형에 대해 포맷터를 한 번 만들고 재사용할 것을 권장합니다. SwiftUI 및 UIKit에서 포맷터는 종종 정적 속성에 캐시되거나 첫 번째 액세스 시 지연 생성됩니다.
DateFormatter는 많은 iOS 시스템 구성 요소에서 사용됩니다. UIDatePicker는 countDownTimer 모드에서 날짜를 표시하기 위해 내부적으로 DateFormatter를 사용합니다. 포맷터가 있는 TextField는 사용자가 입력한 날짜를 자동으로 검증할 수 있습니다. Core Data는 Date 유형의 속성을 지원하지만, 문자열 표현은 항상 DateFormatter를 통해 처리됩니다.
스레드 안전성 — DateFormatter는 스레드 안전하지 않습니다. 다른 스레드에서 포맷터 속성을 수정하면 정의되지 않은 동작이 발생합니다. 다중 스레드 사용을 위해 각 스레드에 대해 별도의 포맷터 인스턴스를 생성하거나 NSLock 또는 직렬 큐를 통한 동기화를 사용하세요.
dateStyle과 timeStyle은 날짜 표시를 구성하는 가장 간단한 방법입니다. 각 스타일에는 네 가지 변형이 있습니다: .short, .medium, .long, .full. dateStyle과 timeStyle의 조합을 통해 날짜 및 시간 형식을 독립적으로 구성할 수 있으며, .none 속성은 해당 부분을 비활성화합니다.
미국 로케일의 경우 .short는 날짜를 “7/21/26”으로 형식 지정하고, 러시아 로케일의 경우 “21.07.2026”으로 지정합니다. 러시아 로케일의 .long 스타일은 “2026년 7월 21일”을 출력하고, .full은 “2026년 7월 21일 화요일”을 요일과 함께 출력합니다. 네 가지 스타일 모두 구성 요소 순서와 구분 기호를 포함하여 지역 표준에 자동으로 적응합니다.
iOS 15+의 SFDateFormatter는 RelativeDateFormatter 및 DateIntervalFormatter를 통해 대체 접근 방식을 제공합니다. RelativeDateFormatter는 컨텍스트 표시를 위해 “오늘”, “어제”, “3일 후”를 출력합니다. DateIntervalFormatter는 날짜 범위를 표시합니다: “2026년 7월 21일–25일” — 예약 및 계획용입니다.
| 스타일 | 예(ru_RU) | 예(en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 2026년 7월 21일 | Jul 21, 2026 |
| .long | 2026년 7월 21일 | July 21, 2026 |
| .full | 2026년 7월 21일 화요일 | Tuesday, July 21, 2026 |
스타일을 결합할 때 DateFormatter는 자동으로 구분 기호를 선택합니다: .short.date + .short.time의 경우 결과는 “2026. 7. 21. 14:30”이 될 수 있습니다. .full.date + .full.time의 경우 — “2026년 7월 21일 화요일 14:30:00 MSK”. 구분 기호는 개발자가 아닌 로케일에 의해 관리됩니다 — 이는 사용자의 지역적 기대에 부합하도록 보장합니다.
dateFormat을 사용하면 Unicode LDML 사양 기호를 사용하여 임의의 형식 패턴을 설정할 수 있습니다. 이를 통해 표시를 완전히 제어할 수 있습니다: 연도와 월만 표시하거나, 날짜 없이 요일만, 또는 초 없이 시간만 표시할 수 있습니다. 사용자 정의 형식은 특정 디자인 요구 사항에 필수적입니다.
주요 기호 — yyyy(연도: 2026), MM(월: 07), dd(일: 21), HH(시간: 14), mm(분: 30), ss(초: 00). 전체 월 이름에는 MMMM(7월), 약어에는 MMM(7월)을 사용합니다. 요일 — EEEE(화요일), 약어 — E(화).
dateFormat을 사용할 때는 포맷터의 로케일을 설정하는 것이 중요합니다. 로케일이 설정되지 않은 경우 포맷터는 시스템 로케일을 사용하며, 이는 API에서 고정 형식에 바람직하지 않을 수 있습니다. Apple은 특히 서버 응답에서 날짜를 구문 분석할 때 locale = Locale(identifier: “en_US_POSIX”)로 고정 지역 간 형식을 설정할 것을 권장합니다.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// 사용자 정의 문자열 구문 분석
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
dateFormat의 오류는 애플리케이션 충돌의 일반적인 원인 중 하나입니다. 형식이 문자열과 일치하지 않으면 date(from:) 메서드가 nil을 반환합니다. 안전한 옵셔널 언래핑을 위해 guard let 또는 ??를 사용하세요. 형식을 검증하려면 지원되는 모든 언어에서 테스트하세요 — 일부 LDML 기호는 로케일에 따라 다르게 작동합니다.
Locale은 월 이름, 요일 이름 및 사용되는 구분 기호를 결정합니다. DateFormatter는 기본적으로 Locale.current를 사용하지만, 일부 시나리오에서는 특정 로케일을 지정해야 합니다: 로그의 고정 형식에는 en_US_POSIX를, 서버 날짜에는 서버와 일치하는 로케일을 사용하세요.
TimeZone 속성은 표시할 시간대를 결정합니다. 기본적으로 시스템 시간대가 사용되지만, 국제 사용자를 대상으로 하는 애플리케이션에서는 종종 사용자의 시간대 또는 UTC로 날짜를 표시해야 합니다. timeZone을 변경하면 표시에만 영향을 미칩니다 — Date 값은 변경되지 않습니다.
중요한 기능: DateFormatter가 문자열 구문 분석에 사용되고 문자열에 시간대 표시가 포함된 경우(예: UTC의 Z가 있는 “2026-07-21T14:30:00Z”), timeZone 속성은 무시됩니다 — 포맷터는 문자열의 시간대를 사용합니다. 문자열에 시간대가 없는 경우 포맷터의 timeZone이 적용됩니다.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// 문자열에서 시간대 없이 구문 분석
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — 사용자의 시스템 설정이 변경될 때 자동으로 업데이트되는 특수 로케일 유형입니다. DateFormatter는 기본적으로 이를 지원합니다. 애플리케이션이 백그라운드에서 실행 중이고 사용자가 시스템 언어를 변경하는 경우, 변경 전에 생성된 포맷터는 이전 로케일을 계속 사용합니다 — 업데이트하려면 새 인스턴스를 생성해야 합니다.
ISO8601DateFormatter는 ISO 8601 형식의 날짜 작업을 위한 특수 포맷터입니다. 이 형식은 REST API, JSON 및 데이터 교환의 사실상 표준입니다. ISO8601DateFormatter는 로케일에 의존하지 않고 고정된 구문 분석 문법을 사용하기 때문에 DateFormatter보다 훨씬 빠르게 작동합니다.
주요 포맷터 옵션 — .withInternetDateTime(2026-07-21T14:30:00Z), .withFractionalSeconds(밀리초 추가), .withTimeZone(시간대 오프셋 포함). 옵션을 결합하여 모든 ISO 8601 변형을 얻을 수 있습니다: 밀리초 포함, 시간대 포함, 날짜만 등.
JSONEncoder.DateEncodingStrategy를 사용하면 모든 Codable 모델에 대한 날짜 인코딩을 전역적으로 구성할 수 있습니다. 옵션 — .iso8601(ISO8601DateFormatter 사용), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. 전략 선택은 전체 직렬화 수명 주기에 영향을 미치며 모든 API 엔드포인트에서 일관되어야 합니다.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// ISO8601과 함께하는 JSONEncoder
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// 대안: 사용자 정의 포맷터와 함께하는 JSONEncoder
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter vs ISO8601DateFormatter — API에서 날짜 직렬화 및 구문 분석에는 ISO8601DateFormatter를 선택하세요. DateFormatter보다 5-10배 빠르며 지역화 오류가 발생하지 않습니다. DateFormatter는 사용자의 모국어로 월 및 요일 이름이 포함된 지역화된 표시가 필요한 사용자 인터페이스용으로 사용하세요.
iOS 애플리케이션에서 DateFormatter의 실제 사용 시나리오를 살펴보겠습니다: 뉴스 피드의 날짜 표시, 생년월일 입력, 다른 시간대의 날짜가 포함된 보고서 내보내기.
RelativeDateFormatter는 뉴스 피드에 최적입니다. 새 뉴스에는 “방금”, “5분 전”, “어제”를 표시하고 오래된 뉴스에는 전체 날짜로 전환합니다. 전환 임계값은 달력을 통해 구성됩니다: 뉴스의 경우 24시간 임계값을, 메신저의 경우 일주일을 사용하세요.
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
생년월일 입력 — 또 다른 일반적인 시나리오입니다. DateFormatter는 특정 dateFormat “dd.MM.yyyy” 및 locale “ru_RU”로 구성됩니다. 입력된 문자열을 구문 분석할 때 가능한 오류를 처리하는 것이 중요합니다: 포맷터는 잘못된 문자열에 대해 nil을 반환합니다. 성공적인 구문 분석 후 날짜가 허용 가능한 범위 내에 있는지 확인됩니다 — 1900년 이전이 아니고 오늘 이후가 아닙니다.
날짜가 포함된 보고서 내보내기는 사용자 로케일과 무관한 고정 형식이 필요합니다. locale en_US_POSIX 및 시간대 UTC와 함께 dateFormat “yyyy-MM-dd HH:mm:ss”를 사용하세요. 이 접근 방식은 시스템의 지역 설정에 관계없이 파일이 모든 국가에서 올바르게 열리도록 보장합니다.
| 시나리오 | 포맷터 | 주요 설정 |
|---|---|---|
| 뉴스 피드 | RelativeDateFormatter | unitsStyle = .full |
| 날짜 입력 | DateFormatter | dateFormat + 폴백 |
| API 직렬화 | ISO8601DateFormatter | withInternetDateTime |
| 보고서 내보내기 | DateFormatter | en_US_POSIX + UTC |
자주 묻는 질문
가장 일반적인 이유 — dateFormat과 문자열 형식 간의 불일치입니다. 예를 들어, 형식 “dd.MM.yyyy”는 문자열 “2026-07-21”을 구문 분석하지 않습니다. 두 번째 이유 — 로케일 불일치: 문자열 “July 21, 2026”은 ru_RU 로케일로 구문 분석되지 않습니다. 세 번째 — LDML 기호의 오타: yyyy를 사용하세요(YYY가 아닙니다. 의미가 다릅니다).
아니요. DateFormatter는 무거운 객체이며 초기화에 로케일 데이터 로드가 포함됩니다. 형식 지정 유형당 하나의 인스턴스를 만들고 재사용하세요. 다중 스레드 환경에서는 스레드-로컬 저장소 또는 직렬 큐가 있는 포맷터 풀을 동기화에 사용하세요.
DateFormatter는 절대 날짜(2026년 7월 21일)를 표시하는 반면, RelativeDateFormatter는 상대 날짜(오늘, 어제, 3일 후)를 표시합니다. RelativeDateFormatter는 iOS 15+에서 도입되었으며 동일한 LDML 템플릿을 사용하지만 자동으로 상대 표시를 선택합니다.
구문 분석 전에 포맷터의 timeZone을 UTC로 설정하세요. 서버가 시간대 표시 없이 현지 시간으로 날짜를 반환하는 경우 API 사양을 확인하세요 — 대부분 UTC를 의미합니다. 끝에 Z가 있는 ISO 8601의 경우 timeZone이 필요하지 않습니다 — 포맷터가 문자열에서 오프셋을 구문 분석합니다.
동기화 없이 다른 스레드에서 단일 인스턴스를 사용하지 마세요. 각 스레드에서 새 인스턴스를 만들거나 저장소에 Thread.current.threadDictionary를 사용하세요. 대안으로 string(from:) 및 date(from:) 기간 동안 잠금하는 NSLock이 있습니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.