[Swift][Foundation] Date.FormatStyle の使い方

Foundation

     
⌛️ 4 min.
Date 向けに用意された FormatStyle をまとめます。

環境&対象

以下の環境で動作確認を行なっています。

  • 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 の使い方
  • Date.FormatStyle に定義されている
  • 簡単に使うには .dateTime を使用する
  • Date, Time 単位でもいくつか表示方法を選べる
  • Date, Time 内の個要素ごとにも 表示方法を選べる

説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。

SwiftUI おすすめ本

SwiftUI を理解するには、以下の本がおすすめです。

SwiftUI ViewMatery

SwiftUI で開発していくときに、ViewやLayoutのための適切なmodifierを探すのが大変です。
英語での説明になってしまいますが、以下の”SwiftUI Views Mastery Bundle”という本がビジュアル的に確認して探せるので、便利です。

英語ではありますが、1ページに コードと画面が並んでいるので、非常にわかりやすいです。

View に適用できる modifier もわかりやすく説明されているので、ビューの理解だけではなく、どのような装飾ができるかも簡単にわかります。

超便利です

SwiftUIViewsMastery

販売元のページは、こちらです。

SwiftUI 徹底入門

# SwiftUI は、毎年大きく改善されていますので、少し古くなってしまいましたが、いまでも 定番本です。

Swift学習におすすめの本

詳解Swift

Swift の学習には、詳解 Swift という書籍が、おすすめです。

著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。

最新版を購入するのがおすすめです。

現時点では、上記の Swift 5 に対応した第5版が最新版です。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です