ISO8601DateFormatter는 국제 표준 ISO 8601에 따라 날짜를 포맷하고 파싱하기 위해 설계된 iOS 및 macOS의 Foundation 클래스입니다. Apple Developer Documentation, 2024에 따르면, ISO8601DateFormatter는 DateFormat을 수동으로 설정할 필요 없이 밀리초, 시간대 및 초의 분수를 자동으로 처리합니다. DateFormatter와 달리 이 클래스는 Locale 및 TimeZone에 의존하지 않으며 ISO 8601 사양에 따라 엄격하게 작동하므로 서버와 클라이언트 간의 날짜 교환에 이상적입니다. 이 클래스는 iOS 10 및 macOS 10.12부터 사용할 수 있습니다.
주요 내용
ISO8601DateFormatter는 Foundation에서 Formatter의 특수화된 하위 클래스로, Date와 ISO 8601 형식 문자열 간의 양방향 변환을 구현합니다. ISO 8601 표준(International Standard for the Representation of Dates and Times)은 날짜와 시간 교환을 위한 국제 형식을 정의합니다: 2024-07-21T14:30:00+00:00. DateFormatter와 달리 이 클래스는 dateFormat을 지정할 필요가 없으며 주어진 옵션에 따라 문자열 구조를 자동으로 결정합니다.
DateFormatter에 비한 ISO8601DateFormatter의 주요 장점: 로케일 의존성 없음(파싱이 모든 기기에서 동일하게 작동), 초 분수에 대한 내장 지원(모든 소수 자릿수), 전달된 옵션에 기반한 자동 형식 감지. 이 클래스는 또한 Z 접미사(UTC 지정), +HH:mm 형식의 시간대 및 축소된 정밀도(시간 없는 날짜만)를 올바르게 처리합니다.
ISO 사양(ISO 8601-1:2019)에 따르면, 표준은 네 가지 정밀도 수준을 지원합니다: 연도(2024), 연-월(2024-07), 전체 날짜(2024-07-21) 및 시간대가 있는 날짜-시간(2024-07-21T14:30:00+00:00). ISO8601DateFormatter는 형식 옵션의 조합을 통해 이러한 모든 수준을 지원하며, 개발자가 수동으로 dateFormat 문자열을 구성할 필요가 없습니다.
작동 원리 ISO8601DateFormatter는 비트 옵션(formatOptions)의 조합에 기반하며, 각 옵션은 출력에 특정 날짜 또는 시간 구성 요소를 포함합니다. 예를 들어, .withFullDate 옵션은 연도, 월, 일을 포함하고; .withTime은 시, 분, 초를 포함합니다. 옵션을 결합하여 개발자는 dateFormat 문자열을 작성하지 않고도 원하는 정밀도 수준을 얻을 수 있습니다.
내부적으로 ISO8601DateFormatter는 파싱을 위해 ICU 라이브러리를 사용하지만 고정된 ISO 8601 규칙을 따릅니다. 즉, 기기의 Locale 및 TimeZone 설정을 무시하며 결과는 항상 예측 가능합니다. 시간대 설정에는 timeZone 속성이 사용되며 기본값은 UTC입니다. timeZone이 nil로 설정된 경우 기기의 로컬 시간이 사용됩니다.
| 옵션 | 설명 | 출력 예시 |
|---|---|---|
| .withFullDate | 연도, 월, 일 | 2024-07-21 |
| .withTime | 시, 분, 초 | 14:30:00 |
| .withMilliseconds | 초의 분수(최대 3자리) | .123 |
| .withFractionalSeconds | 초의 분수(모든 정밀도) | .123456 |
| .withTimeZone | 시간대 | +03:00 |
| .withColonSeparatorInTimeZone | 시간대의 콜론 구분자 | +03:00(+0300과 비교) |
| .withInternetDateTime | 전체 형식(날짜 + 시간 + 시간대) | 2024-07-21T14:30:00+00:00 |
옵션 조합: .withInternetDateTime은 .withFullDate, .withTime 및 .withTimeZone을 결합한 것과 동일합니다. 밀리초가 포함된 문자열을 파싱하려면 .withFractionalSeconds를 추가하세요. .withMilliseconds는 초의 분수를 세 자리로 제한하는 반면, .withFractionalSeconds는 소수점 이하 1~9자리의 모든 정밀도를 지원한다는 점을 기억하는 것이 중요합니다.
형식 옵션 ISO8601DateFormatter의 옵션은 세 그룹으로 나뉩니다: 날짜 구성 요소(withFullDate, withYear, withMonth, withDay, withWeekOfYear), 시간 구성 요소(withTime, withHours, withMinutes, withSeconds) 및 추가 설정(withMilliseconds, withFractionalSeconds, withTimeZone, withColonSeparatorInTimeZone, withDashSeparatorInDate, withFullTime). 이들을 결합하여 거의 모든 ISO 8601 하위 형식을 얻을 수 있습니다.
중요 참고 사항: .withFractionalSeconds와 .withMilliseconds는 상호 배타적입니다. 둘 다 설정된 경우 .withFractionalSeconds가 적용됩니다. 서버 데이터에서 밀리초를 파싱하려면 .withFractionalSeconds를 권장합니다. 많은 서버가 3자리, 6자리 또는 9자리의 초의 분수를 보내며 .withFractionalSeconds는 모든 길이를 처리할 수 있습니다.
import Foundation
// Configure ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Different format option combinations
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Date: \(dateOnly)")
formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime: \(dateTime)")
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("Full: \(full)")
// Parse string with milliseconds
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Parsed: \(parsed)")
}
기본 사용법 ISO8601DateFormatter의 사용은 인스턴스 생성, timeZone(서버 데이터에는 UTC 권장) 및 formatOptions 설정으로 시작되며, 그런 다음 포맷을 위해 string(from:)을, 파싱을 위해 date(from:)를 호출할 수 있습니다. DateFormatter와 달리 Locale에 대해 걱정할 필요가 없습니다 — 클래스가 지역 설정을 무시합니다.
import Foundation
let formatter = ISO8601DateFormatter()
// Parse different ISO 8601 formats
let strings: [String] = [
"2024-07-21T14:30:00Z",
"2024-07-21T14:30:00+03:00",
"2024-07-21T14:30:00.123Z",
"2024-07-21"
]
for str in strings {
if let autoParsed = formatter.date(from: str) {
print("Parsed '\(str)': \(autoParsed)")
} else {
// Use withFullDate for date-only strings
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback parsed '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serialize to RFC 3339 (GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
가변 길이 초의 분수가 있는 날짜 파싱은 많은 최신 API의 기능입니다. 서버는 2024-07-21T14:30:00.123Z(3자리) 또는 2024-07-21T14:30:00.123456Z(6자리)를 보낼 수 있습니다. .withFractionalSeconds 옵션이 있는 ISO8601DateFormatter는 두 변형을 모두 올바르게 처리하는 반면, dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"가 있는 DateFormatter는 세 자리 밀리초만 처리합니다.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Different fractional second precision
let variants: [String] = [
"2024-07-21T14:30:00.1Z",
"2024-07-21T14:30:00.12Z",
"2024-07-21T14:30:00.123Z",
"2024-07-21T14:30:00.123456Z",
"2024-07-21T14:30:00.123456789Z"
]
for variant in variants {
if let parsed = variantFormatter.date(from: variant) {
print("OK: \(variant) -> \(parsed)")
} else {
print("FAIL: \(variant)")
}
}
// Use withMilliseconds (3 digits only)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("With milliseconds: \(milliParsed)")
모든 변형의 파싱 테스트: 표시된 코드는 .withFractionalSeconds가 있는 ISO8601DateFormatter가 1~9자리의 모든 길이의 초의 분수를 성공적으로 처리함을 보여줍니다. 이는 다양한 서버 플랫폼과의 호환성에 중요합니다: .NET은 종종 7자리(100나노초 틱)를 생성하고, Python은 6자리, Java는 버전에 따라 3자리 또는 9자리를 생성합니다.
DateFormatter도 ISO 8601을 파싱할 수 있지만 dateFormat, locale 및 timeZone의 수동 설정이 필요합니다. 주요 문제는 DateFormatter가 Locale에 의존하며 en_US_POSIX를 설정하지 않으면 비표준 날짜 형식을 사용하는 지역의 사용자에게 파싱이 실패할 수 있다는 것입니다. ISO8601DateFormatter는 아키텍처 수준에서 이 문제를 해결합니다: Locale을 사용하지 않습니다.
| 매개변수 | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| Locale 설정 | 필요 없음(무시) | en_US_POSIX 필수 |
| DateFormat | 자동(옵션을 통해) | 수동 형식 문자열 |
| 초의 분수 | 모든 정밀도(.withFractionalSeconds) | 고정 SSS |
| Z 접미사 | 올바르게 처리 | dateFormat을 통해 |
| 성능 | 높음(특화됨) | 낮음(일반) |
| 표준 | ISO 8601만 | 모든 형식 |
| iOS 버전 | iOS 10+ | iOS 2+ |
DateFormatter를 사용해야 하는 경우: ISO 8601이 아닌 형식으로 날짜를 포맷해야 하는 경우(예: UI용 "2024년 7월 21일") 또는 iOS 9 이하를 지원해야 하는 경우. 서버-클라이언트 날짜 교환 작업에는 ISO8601DateFormatter를 사용하세요 — 더 안전하고 효율적이며 코드가 덜 필요합니다. ISO 8601을 위한 DateFormatter는 로케일 및 지역 설정과 관련된 잠재적 버그의 원천입니다.
DateFormatter에서 ISO8601DateFormatter로 마이그레이션: DateFormatter + dateFormat + locale + timeZone 생성을 ISO8601DateFormatter + formatOptions + timeZone으로 대체하세요. date(from:)를 통한 문자열 파싱은 변경되지 않습니다. 이전 버전과의 호환성을 위해 DateFormatter로의 폴백과 함께 #available(iOS 10, *)을 사용할 수 있습니다.
formatOptions 설정 누락으로 인해 포맷터가 기본값인 .withInternetDateTime을 사용하게 됩니다. 서버가 시간 없는 날짜(2024-07-21)를 보내면 파싱이 nil을 반환합니다. formatOptions가 서버에서 올 수 있는 모든 가능한 형식을 포함하는지 항상 확인하세요. 가변 형식의 API의 경우 다른 옵션 조합으로 폴백 시도를 사용하세요.
withMilliseconds와 withFractionalSeconds의 혼동은 초의 분수가 있는 날짜를 파싱할 때 흔한 실수입니다. withMilliseconds는 소수점 이하 정확히 3자리를 기대합니다. 서버가 6자리(마이크로초)를 보내면 withMilliseconds를 사용한 파싱이 실패합니다. 모든 자릿수와의 호환성을 위해 .withFractionalSeconds를 사용하세요. .withFractionalSeconds는 iOS 13에서 도입되었습니다; 이전 버전의 경우 dateFormat과 함께 DateFormatter를 사용하세요.
시간대 무시는 또 다른 일반적인 문제입니다. 서버가 시간대(+03:00)와 함께 날짜를 보내고 포맷터가 UTC로 설정된 경우 파싱은 실패하지 않지만 결과는 UTC가 됩니다. 개발자들은 종종 Date가 시간대를 보존할 것이라고 기대하지만 Date는 절대적인 시점이며 시간대 정보를 저장하지 않습니다. 올바르게 표시하려면 시간대를 별도로 저장하거나 올바른 timeZone과 함께 ISO8601DateFormatter를 사용하세요.
Apple 포럼(2024)에 따르면 ISO8601DateFormatter에 대한 질문의 약 20%는 초가 선택적인 형식과 관련이 있습니다. ISO 8601 표준은 초가 없는 형식을 허용합니다: 2024-07-21T14:30+03:00. .withInternetDateTime이 있는 ISO8601DateFormatter는 이 형식을 지원하지 않습니다 — 파싱하려면 dateFormat = "yyyy-MM-dd'T'HH:mmZ"가 있는 DateFormatter가 필요합니다. 축약된 시간 형식을 사용하는 API로 작업할 때 이 제한 사항을 고려하는 것이 중요합니다.
자주 묻는 질문
ISO8601DateFormatter는 ISO 8601 형식으로 날짜를 포맷하고 파싱하기 위한 특수화된 Foundation 클래스로, iOS 10부터 사용 가능합니다. dateFormat을 수동으로 설정하지 않고도 표준 형식을 자동으로 처리합니다.
ISO8601DateFormatter는 Locale에 의존하지 않으며 dateFormat 대신 옵션을 사용하고 모든 길이의 초의 분수를 올바르게 처리합니다. DateFormatter는 범용적이지만 수동 설정이 필요하고 지역 설정과 관련된 버그가 발생하기 쉽습니다.
.withFractionalSeconds 옵션을 사용하세요 — 소수점 이하 1~9자리를 지원합니다. 정밀도가 변할 수 있는 경우 .withMilliseconds를 사용하지 마세요. .withFractionalSeconds는 iOS 13부터 사용 가능합니다.
기본적으로 UTC입니다. 변경하려면 timeZone 속성을 설정하세요. timeZone = nil인 경우 기기의 로컬 시간이 사용됩니다. +HH:MM 형식의 명시적 시간대가 있는 문자열을 파싱할 때 포맷터가 자동으로 이를 고려합니다.
기본 formatOptions가 .withInternetDateTime이기 때문에 날짜 + 시간 + 시간대를 기대합니다. 날짜만 파싱하려면 formatOptions = [.withFullDate]를 설정하세요. 두 변형을 모두 지원하려면 다른 옵션으로 폴백을 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.