swift에서 날짜를 표현하고 싶을 때는 보통 DateFormatter를 사용해서 Date 인스턴스를 String으로 변환시키거나 날짜형식을 가진 String을 Date 인스턴스로 변환시키곤 했죠
근데 넘 당연하게 써와서 귀찮은지 모르고 썼지만 생각해 보면
- DateFormatter 인스턴스 만들고
- dateFormat 지정해 주고
- 그 외 원하는 locale, timeZone 등의 프로퍼티를 지정해 주는 등
좀 고통스러웠을 수도 있어요?
그래서 date를 String으로 좀 더 쉽게 변환시킬 수 있다는 컨셉으로 등장한 메서드가 Date의 formatted 메서드인데요!
formatted 메서드는 iOS15부터 제공하고 뒤에서도 언급하겠지만 Float, Int, Sequence 등에서도 추가되어 다양한 타입을 쉽게 변환할 수 있게 되어 정말 편하게 우리가 원하는 의도대로 포맷팅 할 수가 있어요 :)
# DateFormatter로 Date를 변환했을 때
위에서 말한 것처럼 기존에 많이 했던 방식처럼 DateFormatter를 사용하여 date타입의 날짜를 문자열로 바꾸려면
- 포맷을 특정해주어야 하고
- 원하는 언어로 locale 시켜야 하고
- 원하는 캘린더로 세팅하는 등등 많은 프로퍼티를 활용해서 설정을 해주어야 하는데
아래 예시 코드를 봅시당
let formatter = DateFormatter()
formatter.dateFormat = "MMMM dd, yyyy"
formatter.locale = .init(identifier: "en")
formatter.calendar = Calendar(identifier: .gregorian)
// ...
let date = Date()
let stringDate = formatter.string(from: date)
→ 이렇게 하면 원하는 포맷 스타일에 대해세팅해야 할 것들이 많아서 매우 복잡하고, 원하는 것을 자세하게 써야만 원하는 결과가 출력되는 것을 알 수 있어요 ㅠㅠ
DateFormatter를 좀 더 간단하게 써보자면?? .dateStyle, .timeStyle
DateFormatter인스턴스의 dateStyle, timeStyle 속성을 사용해 주면 우리가 직접 많은 세팅을 해줄 필요가 없고, 그냥 표현하고 싶은 date style만 특정해 줄 수도 있긴 합니다!
let todayDate = Date()
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.timeStyle = .full
formatter.locale = Locale(identifier: "ko_KR")
print(formatter.string(from: todayDate))
//2024년 5월 28일 화요일 오전 8시 53분 55초 대한민국 표준시
그럼에도!! DateFormatter의 인스턴스를 만들고 각각 원하는 프로퍼티를 설정해주어야 하므로 복잡하다고 생각할 수 있고, 내가 설정한 것에 따라 오류가 나기 쉽다(error-prone)는 단점이..있네요 @.@
💡 그래서! 새롭게 등장한 API! 💡
애플이 iOS 15부터 사용할 수 있도록 발표한 새로운 인스턴스 메서드는 포맷팅을 좀 더 쉽고 유연하게 해줍니다.
그럼 그 인스턴스 메서드가 무엇이냐?!
바로 이 글에서 자세히 살펴볼 .formatted() 입니다!
# formatted 메서드로 Date를 변환할 때
foamatted 메서드는 Date 타입 확장(extension) 내부에 있는 인스턴스 메서드 이기 때문에 DateFormatter처럼 formatter 객체 만들지 않고도 바로 적용 가능한데요
바로 예시부터 간단히 보면
let todayDate = Date()
let todayDateString2 = todayDate.formatted(date: .abbreviated, time: .omitted)
print(todayDateString2) // 28 May 2024
뭐야 이렇게 쉽게 사용할 수 있다고?
DateFormatter와의 사용성 비교
그래서 얼마나 간편하게 사용할 수 있는지 DateFormatter와 비교해 보았는데요
formatted의 date, time 파라미터가 모두 .complete이라고 했을 때, DateFormatter와 formatter와의 차이입니다.
DateFormatter에서는 dateFormat을 하나하나 정해줘야 원하는 문자열로 나오는데 formatted 메서드는 date, string에 넘기는 인자에 따라 자동으로 문자열을 만들어 출력해 주죠
//DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "EEEE, dd MMMM yyyy 'at' h:mm:ss a Z"
let todayDateString1 = formatter.string(from: todayDate)
//formatted
let todayDateString2 = todayDate.formatted(date: .complete, time: .complete)
print("todayDateString1", todayDateString1) //Tuesday, 28 May 2024 at 9:08:38 AM +0900
print("todayDateString2", todayDateString2) //Tuesday, 28 May 2024 at 9:08:38 AM GMT+9
시간 데이터 빼고 날짜만 간단하게 보고 싶을 때는 이렇게도 해볼 수 있어요. DateFormatter가 dateFormat를 지정해줘야 하고 코드 길이가 formatter보다 긴 것은 여기서도 마찬가지입니다!
//DateFormatter
let formatter = DateFormatter()
formatter.dateFormat = "dd MMMM yyyy"
let todayDateString1 = formatter.string(from: todayDate)
//formatted
let todayDateString2 = todayDate.formatted(date: .abbreviated, time: .omitted)
print("todayDateString1", todayDateString1) //28 May 2024
print("todayDateString2", todayDateString2) //28 May 2024
# formatted 메서드의 파라미터 date, time
https://developer.apple.com/documentation/foundation/date/3766588-formatted
formatted(date:time:) | Apple Developer Documentation
Generates a locale-aware string representation of a date using specified date and time format styles.
developer.apple.com
formatted메서드에는 date와 time 두 가지의 파라미터가 있는데 각각 뭘 입력해줘야 하는지 볼까요?
date
- 여기에는 Date.FormatStyle.DateStyle 타입을 넣을 수 있는데
구현부를 따라가 보면 omitted, numeric, abbreviated, long, complete 이라는 타입 프로퍼티들을 사용하면 된다는 것을 알 수 있습니다!
각각 프로퍼티를 적용하면 어떤 문자열이 출력될 수 있는지 친절하게 나와있네욤!
공식문서에서도 잘 나와있습니당
https://developer.apple.com/documentation/foundation/date/formatstyle/datestyle
let meetingDate = Date()
meetingDate.formatted(date: .omitted, time: .standard)
// 9:42:14 AM
meetingDate.formatted(date: .numeric, time: .omitted)
// 10/17/2020
meetingDate.formatted(date: .abbreviated, time: .omitted)
// Oct 17, 2020
meetingDate.formatted(date: .long, time: .omitted)
// October 17, 2020
meetingDate.formatted(date: .complete, time: .omitted)
// Saturday, October 17, 2020
meetingDate.formatted()
// 10/17/2020, 9:42 AM
time
- 여기에는 Date.FormatStyle.DateStyle 타입을 넣을 수 있는데
구현부를 따라가 보면 omitted, shortened, standard, complete 이라는 타입 프로퍼티들을 사용하면 된다는 것을 알 수 있습니다!
공식문서에서도 어떻게 사용하면 되는지 나와있어요!
https://developer.apple.com/documentation/foundation/date/formatstyle/timestyle
let meetingDate = Date()
meetingDate.formatted(date: .numeric, time: .omitted)
// 10/17/2020
meetingDate.formatted(date: .numeric, time: .shortened)
// 10/17/2020, 9:54 PM
meetingDate.formatted(date: .numeric, time: .standard)
// 10/17/2020, 9:54:29 PM
meetingDate.formatted(date: .numeric, time: .complete)
// 10/17/2020, 9:54:29 PM CDT
meetingDate.formatted()
// 10/17/2020, 9:54 PM
💡 근데 어쨌든 편하긴 할 수 있지만 정해놓은 스타일 말고도 포맷을 내 맘대로 정하고 싶기도 하잖아요?
그럴 때 사용할 수 있는 게 Date 내부 구조체인 FormatStyle입니다.
# FormatStyle
FormatStyle 어디서 많이 봤는데? 한다면?
위에 date, time 파라미터에 들어가는 타입이 각각 Date.FormatStyle.DateStyle, Date.FormatStyle.DateStyle 였죠
-> 여기에서의 FormatStyle 맞습니다!
구현부 가보면 FormatStyle 구조체 안에 locale, timeZone, calendar 등이 있어서 이런 걸 커스텀할 수 있겠구나 싶죠!
실제로 이런 식으로 사용할 수 있다고 공식문서에 나옵니다.
let todayDate = Date()
let customFormat = Date.FormatStyle()
.day(.twoDigits)
.month(.wide)
.weekday(.short)
.hour(.conversationalTwoDigits(amPM: .wide))
.year(.defaultDigits)
.month(.abbreviated)
.day(.twoDigits)
.hour(.defaultDigits(amPM: .abbreviated))
.minute(.twoDigits)
.timeZone(.identifier(.long))
.era(.wide)
.dayOfYear(.defaultDigits)
.weekday(.abbreviated)
.week(.defaultDigits)
.locale(Locale(identifier: "ko_KR"))
print(todayDate.formatted(customFormat))
FormatStyle도 iOS 15부터 제공되는데 formatted 본 김에 FormatStyle도 함께 좀 더 자세히 보면
공식문서
https://developer.apple.com/documentation/foundation/date/formatstyle
Date.FormatStyle | Apple Developer Documentation
A structure that creates a locale-appropriate string representation of a date instance and converts strings of dates and times into date instances.
developer.apple.com
정의
A structure that creates a locale-appropriate string representation of a date instance and converts strings of dates and times into date instances.
: 날짜 인스턴스의 로케일에 적합한 문자열 표현을 생성하고 날짜 및 시간 문자열을 날짜 인스턴스로 변환하는 구조입니다.
정의가 이렇게 나와있는데
그럼 locale-appropriate string representation 이란 뭘 의미하는 걸까?
한국어 번역에 나온 것처럼 직역하면 "로케일에 적합한 문자열 표현"인데
왜 로케일에 적합한 문자열 표현이 필요한 걸까요?
한국에서는 보통 년월일 순서로 날짜를 표현하곤하는데 미국이나 영어권은 일월년으로 표현을 하곤하죠.
이렇게 나라마다, 위치마다 날짜랑 시간을 표현하는 방식이 다른데 이것에 적합한 표현을 만들어준다는 것입니다.
아래 코드에서 볼 수 있는 것처럼 locale을 한국으로 지정해 주었을 때는 “2024년 5월 27일”, 아닐 때는 “May 27, 2024”으로 포맷팅 되는 것을 볼 수 있습니다. ‘한국어’로 표현해 주는 것 뿐만 아니라 ‘한국식’으로 표현해주는 것을 볼 수 있습니다.
//default - 영어표현
let customFormat = Date.FormatStyle()
.month(.wide)
.day(.twoDigits)
.year(.defaultDigits)
let todayDateString2 = todayDate.formatted(customFormat)
print("todayDateString2", todayDateString2)
//May 28, 2024
//locale 추가
let customFormat = Date.FormatStyle()
.month(.wide)
.day(.twoDigits)
.year(.defaultDigits)
.locale(Locale(identifier: "ko_KR"))
let todayDateString2 = todayDate.formatted(customFormat)
print("todayDateString2", todayDateString2)
//2024년 5월 28일
참고 ) .dateTime은 FormatStyle을 단축하여 표현하는 factory variable라고 합니다
let localeArray = ["en_US", "sv_SE", "en_GB", "th_TH", "fr_BE"]
for localeID in localeArray {
print(meetingDate.formatted(.dateTime
.day(.twoDigits)
.month(.wide)
.weekday(.short)
.hour(.conversationalTwoDigits(amPM: .wide))
.locale(Locale(identifier: localeID))))
}
// Th, November 12, 7 PM
// to 12 november 19
// Th 12 November, 19
// พฤ. 12 พฤศจิกายน 19
// je 12 novembre, 19 h
# String -> Date
근데 이렇게 Date인스턴스를 String으로 포맷팅하다 보면 궁금한 게
그럼 String으로 되어 있는 건 Date인스턴스로 못 만드나??
DateFormatter는 가능했는데!
DateFormatter에서의 Date -> String, String -> Date
let todayDate = Date()
let formatter = DateFormatter()
formatter.dateFormat = "dd MMMM yyyy hh:mm:ss"
formatter.locale = Locale(identifier: "ko_KR")
// Date -> String
let todayDateString1 = formatter.string(from: todayDate)
print("todayDateString1", todayDateString1)
// String -> Date
let todayDateInstance = formatter.date(from: todayDateString1)
print(todayDateInstance)
formatted 에서의 Date -> String, String -> Date
formatted에서도 String을 Date인스턴스로 만드는 것 물론 가능합니다. 이것도 Date.FormatStyle을 통해서 만들 수 있는데
아래 코드처럼 FormatStyle을 통해 String을 Date로 파싱 할 수 있고
let inputFormat = Date.FormatStyle()
.locale(Locale(identifier: "en_GB"))
.year()
.month()
.day()
// Parse string inputs into date instances.
let productIntroDate = try? Date("9 Jan 2007", strategy: inputFormat)
let anotherIntroDate = try? Date("27 Jan 2010", strategy: inputFormat)
let conferenceDate = try? Date("7 Jun 2021", strategy: inputFormat)
FormatStyle을 통해 Date를 다시 String형태로 만들 수도 있습니다.
let outputFormatStyle = Date.FormatStyle() // Define format style for string output.
.locale(Locale(identifier: "en_US"))
.year()
.month(.wide)
.day(.twoDigits)
.weekday(.abbreviated)
// Apply the output format on the three dates below.
print(outputFormatStyle.format(conferenceDate!)) // Mon, June 07, 2021
print(outputFormatStyle.format(anotherIntroDate!)) // Wed, January 27, 2010
print(outputFormatStyle.format(productIntroDate!)) // Tue, January 09, 2007
# ParseStrategy
또 참고로 복잡한 String에서 날짜 데이터를 추출해서 포맷팅 할 수도 있습니다
https://developer.apple.com/documentation/foundation/date/formatstyle#3830752
Date.FormatStyle | Apple Developer Documentation
A structure that creates a locale-appropriate string representation of a date instance and converts strings of dates and times into date instances.
developer.apple.com
공식문서에서 보면 Date.ParseStrategy 메소드는 제공된 문자열을 날짜 인스턴스로 구문 분석한다고 합니다.
let inputString = "Archive for month 8, archived on day 23 - complete."
let strategy = Date.ParseStrategy(
format: "Archive for month \(month: .defaultDigits), archived on day \(day: .twoDigits) - complete.",
locale: Locale(identifier: "en_US"),
timeZone: TimeZone(abbreviation: "CDT")!
)
if let date = try? Date(inputString, strategy: strategy) {
print(date.formatted())
}
//23/08/1969, 2:00 PM
이런 식으로 사용할 수 있고
한국어로 된 문자열도 이렇게 파싱해볼 수 있더라구요
let inputString = "오늘의 날짜는 2024년 5월이고,24일입니다. 나는 17:30에 집에 갈 것입니다."
let strategy = Date.ParseStrategy(
format: "오늘의 날짜는 \(year: .defaultDigits)년 \(month: .wide)이고, \(day: .twoDigits)일입니다. 나는 \(hour: .twoDigits(clock: .twentyFourHour, hourCycle: .oneBased)):\(minute: .twoDigits)에 집에 갈 것입니다.",
// locale: Locale(identifier: "en_US"),
locale: Locale(identifier: "ko_KR"),
timeZone: .current
)
if let date = try? Date(inputString, strategy: strategy) {
print(date.formatted())
}
//24/05/2024, 5:30 PM
Date에 대한 포맷팅은 이쯤 보고 formatted 라는 메서드는 다양한 타입에 쓸 수 있는 인스턴스 메서드이기도 한데 이 부분을 좀 살펴볼까 합니다!
(물론 똑같이 Date안에 있는 메서드가 아니라 각각의 타입 안에 formatted라는 메서드가 있는 것입니다.)
# 다양한 타입에 대한 formatted
소수점에 대한 formatted
0.5.formatted()
0.5335.formatted(.number.precision(.significantDigits(2)))
2.5.formatted(.currency(code: "usd"))
정수에 대한 formatted
100500.formatted() //"100,500"
세 자릿수씩 끊어서 표현해 줄 때 아주 유용합니다!
Sequence에 대한 formatted
Sequence에서는 요소의 타입이 String일 때만 가능하다고 하네요.
//⭕️ 요소의 타입이 String인 array
["Apple", "Amazon", "Meta"].formatted() // "Apple, Amazon, and Meta"
["Apple", "Amazon", "Meta"].formatted(.list(type: .or)) // "Apple, Amazon, or Meta"
//⭕️ 요소의 타입이 String인 set
let set : Set<String> = ["Apple", "Amazon", "Meta"]
set.formatted(.list(type: .and)) //"Amazon, Meta, and Apple"
//set는 순서가 없기 때문에 순서는 랜덤으로 포맷팅된다.
//❌dictionary는 요소 타입이 (key: String, value: String)이므로 불가
//let dictionary : Dictionary<String, String> = ["1": "Apple", "2": "Amazon", "3": "Meta"]
//dictionary.formatted(.list(type: .and, width: .standard))
이렇게 iOS 15부터 제공되는 formatted에 대해 알아보았습니다!
날짜를 포맷팅 할 때 DateFormatter를 습관적으로 써와서 formatted가 얼마나 많이 사용되고 있는지는 모르지만 다음 프로젝트할 때에는 일부러라도 써봐야겠어요. 훨씬 간단하니 회사나 팀에서도 앞으로 많이들 사용하겠죠?
'Swift' 카테고리의 다른 글
[Swift] 이미지를 로드하는 여러 방법과 Data(contentsOf:)의 priority inversion 이슈 | Kingfisher, Data(contentsOf:), URLSession, Alamofire (0) | 2024.06.25 |
---|---|
네임스페이스를 관리해보자 | enum, struct로 namespace 관리하기 (0) | 2024.06.21 |
[Swift] 프로토콜 뽀개볼까 (5) | Identifiable (0) | 2024.05.10 |
[Swift] 프로토토콜 뽀개볼까 (4) | Hashable, HashTable (0) | 2024.05.09 |
[Swift] 프로토콜 뽀개볼까 (3) | Codable (feat. Encodable & Decodable), JSON이란? (1) | 2024.04.28 |