「ゴルトン社長」のはじめの一歩

すげーブログ

世の中のすげーを伝える!

【iphoneアプリ開発_初心者必見】日付(Date)の基礎・基本

最終更新日:2020年08月14日
f:id:BlueThree:20200814204123j:plain:w500
家に引きこもっている「ゴルトン社長」です。(twitter : @GoRuton_1stStep)

日付(Date)の基礎・基本

  • アプリで頻繁に扱う日付などの時間情報について記事をまとめました
  • 世界で主に使われる時刻の定義の種類に関してもまとめました

人気の記事!

人生を変えたい人に向けて「はじめの一歩」を踏み出したい人にオススメ!

www.goruton.com www.goruton.com www.goruton.com www.goruton.com

1)Dateとは何か

Dateを扱う前に時刻と何なのか知る必要があります。 世界で主に使われる時刻の定義の種類について説明しております.

【UTC(協定世界時)】

  • UTCは世界共通の時刻である
  • 各地域の標準時はUTCを基準として算出
  • UTCは原子時をもとに時刻を進めている
  • 原子時を人が扱う現実の時間に変換(整数として扱う)すると,ほんの僅かな誤差が生じる
  • この誤差の影響が1秒以上になると問題が生じるため、1秒以上の誤差が生じないように人工的に原子時にうるう秒を考慮している

【GMT(グリニッジ平均時)】

  • UTCが現れる以前まで世界共通時
  • 経度0からの平均太陽時を指す
  • 現在ではUTCと同義で扱われることが多いが,厳密には異なりGMTはうるう秒が考慮されない
  • UTC比較して,100年でおよそ18秒のズレが生じる

【JST(日本標準時)】

  • 日本における時間の標準時間で,UTC+9時間と定義されています。
  • UTCで1時であると,JSTだと10時です

【UNIXタイム】

  • 1970年1月1日0時0分0秒(UNIXエポック)からの経過時間を算出されます
  • これを使用する事で,PCなどはインターネットが繋がっていない状態でも経過時間を測る事が可能になります
  • UNIXタイムは本当の時刻経過を表しておらず、うるう秒も考慮されません
  • しかし,多くのシステムでは運用的な問題が少ないため実質的な時刻として扱われています。

2)やりがちなミス・対策

A) Formatterでロケール・タイムゾーンを設定しない
  • インドのように公用語が母国語ではないケースや、「言語」は英語を設定しているが「地域」は日本など必ずしも現在設定している地域(ロケール)が日付の表示したい言語とは限りません。
  • これについては現在の地域を使用する方法やprefferedLanguagesを使用する方法もあります
  • しかし.明示的に指定することでUXの向上や正しいローカライズ結果を得ることができます。
B) 日付文字列へ変換時にローカライズされるべき箇所をハードコードしている
//やりがちなミス1
formatter.dateFormat = "yyyy年MM月dd日"
print(formatter.string(from: Date())) // 2017年8月12日

【理由】

  • 月、日などローカライズされるべき文字が混合している
  • ローカライズの対応を検討の際に,Localizable.stringsに国ごとのパターンを個別に追加する必要性が出るためよくない
//対策1
formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "ydMMM", options: 0, locale: Locale(identifier: "ja_JP"))
print(formatter.string(from: Date())) // 2017年8月12日
  • テンプレートとロケールを組み合わせることで,その地域に合わせた形式での日付の出力が可能です
C) 曜日の算出を個々で試みる
//やりがちなミス2
let weeks = ["日","月","火","水","木","金","土"]
// 曜日の算出
let week = weeks[currentIndex] // 日

【理由】

  • DateFormatterのパターンを使用することで解決できます

UTS #35: Unicode Locale Data Markup Language

//対策2
let formatter = DateFormatter()
formatter.dateFormat = DateFormatter.dateFormat(fromTemplate: "EEEEE", options: 0, locale: Locale.current)
print(formatter.string(from: Date())) // 日
D) StringからDate変換時にen_US_POSIX以外をロケールを指定する
//やりがちなミス3
let now = "2017/8/12"
formatter.dateFormat = "yyyy/MM/dd"
formatter.locale = Locale(identifier: "ja_JP")
let date = formatter.date(from: now)

【理由】

  • これを実行してみると通常の設定ではdateは正しく2017/8/12と取得されます。
  • しかし、例えばiPhoneの設定から日付を西暦ではなく「和暦」にすると4005/8/12と取得されてしまいます。
  • これは和暦を設定したために,平成2017年と解釈されyyyyにより,西暦変換すると平成29年現在が2017年であることから平成2017年は西暦4005年となります。
  • このようにformatterに対して,本体設定が解釈に影響を及ぼすコードであることからやりがちなミスと言えます。

3)定石の書き方

定石1) styleの検討
  • DateFormatterにはdateStyleとtimeStyleがある
  • 日付と時間の出力形式をローカライズしたフォーマットとして扱ってくれるプロパティがある
  • 出力の仕様がこのstyleの指定で満たされる場合は、個別にyyyy年MM月dd日のような固定フォーマットを与えるべきではありません。
let f = DateFormatter()
f.timeStyle = .full
f.dateStyle = .full
f.locale = Locale(identifier: "ja_JP")
let now = Date()
print(f.string(from: now)) // 平成30年8月13日日曜日 16時29分05秒 日本標準時
ja_JPにおける例

f:id:BlueThree:20190626233502p:plain
ja_JP

具体例1(日付だけ出力):

let f = DateFormatter()
f.dateStyle = .long
f.timeStyle = .none
let now = Date()
print(f.string(from: now)) //2017年8月13日

具体例2(時刻だけ出力):

let f = DateFormatter()
f.dateStyle = .none
f.timeStyle = .medium
let now = Date()
print(f.string(from: now)) //16:36:46
定石2) テンプレートの検討
  • DateFormatter.dateFormat(fromTemplate:options:locale)
  • styleよりも自由度が高い出力形式の指定が可能です
  • テンプレート文字の種類と意味一覧は以下のリンク先から確認できます
  • UTS #35: Unicode Locale Data Markup Language
extension DateFormatter {
    // テンプレートの定義(例)
    enum Template: String {
        case date = "yMd"     // 2017/1/1
        case time = "Hms"     // 12:39:22
        case full = "yMdkHms" // 2017/1/1 12:39:22
        case onlyHour = "k"   // 17時
        case era = "GG"       // "西暦" (default) or "平成" (本体設定で和暦を指定している場合)
        case weekDay = "EEEE" // 日曜日
    }

    func setTemplate(_ template: Template) {
        // optionsは拡張用の引数だが使用されていないため常に0
        dateFormat = DateFormatter.dateFormat(fromTemplate: template.rawValue, options: 0, locale: .current)
    }
}

// テンプレートから時刻を表示
let f = DateFormatter()
f.setTemplate(.full)
let now = Date()
print(f.string(from: now)) // 2017/8/13 17:24:21
定石3) dateFormatの検討

styleとtemplateは便利であるものの当然全てのパターンを網羅できるものではありません。 日本語の場合時や分などのローカライズされるべき文字がありますが、多くの場合は記号区切りです。 つまり○時○分のような記号と一対一で結びつかないローカライズを期待する文字は世界共通ではないのでRFC3339では非推奨とされています。

  • 下記の場合などにはdateFormatを使用します。

<例>

【時刻をあと○分と表示したいという場合】

Localizable.strings(ja)からあとmm分をdateFormatに与えるのが良いということになります。(Localizable.strings(en)の場合はmm minutes remaining)。

定石4) StringからDateに変換するときは,en_US_POSIXを指定すべし
let f = DateFormatter()
f.dateFormat = "yyyy/MM/dd"
let now = Date()
print(f.string(from: now))

【結果】 本体設定が西暦(グレゴリアン)の場合 -> 2017/8/12 本体設定が和暦の場合 -> 0029/8/12 本体設定がタイ仏暦の場合 -> 2560/8/12

上記のような設定では正常に動いていても設定を変更した途端にこのコードは破綻してしまいます。 これはdateFormatが現在の時刻表示設定を用いてフォーマットの解釈を行うためです。 つまり任意のフォーマットを指定する場合、本来全ての設定を網羅した処理が必要となってしまいます。

if グレゴリアン {...}
else if 和暦 {...}
else if タイ仏暦 {...}
...
  • ロジックを複雑化させメンテナンス性を下げるコードにシフトしていく未来が見えるためアンチパターンです。
  • これを解決するためにen_US_POSIXを使用します。
  • よく生物の学名がラテン語で表記されているのを見ると思いますが、これはラテン語がこれ以上言語として仕様変更がないとみなされているため採択されたものです。
  • つまり、あとから変更されないことが保証されているため、膨大な生物の名前を一意に与えたいときに便利だから採択されたというわけです。 
  • このラテン語の例は今回のen_US_POSIXを使う理由と同じです。
  • en_US_POSIXは仕様変更されないことが保証されている唯一のロケールであるため、RFC3339に準拠した日付文字列(例: yyyy-MM-dd'T'HH:mm:ss'Z')をDateに変換できる保証があります。
  • en_US_POSIXはja_JPなどのロケールとは違い当然、和暦やタイ仏暦などもありません。
  • つまり、いかなる本体設定をしようともRFC3339のフォーマットの日付文字列を正しく解釈することができるということです。
  • そのため一度en_US_POSIXで解釈したDateオブジェクトから任意の出力形式に変換することによって設定やLocaleに依存しない日付処理を実現することができます。

4)Formatter

和暦の元号名は自動で取得できる
let day: Double = 60 * 60 * 24
let year: Double = day * 365
let f = DateFormatter()

// 元号名をつけて出力
f.dateFormat = DateFormatter.dateFormat(fromTemplate: "GydMMMEEE", options: 0, locale: Locale(identifier: "ja_JP"))

// 令和(現在)
print(f.string(from: Date())) // 令和1年5月30日(日)

// 平成
print(f.string(from: Date())) // 平成29年8月13日(日)

// 昭和(30年前)
print(f.string(from: -(year * 30))) // 昭和15年1月9日(火)

// 大正(50年前)
print(f.string(from: -(year * 50))) // 大正9年1月14日(水)

// 明治(70年前)
print(f.string(from: -(year * 70))) // 明治33年1月18日(木)

// 安政(110年前)
print(f.string(from: -(year * 110))) // 安政7年1月28日(土)

// 応仁(502年前) 応仁の乱の時代
print(f.string(from: -(year * 502))) // 応仁3年4月23日(日)

// 天禄(1000年前)
print(f.string(from: -(year * 1000))) // 天禄1年8月26日(金)

// 大化(1326年前) 限界...これ以上昔にすると...
print(f.string(from: -(year * 1326))) // 大化0年11月15日(月)
当日を「今日」、前日を「昨日」などと表示する
let f = DateFormatter()
f.dateStyle = .medium
f.timeStyle = .none
f.doesRelativeDateFormatting = true
let now = Date()
print(f.string(from: now)) // 今日

doesRelativeDateFormatting = trueの時の出力対応表

f:id:BlueThree:20190627000013p:plain
対応表

当日を「今日」、前日を「昨日」などと表示する
let f = DateIntervalFormatter()
f.dateTemplate = "ydMMMEEE"
f.locale = Locale(identifier: "ja_JP")
let 今日 = Date()
let 明後日 = Date(timeIntervalSinceNow: 60 * 60 * 48)
print(f.string(from: 今日, to: 明後日)) // 2017年8月13日(日)~15日(火)
秒数を時刻形式に変換
  • 例えば任意の秒数を○時間○分○秒と出力したい
  • これを自前で実装しようとなると下記の方法を考えますが,複雑なコードが生まれる未来が見えます。

    • 3601秒の場合1時間1秒と表示するなど分を読み落とし
    • hh:mm:ssの形式の場合にはhh以外の1桁は0で埋めて1:00:01とするなど
  • 上記問題はDateComponentsFormatterにより,簡単に解決することができます。

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
print(formatter.string(from: 3601)) // 1:00:01

f:id:BlueThree:20190627000435p:plain
DateComponents_unitsStyle一覧

  • DateComponentsFormatterでは時間以外にもallowedUnitsを指定し,日時に変換する事が可能
  • zeroFormattingBehaviorを指定することである単位で値が0になるときの振る舞いも指定する事が可能

5)DatePicker

  • ユーザーが日付や時刻等を設定するためのUIKitの部品
  • 日付や時間を選択するためにホイールを回転させて使用
  • UIDatePickerはViewのサブクラス
  • UIDatePickerには4つのモードがある(Date and Time, Date, Time, Count Down Timer)
  • UIDatePickerは設定なしの状態:分の項目は1分刻みになります。
  • これを自由に設定するには、minuteIntervalプロパティを利用する必要がある

参考リンク

www.egao-inc.co.jp

重要参考リンク

【Swift】Dateの王道 【日付】 - Qiita 日付関連クラスのまとめ(Swift3) - Qiita

最後に!!

f:id:BlueThree:20200805191056j:plain:w500

最後までご覧いただき、本当にありがとうございます!!

最近は、たくさんの読者さんから「コメント」や「メッセージ」が届くようになりました!!
皆さんと会話できて嬉しいですし、コメントで毎日励まされています。

ありがとうございます!

これからも、ゴルトン社長は「毎日」ブログを更新しています! www.goruton.com www.goruton.com www.goruton.com www.goruton.com www.goruton.com

皆さんから人気がある記事

www.goruton.com www.goruton.com www.goruton.com www.goruton.com www.goruton.com

まとめ記事

www.goruton.com www.goruton.com www.goruton.com www.goruton.com www.goruton.com www.goruton.com www.goruton.com