Sponsor Link
環境&対象
- macOS14.2 Beta 3
- Xcode 15.1 beta 3
- iOS 17.2
- Swift 5.9
FormastStyle
FormatStyle は、iOS15/macOS12 から導入されている データを文字列に変換(フォーマット)するための Protocol です。
以前の DateFormatter 等 の Formatter を置き換えるものです。
この記事では特に、Date に関連する FormatStyle を説明していきます。
Date向け FormatStyle
Date 向けの FormatStyle としてすでに いくつか定義されています。
定義は、Date.FormatStyle として定義されています。
Apple のドキュメントは、こちら。
ざっくりと抜粋すると、以下の種類が用意されています。
- Date.FormatStyle
- 通常(?) のフォーマット
- Date.ISO8601FormatStyle
- ISO8601 準拠のフォーマット(JSON 等)
- Date.RelativeFormatStyle
- 時間経過表示向けフォーマット
- Date.IntervalFormatStyle
- 時間区間表示向けフォーマット
この記事では、上記のうちの Date.FormatStyle を説明します。
Date.FormatStyle
使ってみる
Date.FormatStyle には、static な FormatStyle が .dateTime として用意されています。
以下のように使用することができます。
let ja = Locale(identifier: "ja-JP")
let en = Locale(identifier: "en-US")
let date = Calendar.current.date(from: DateComponents(year: 2023, month: 12, day: 24, hour: 9, minute: 0, second: 0))!
// MARK: locale: ja-JP
XCTAssertEqual(date.formatted(.dateTime.locale(ja)), "2023/12/24 9:00")
XCTAssertEqual(date.formatted(.dateTime.locale(en)), "12/24/2023, 9:00 AM")
ja-JP 環境では、”2023/12/24 9:00″ という文字列に変換され、
en-US 環境では、”12/24/2023, 9:00 AM” という文字列に変換されます。
Date/Time 単位でのカスタマイズ
Date, Time 部分それぞれについての表示を指定することができます。
Date には、以下のような指定が可能です。
.numeric: 数値で
.abbreviated: 短縮形で
.long: 長い表現で
.complete: 完全な表現で
.omitted: 省略する(表示しない)
Time には、以下のような指定が可能です。
.shortend: 短い表現で
.standard: 標準的な表現で
.complete: 完全な表現で
.omitted: 省略する(表示しない)
具体的な表記については以下のようになります。
なお、ja-JP 環境では、差異の無い指定もあります。
let ja = Locale(identifier: "ja-JP")
let en = Locale(identifier: "en-US")
let date = Calendar.current.date(from: DateComponents(year: 2023, month: 12, day: 24, hour: 9, minute: 0, second: 0))!
// MARK: locale: ja-JP
XCTAssertEqual(date.formatted(.dateTime.locale(ja)), "2023/12/24 9:00")
// note: .dateTime = .numeric, .shortened
// for date: .numeric, .abbreviated, .long, .complete, .omitted
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .shortened, locale: ja)),
"2023/12/24 9:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .abbreviated, time: .shortened, locale: ja)),
"2023年12月24日 9:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .long, time: .shortened, locale: ja)),
"2023年12月24日 9:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .complete, time: .shortened, locale: ja)),
"2023年12月24日 日曜日 9:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .omitted, time: .shortened, locale: ja)),
"9:00")
// for time: .shortened, .standard, .complete, .omitted
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .shortened, locale: ja)),
"2023/12/24 9:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .standard, locale: ja)),
"2023/12/24 9:00:00")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .complete, locale: ja)),
"2023/12/24 9:00:00 JST")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .omitted, locale: ja)),
"2023/12/24")
// MARK: locale: en-US
XCTAssertEqual(date.formatted(.dateTime.locale(en)), "12/24/2023, 9:00 AM")
// note: .dateTime = .numeric, .shortened
// for date: .numeric, .abbreviated, .long, .complete, .omitted
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .shortened, locale: en)),
"12/24/2023, 9:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .abbreviated, time: .shortened, locale: en)),
"Dec 24, 2023 at 9:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .long, time: .shortened, locale: en)),
"December 24, 2023 at 9:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .complete, time: .shortened, locale: en)),
"Sunday, December 24, 2023 at 9:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .omitted, time: .shortened, locale: en)),
"9:00 AM")
// for time: .shortened, .standard, .complete, .omitted
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .shortened, locale: en)),
"12/24/2023, 9:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .standard, locale: en)),
"12/24/2023, 9:00:00 AM")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .complete, locale: en)),
"12/24/2023, 9:00:00 AM GMT+9")
XCTAssertEqual(date.formatted(Date.FormatStyle(date: .numeric, time: .omitted, locale: en)),
"12/24/2023")
個別要素の表示 をカスタマイズ
Date, Time 内の個別の要素表示をカスタムするために、以下の メソッドが用意されています。
Date の要素向け:
- era
- 時代(?) 例: AD
- year
- 年
- quarter
- 四半期
- month
- 月
- week
- 週
- day
- 日
- weekday
- 曜日
- dayOfYear
- 日(年の中の日)
Time の要素向け:
- hour
- 時間
- minute
- 分
- second
- 秒
- secondFraction
- 秒の詳細表示
- timeZone
- タイムゾーン
上記は、それぞれの数値等の表現を 変更するものです。
表示順序は、FormatStyle が Locale 等をベースに決定しますので、変更することはできません。
Date 要素表示カスタマイズ
Date の構成要素である 時代、年、四半期、月、日 等は、以下のように表示をそれぞれカスタマイズすることが可能です。
let ja = Locale(identifier: "ja-JP")
let en = Locale(identifier: "en-US")
let date = Calendar.current.date(from: DateComponents(year: 2023, month: 12, day: 24, hour: 9, minute: 0, second: 0))!
// MARK: locale: ja-JP
// for era: .abbreviated(default), .narrow, .wide
XCTAssertEqual(date.formatted(.dateTime.era(.abbreviated).locale(ja)),
"西暦")
XCTAssertEqual(date.formatted(.dateTime.era(.narrow).locale(ja)),
"AD")
XCTAssertEqual(date.formatted(.dateTime.era(.wide).locale(ja)),
"西暦")
// for year: .defaultDigits(default), .twoDigits, .padded
XCTAssertEqual(date.formatted(.dateTime.year(.defaultDigits).locale(ja)),
"2023年")
XCTAssertEqual(date.formatted(.dateTime.year(.twoDigits).locale(ja)),
"23年")
XCTAssertEqual(date.formatted(.dateTime.year(.padded(6)).locale(ja)),
"002023年")
// for quarter: .abbreviated(default), .narrow, .oneDigit, .twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.quarter(.abbreviated).locale(ja)),
"Q4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.narrow).locale(ja)),
"4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.oneDigit).locale(ja)),
"4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.twoDigits).locale(ja)),
"04")
XCTAssertEqual(date.formatted(.dateTime.quarter(.wide).locale(ja)),
"第4四半期") // 4 は半角
// for month: .abbreviated(default), .defaultDigits, .narrow, .twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.month(.abbreviated).locale(ja)),
"12月")
XCTAssertEqual(date.formatted(.dateTime.month(.defaultDigits).locale(ja)),
"12月")
XCTAssertEqual(date.formatted(.dateTime.month(.narrow).locale(ja)),
"12月")
XCTAssertEqual(date.formatted(.dateTime.month(.twoDigits).locale(ja)),
"12月")
XCTAssertEqual(date.formatted(.dateTime.month(.wide).locale(ja)),
"12月")
// for day: .defaultDigits(default), .ordinalOfDayInMonth, .twoDigits
XCTAssertEqual(date.formatted(.dateTime.day(.defaultDigits).locale(ja)),
"24日")
XCTAssertEqual(date.formatted(.dateTime.day(.ordinalOfDayInMonth).locale(ja)),
"4")
XCTAssertEqual(date.formatted(.dateTime.day(.twoDigits).locale(ja)),
"24日")
// for dayOfYear: .defaultDigits(default), .threeDigits, .twoDigits
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.defaultDigits).locale(ja)),
"358")
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.threeDigits).locale(ja)),
"358")
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.twoDigits).locale(ja)),
"358")
// for weekday: .abbreviated(default), .narrow, .oneDigit, .short. twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.weekday(.abbreviated).locale(ja)),
"日")
XCTAssertEqual(date.formatted(.dateTime.weekday(.narrow).locale(ja)),
"日")
XCTAssertEqual(date.formatted(.dateTime.weekday(.oneDigit).locale(ja)),
"1")
XCTAssertEqual(date.formatted(.dateTime.weekday(.short).locale(ja)),
"日")
XCTAssertEqual(date.formatted(.dateTime.weekday(.twoDigits).locale(ja)),
"1")
XCTAssertEqual(date.formatted(.dateTime.weekday(.wide).locale(ja)),
"日曜日")
// MARK: locale: en-US
// for era: .abbreviated(default), .narrow, .wide
XCTAssertEqual(date.formatted(.dateTime.era(.abbreviated).locale(en)),
"AD")
XCTAssertEqual(date.formatted(.dateTime.era(.narrow).locale(en)),
"A")
XCTAssertEqual(date.formatted(.dateTime.era(.wide).locale(en)),
"Anno Domini")
// for year: .defaultDigits(default), .twoDigits, .padded
XCTAssertEqual(date.formatted(.dateTime.year(.defaultDigits).locale(en)),
"2023")
XCTAssertEqual(date.formatted(.dateTime.year(.twoDigits).locale(en)),
"23")
XCTAssertEqual(date.formatted(.dateTime.year(.padded(6)).locale(en)),
"002023")
// for quarter: .abbreviated(default), .narrow, .oneDigit, .twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.quarter(.abbreviated).locale(en)),
"Q4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.narrow).locale(en)),
"4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.oneDigit).locale(en)),
"4")
XCTAssertEqual(date.formatted(.dateTime.quarter(.twoDigits).locale(en)),
"04")
XCTAssertEqual(date.formatted(.dateTime.quarter(.wide).locale(en)),
"4th quarter")
// for month: .abbreviated(default), .defaultDigits, .narrow, .twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.month(.abbreviated).locale(en)),
"Dec")
XCTAssertEqual(date.formatted(.dateTime.month(.defaultDigits).locale(en)),
"12")
XCTAssertEqual(date.formatted(.dateTime.month(.narrow).locale(en)),
"D")
XCTAssertEqual(date.formatted(.dateTime.month(.twoDigits).locale(en)),
"12")
XCTAssertEqual(date.formatted(.dateTime.month(.wide).locale(en)),
"December")
// for day: .defaultDigits(default), .ordinalOfDayInMonth, .twoDigits
XCTAssertEqual(date.formatted(.dateTime.day(.defaultDigits).locale(en)),
"24")
XCTAssertEqual(date.formatted(.dateTime.day(.ordinalOfDayInMonth).locale(en)),
"4")
XCTAssertEqual(date.formatted(.dateTime.day(.twoDigits).locale(en)),
"24")
// for dayOfYear: .defaultDigits(default), .threeDigits, .twoDigits
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.defaultDigits).locale(en)),
"358")
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.threeDigits).locale(en)),
"358")
XCTAssertEqual(date.formatted(.dateTime.dayOfYear(.twoDigits).locale(en)),
"358")
// for weekday: .abbreviated(default), .narrow, .oneDigit, .short. twoDigits, .wide
XCTAssertEqual(date.formatted(.dateTime.weekday(.abbreviated).locale(en)),
"Sun")
XCTAssertEqual(date.formatted(.dateTime.weekday(.narrow).locale(en)),
"S")
XCTAssertEqual(date.formatted(.dateTime.weekday(.oneDigit).locale(en)),
"1")
XCTAssertEqual(date.formatted(.dateTime.weekday(.short).locale(en)),
"Su")
XCTAssertEqual(date.formatted(.dateTime.weekday(.twoDigits).locale(en)),
"1")
XCTAssertEqual(date.formatted(.dateTime.weekday(.wide).locale(en)),
"Sunday")
Time 要素表示カスタマイズ
Time の構成要素である 時間、分等は、以下のように表示をそれぞれカスタマイズすることが可能です。
let ja = Locale(identifier: "ja-JP")
let en = Locale(identifier: "en-US")
let date = Calendar.current.date(from: DateComponents(year: 2023, month: 12, day: 24, hour: 9, minute: 0, second: 0))!
// MARK: locale: ja-JP
// for hour: .defaultDigits(default), conversationalDefaultDigits, conversationalTwoDigits, twoDigits
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .abbreviated)).locale(ja)),
"9時")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .narrow)).locale(ja)),
"9時")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).locale(ja)),
"9時")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .wide)).locale(ja)),
"9時")
XCTAssertEqual(date.formatted(.dateTime.hour(.conversationalDefaultDigits(amPM: .abbreviated )).locale(ja)),
"9時")
XCTAssertEqual(date.formatted(.dateTime.hour(.conversationalTwoDigits(amPM: .abbreviated)).locale(ja)),
"09時")
XCTAssertEqual(date.formatted(.dateTime.hour(.twoDigits(amPM: .abbreviated)).locale(ja)),
"09時")
// for minute: .defaultDigits(default), twoDigits
XCTAssertEqual(date.formatted(.dateTime.minute(.defaultDigits).locale(ja)),
"0")
XCTAssertEqual(date.formatted(.dateTime.minute(.twoDigits).locale(ja)),
"00")
// for second: .defaultDigits(default), twoDigits
XCTAssertEqual(date.formatted(.dateTime.second(.defaultDigits).locale(ja)),
"0")
XCTAssertEqual(date.formatted(.dateTime.second(.twoDigits).locale(ja)),
"00")
// for timezone: .exemplarLocation, .genericLocation
XCTAssertEqual(date.formatted(.dateTime.timeZone(.exemplarLocation).locale(ja)),
"東京")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericLocation).locale(ja)),
"日本時間")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericName(.short)).locale(ja)),
"JST")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericName(.long)).locale(ja)),
"日本標準時")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.identifier(.short)).locale(ja)),
"jptyo")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.identifier(.long)).locale(ja)),
"Asia/Tokyo")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.iso8601(.short)).locale(ja)),
"+0900")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.iso8601(.long)).locale(ja)),
"+09:00")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.localizedGMT(.short)).locale(ja)),
"GMT+9")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.localizedGMT(.long)).locale(ja)),
"GMT+09:00")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.specificName(.short)).locale(ja)),
"JST")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.specificName(.long)).locale(ja)),
"日本標準時")
// MARK: locale: en-US
// for hour: .defaultDigits(default), conversationalDefaultDigits, conversationalTwoDigits, twoDigits
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .abbreviated)).locale(en)),
"9 AM")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .narrow)).locale(en)),
"9 a")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .omitted)).locale(en)),
"09")
XCTAssertEqual(date.formatted(.dateTime.hour(.defaultDigits(amPM: .wide)).locale(en)),
"9 AM")
XCTAssertEqual(date.formatted(.dateTime.hour(.conversationalDefaultDigits(amPM: .abbreviated )).locale(en)),
"9 AM")
XCTAssertEqual(date.formatted(.dateTime.hour(.conversationalTwoDigits(amPM: .abbreviated)).locale(en)),
"09 AM")
XCTAssertEqual(date.formatted(.dateTime.hour(.twoDigits(amPM: .abbreviated)).locale(en)),
"09 AM")
// for minute: .defaultDigits(default), twoDigits
XCTAssertEqual(date.formatted(.dateTime.minute(.defaultDigits).locale(en)),
"0")
XCTAssertEqual(date.formatted(.dateTime.minute(.twoDigits).locale(en)),
"00")
// for second: .defaultDigits(default), twoDigits
XCTAssertEqual(date.formatted(.dateTime.second(.defaultDigits).locale(en)),
"0")
XCTAssertEqual(date.formatted(.dateTime.second(.twoDigits).locale(en)),
"00")
// for timezone: .exemplarLocation, .genericLocation
XCTAssertEqual(date.formatted(.dateTime.timeZone(.exemplarLocation).locale(en)),
"Tokyo")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericLocation).locale(en)),
"Japan Time")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericName(.short)).locale(en)),
"Japan Time")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.genericName(.long)).locale(en)),
"Japan Standard Time")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.identifier(.short)).locale(en)),
"jptyo")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.identifier(.long)).locale(en)),
"Asia/Tokyo")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.iso8601(.short)).locale(en)),
"+0900")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.iso8601(.long)).locale(en)),
"+09:00")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.localizedGMT(.short)).locale(en)),
"GMT+9")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.localizedGMT(.long)).locale(en)),
"GMT+09:00")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.specificName(.short)).locale(en)),
"GMT+9")
XCTAssertEqual(date.formatted(.dateTime.timeZone(.specificName(.long)).locale(en)),
"Japan Standard Time")
まとめ
Date.FormatStyle の使い方をまとめてみました
- Date.FormatStyle に定義されている
- 簡単に使うには .dateTime を使用する
- Date, Time 単位でもいくつか表示方法を選べる
- Date, Time 内の個要素ごとにも 表示方法を選べる
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
SwiftUI おすすめ本
SwiftUI を理解するには、以下の本がおすすめです。
SwiftUI ViewMatery
SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。
英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。
View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。
超便利です
販売元のページは、こちらです。
SwiftUI 徹底入門
# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link