SwiftでUNIX時間を扱うの巻

iOS

UNIX時間とDate型の変換について

UNIX時間からDate型の文字列に変換する際にちょっと詰まったので備忘録としてメモしておきます。

今回必要になったタスクは、APIを叩いてUNIX時間のデータを取得してそれをDate型に変換するというものです。

{
    "num": 457,
    "start": 1572328800,
    "end": 1572458400,
    "stage": 3,
}

返ってくる値は以下のような感じですね。

このうち1572328800という値がUNIX時間で、UTC時刻における1970年1月1日午前0時0分0秒からの経過秒数を表しています。

ちなみに2019年10月29日06:00:00を表してるんよ。

対してDate型というのは 20191029060000のような、人間がみて直感的にわかりやすい表現です。

APIを叩いて取得したUNIX時間のタイムスタンプをDate型にして別のAPIを叩くのが目的なので、これをDate型に変換します。

ちなみにAPIが受け付けるのは2019102906(四桁の西暦、0埋め月、0埋め日、24時間制の0埋め時)なのでyyyyMMddHHと指定してあげればいいことになります。

これ、意外と覚えにくいんだよねえ。

Swiftで実装

日付を扱うにはDateFormatter()を使うととっても便利です。

let time: Double = 1572328800
let f = DateFormatter()
f.dateFormat = "yyyyMMddHH"
debugPrint(f.string(from: Date(timeIntervalSince1970: time)))

最終的にDate型に変換するのはDateFormatter().string()という関数なのですが、ここの入力してDate型にtimeIntervalSince1970というオプション?を使うとUNIX時間を引数としてとることができます。

つまり、上のコードはUNIX時間(タイムスタンプ)である変数timeをdateFormatで指定したDate型に変換するというものです。

気をつけなければいけないのが、timeIntervalSince1970の引数は倍精度(Double型)でないといけません。Int型だと何かと問題がありますもんね、わかります。

TimeZone問題

さて、最初は上のコードでいいじゃんと思っていたのですが、北米ユーザの方から「履歴と記録が取得できない」とのバグ報告をいただきました。

そんなバカな、と思っていたのですがシミュレータでTimeZone変更するのってめんどくさくてデバッグしてなかったんですよね。

右(北米)のユーザのShift IDがズレてる!!

ここはすべてのユーザが同じ値を返さなければいけないはずなのに、TimeZoneによって値が変わってしまうことが判明したのです。

ということで、調べた結果なんとか修正することができたのでメモしておきます!!

TimeZone考慮したコード

let time: Double = 1572328800
let f = DateFormatter()
f.dateFormat = "yyyyMMddHH"
f.timeZone = NSTimeZone(name: "GMT") as TimeZone?
debugPrint(f.string(from: Date(timeIntervalSince1970: time)))

Date型に変換するDateFormatter()に対してTimeZoneを指定してあげることで、プログラムを実行するユーザがどこにいても常に指定されたTimeZoneで値を返すようにすることができました。

今回の場合は標準時刻を使いたかったのでGMTを指定していますが、お好きな標準時刻を使ってもらって大丈夫です。

せっかくタイムゾーンの話をしたのでタイムゾーンの関連書籍を貼ろうとしたのですが全く見つからなかったので仕方なく時計の紹介をしておきます。

個人的にはオメガのシーマスターがお気に入りです(本当)

まとめ

Swiftって最初はとっつきにくくて面倒くさい感じだったんですが、慣れてくると面白いなあって感じるようになってきました。

ただ、自分がソースコードを複数持っているプロジェクトを書いたことがないので変数の定義があっちらこっちらでわけわからないことになっちゃっているのがもったいないです。

コードの書き方というよりは、管理の仕方とかを学びたいですね。

コメント

  1. より:

    ああ…これも Cocoa あるあるですね。他にもシステムで和暦を指定してたりすると年だけシステムから取得しても間に formatter が入っていると西暦ではなかったりするので、シリアライズする際はどのような取扱いがされているか把握しておかないといけないですね。

  2. より:

    何度も参照する変数や定数は共有ヘッダなどに纏めるのが基本だと思うのですが、結合性とのトレードオフでもあります。共有ヘッダを変更するだけで全体がコンパイルされるのは無駄があるわけです。これはバランスが難しいところです。
    Cocoa の場合 UTF16 で記録された Localizable.strings を NSLocalizedString で呼び出す仕組みがあります。これは主にアプリのメッセージなどで参照しますが、あらかじめ決めた key による辞書なので、翻訳したい場合でも再度コンパイルしなくても大丈夫な疎結合な例です。
    NSNotification による通知を登録する際に使う key は const NSString * とかヘッダに書いてありますが、実体はライブラリに含まれていて、ライブラリを使う場合はポインタのアドレスとリンクさせて使います。ポインタの内容はライブラリ作成者が自由に変えられるので、その意味では疎結合と言えます。
    switch 文やフラグなどは NS_ENUM や NS_OPTIONS で定義します。最近のコンパイラは優秀なので定数は埋め込まれる可能性がありライブラリの場合は定義を後で変更すると不具合がでる場合があります。
    クラスのあるプログラムの場合、クラス変数や定数として利用する方法もありますが、Cocoa ではあまり使わない手だったように思います。
    他にも NSHomeDirectory の様に函数呼び出しで決まった値を共有したり、シングルトンクラスを作って情報を共有する方法も考えられます。

タイトルとURLをコピーしました