はじめに
先日[Swift]日付を取得し差分を出す方法と言う記事を書いたが、いやいやもっと標準ライブラリ使って簡単に取得できるよ!って事を知れたので試してみました。
環境
・ iOS 13 以上
DateComponentsFormatter
A formatter that creates string representations of quantities of time.
→時間の量の文字列表現を作成するFormatterです。(公式より翻訳)
時間の表現を作成するなら普段用使用するFormatterと同じでは?と言う感じがしますが、とにかく使用してみる。
let time: TimeInterval = 300
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = true
formatter.includesTimeRemainingPhrase = true
formatter.allowedUnits = [.minute]
let outputString = formatter.string(from: time)!
print(outputString)
おお!約って言うのが気になるがどうやらtimeにある秒数から残りを算出してくれている様子。
続いてtime=330にして下記の書き換えてみる。
formatter.allowedUnits = [.minute]
分と秒がしっかり取れていることが確認できます。
またincludesApproximationPhraseは結果の語句が不正確な時間値を反映しているかどうかを示すBool値なので
formatter.includesApproximationPhraseをfalseにすると
となります。(省略可能)
includesTimeRemainingPhraseは出力文字列が残り時間を反映するかどうかを示すBool値なのでfalseにすると”残り”と言う文字が消えます。
let time: TimeInterval = 30000000
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.includesApproximationPhrase = false
formatter.includesTimeRemainingPhrase = false
formatter.allowedUnits = [.day, .hour, .minute, .second]
let outputString = formatter.string(from: time)!
print(outputString)
allowedUnitsに追加していく事でそれぞれの日時を取得する事ができます。また0の場合は表示されません。(今回は秒が0秒で表示されていない)
RelativeDateTimeFormatter
A formatter that creates locale-aware string representations of a relative date or time.
→相対日付または時刻のロケール対応文字列表現を作成するFormatterです。(公式より翻訳)
またiOS13より使用可能となっています。
localizedString(for: Date, relativeTo: Date) -> String
を使用する事でフォーマッタのカレンダーを使用して、参照日から指定された日付までの日付間隔をフォーマットしてくれます。
struct RelativeDateTimeFormatterTraining: View {
@State var timeText = ""
var body: some View {
VStack{
Button(action: {
let targetDate = Date(timeIntervalSinceNow: 300)
let nowDate = Date(timeIntervalSinceNow: 0)
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
formatter.locale = Locale(identifier: "ja-JP")
timeText = formatter.localizedString(for: targetDate, relativeTo: nowDate)
}, label: {
Text("Button")
})
Text(timeText)
}
}
}
formatter.localizedString(for: targetDate, relativeTo: nowDate)で
forの部分ではフォーマットする間隔の終了日を、relativeToにはフォーマットする間隔の開始日を入れる事で300秒分の差分を計算して自動で5分後と算出してString型で返してくれます。
その他にも
func localizedString(from: DateComponents) -> String
指定された日付コンポーネントで表される相対時間をフォーマットします。
func localizedString(fromTimeInterval: TimeInterval) -> String
フォーマッタのカレンダーを使用して、指定された時間間隔をフォーマットします。
func string(for: Any?) -> String?
現在の日付と時刻に相対的な日付のフォーマットされた文字列を作成します。
があります。
timeIntervalSinceとlocalizedString(fromTimeInterval: TimeInterval) -> Stringを使用する事でTwitterの様な”〜分前”の様な実装を容易に行うことができます。※ RelativeDateTimeFormatterは文法的に正しくない可能性があります。と公式で一文があるように、一番大きな単位以下の単位は基本切り捨てとなりますのでかなり情報落ちするケースも出てきます。
例えば
VStack{
Button(action: {
let dateFormat = DateFormatter()
// 日時をString型で定義
let textLogDate = "2021-01-01 12:00:00"
let textNowDate = "2021-01-01 12:05:40"
// dateFormatを初期設定した"2021-01-01 12:00:00"と同じ形式になるように定義
dateFormat.dateFormat = "yyyy-MM-dd HH:mm:ss"
// timeZoneを日本に
dateFormat.timeZone = TimeZone(identifier: "Asia/Tokyo")
// String型の日時をDate型に変換
let logDate = dateFormat.date(from: textLogDate) ?? Date()
let nowDate = dateFormat.date(from: textNowDate) ?? Date()
// timeIntervalSinceを使用すると差分を取得できる->nowDate-logDate
let dateSubtraction: TimeInterval = logDate.timeIntervalSince(nowDate)
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .abbreviated
formatter.locale = Locale(identifier: "ja-JP")
timeText = formatter.localizedString(fromTimeInterval: dateSubtraction)
print(timeText)
}, label: {
Text("Button")
})
Text(timeText)
}
とした場合結果は
となります。正確には5分40秒前ですが、今回の場合は一番大きな単位は分になりますので秒は切り捨てられます。
まとめ
とにかくTwitterにある様な”〜分前”みたいな実装をする際にはRelativeDateTimeFormatterは本当に便利なものだが正確に取得したい場合はDateComponentsFormatterを使用した方が良さそうだと感じました。しかし標準ライブラリでここまで簡単に差分を取って、String型で返してくれるのは本当に便利なので今後は活用して行きたいです!