[ SwiftUI ] タイムゾーン を意識した 日付, 時刻 文字列 を 扱う方法

Swift に限った話ではありませんが、 日付 や 時刻 を扱う際に、特定の地域のみを対象にする場合は タイムゾーン や 時差 を意識することはありませんが、 グローバル な ユーザ を対象とする場合は、 ユーザ の ローカル 時間帯 を意識することがユーザの利便性向上につながります。 本記事では、 UTC で表現された 日付文字列 を タイムゾーン を意識しながら管理し、 別の タイムゾーン で表示する方法について整理していきます。

Xcode: 13.0
iOS: 14.5
Swift: 5

今回実現すること

今回は、 “2021-10-06T18:45:00+00:00” という UTC で表現された文字列を扱い、これを様々なタイムゾーンで表示してみます。。
例えば JST の場合、 UTC に +9 時間足した 2021年10月7日 3:45 AM と表示することを目指します。


Timezone の表示

まずは百聞は一見にしかずということで、 「 現在時刻 」 と 「 現在の タイムゾーン 」 を表示する View を作成して内容をみてみます。

struct DateTimeTzView: View {
    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text("Current Time: ")
                Text(Date().description(with: .current))
                    .foregroundColor(Color.blue)
            }
            Divider()
            HStack {
                Text("Timezone: ")
                Text(TimeZone.current.abbreviation()!)
                    .foregroundColor(Color.orange)
            }
        }
        .padding()
    }
}

システムの設定を 日本 としているため、 以下のような結果となっています。

  • Locale: Japan Standard Time (JST)
  • Timezone: GMT+9

システム の設定を ロンドン に変更するとこの内容が変化していきます。

  • Locale: British Summer Time (BST)
  • Timezone: GMT+1

以降では、このように 文字列 を タイムゾーン を意識して扱い、 ユーザ が所属する タイムゾーン 別に表示を変更する方法を整理していきます。


日付文字列 を Date 型に変換

@State 変数の用意

それでは、日付文字列 を Date 型に変換していきます。最初に、アプリ上で 文字列や フォーマット の指定可能にするため、  @State として以下の 3 つの変数を用意します。

@State private var dateString = "2021-10-06T18:45:00+00:00"
@State private var dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
@State private var dateObject: Date = Date()

入力領域と変換後の出力領域の作成

続いて、 日付文字列 と フォーマット 形式、 加えて Date 型に変換した結果 を表示する View を作成していきます。

VStack(alignment: .leading) {
    HStack {
        Text("Date String & Format: ")
        VStack {
            TextField("date string", text: $dateString)
                .foregroundColor(Color.red)
            TextField("date format", text: $dateFormat)
                .foregroundColor(Color.green)
        }
    }
    Divider()
    HStack {
        Text("Date Object: ")
        Text(dateObject.description(with: .current))
            .foregroundColor(Color.blue)
    }
    Divider()
    HStack {
        Text("Timezone: ")
        Text(TimeZone.current.abbreviation()!)
            .foregroundColor(Color.orange)
    }
}

変換処理 の作成

続いて、 変換処理 を実装していきます。 今回は 文字列 として処理するのは UTC で表現されたものとしています

private func convDate() {
    let dateFormatter = DateFormatter()
    let timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = dateFormat
    dateFormatter.timeZone = timeZone
    
    if let date = dateFormatter.date(from: dateString) {
        dateObject = date
    } else {
        print("dateFormatter Failed")
    }
}

Date型 への 変換処理 については、以下の記事でも紹介していますので、参考にしてください。

様々な パターン に対応した フォーマット の指定方法

以下の サイト に様々な パターン に対応した フォーマット 指定方法が紹介されていますので、参考にしてください。

Date Format in Swift

動的な変換処理の実装

最後に、 文字列の入力に応じて動的に Date型への変換処理を実施するようにします。

.onAppear {
    convDate()
}
.onChange(of: dateString) { newValue in
    convDate()
}
.onChange(of: dateFormat) { newValue in
    convDate()
}

作成したアプリの動作 ( 日付文字列 から タイムゾーンを意識した Date型 の表示 )

JST として表示

システムの時間帯を JST ( GMT+) として表示すると、想定したとおり 10月7日3:45 AM となっています (青文字)

BST として表示

システムの時間帯を BST (GMT+1) とすると、それに追随して Date Object の内容(青文字)も変化します。

年またぎにも対応しています。


まとめ

  • Swift では 現在時刻は Date().description(with: .current)) で取得可能
  • Swift では 現在のタイムゾーンは TimeZone.current.abbreviation()! で取得可能
  • UTC として 日付文字列を扱う場合は dateFormatter.timezone に TimeZone(abbreviation: “UTC”) を指定

関連記事

参照情報

DateFormatter

Date Format in Swift


今回作成した全ソースコード

// ContentView.swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        DateTimeTzView()
            .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
//  DateTimeTzView.swift
import SwiftUI

struct DateTimeTzView: View {
    @State private var dateString = "2021-10-06T18:45:00+00:00"
    @State private var dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
    @State private var dateObject: Date = Date()

    var body: some View {
        VStack(alignment: .leading) {
            HStack {
                Text("Date String & Format: ")
                VStack {
                    TextField("date string", text: $dateString)
                        .foregroundColor(Color.red)
                    TextField("date format", text: $dateFormat)
                        .foregroundColor(Color.green)
                }
            }
            Divider()
            HStack {
                Text("Date Object: ")
                Text(dateObject.description(with: .current))
                    .foregroundColor(Color.blue)
            }
            Divider()
            HStack {
                Text("Timezone: ")
                Text(TimeZone.current.abbreviation()!)
                    .foregroundColor(Color.orange)
            }
        }
        .padding()
        .onAppear {
            convDate()
        }
        .onChange(of: dateString) { newValue in
            convDate()
        }
        .onChange(of: dateFormat) { newValue in
            convDate()
        }
    }
    private func convDate() {
        let dateFormatter = DateFormatter()
        let timeZone = TimeZone(abbreviation: "UTC")
        dateFormatter.dateFormat = dateFormat
        dateFormatter.timeZone = timeZone
        
        if let date = dateFormatter.date(from: dateString) {
            dateObject = date
        } else {
            print("dateFormatter Failed")
        }
    }
}