Slide 1

Slide 1 text

日付と正規化 川津 雄介(@KawatsuYusuke) ※この資料はインターネットに公開してます

Slide 2

Slide 2 text

2
 © Link and Motivation 株式会社リンクアンドモチベーション エンジニアリングマネージャー 川津 雄介 Yusuke Kawatsu ● 前職は某複写機メーカーでエンジニアしてた ● OSレイヤーからWeb/モバイルまで何でもやる ● 開発室全体を横断した生産性向上に注力! https://github.com/megmogmog1965 https://qiita.com/megmogmog1965 https://twitter.com/KawatsuYusuke

Slide 3

Slide 3 text

入出力の正規化 01

Slide 4

Slide 4 text

【例】Python 2 時代の文字列オブジェクト Python 2 str 型 unicode 型 ● バイナリ配列なので、元の文字エンコーディングが何か分から ない ● 内部的に UCS-2 / UCS-4 で統一されている ● str 型と `+` 演算する際は、自動的に unicode 型に変換する

Slide 5

Slide 5 text

様々な Encoding の文字列が混在する Python 2 時代のプログラム空間 Shift-JIS UTF-8 UTF-8 (bom) Linux OS (Locale = ja_JP.eucjp) ”文字リテラル” UTF-8 ascii "+" 演算 str 型 (*default) str 型 (*default) str 型 (*default) str 型 (*default)

Slide 6

Slide 6 text

Shift-JIS UTF-8 様々な Encoding の文字列が混在する str1 ”あああ” str2 ”あああ” Shift-JIS UTF-8 `str1 + str2` をすると死ぬ 文字列が何の Encoding か? は分からない

Slide 7

Slide 7 text

出入口で「正規化」をするのが鉄則 Python 2 時代のプログラム空間 Shift-JIS UTF-8 UTF-8 (bom) Linux OS (Locale = ja_JP.eucjp) ”文字リテラル UTF-8 Unicode型 Unicode 演算のみ Unicode型 Unicode型 Unicode型 Python 3 は 言語仕様でこれを強制 入る・出る 瞬間に 絶対にする!

Slide 8

Slide 8 text

日時とタイムゾーン (協定世界時) 02

Slide 9

Slide 9 text

TIMEZONE https://www.timeanddate.com/time/map/ 2024-01-01T00:00:00Z 2024-01-01T09:00:00+09:00 この2つは同じ時間です

Slide 10

Slide 10 text

「深夜」と「朝」だけど、同じ「時」! 2024-01-01T00:00:00Z 2024-01-01T09:00:00+09:00 ロンドン 日本

Slide 11

Slide 11 text

世界中のみんなが同じ「時」を生きている ©福本伸行/講談社 1990年代当時の人口 57 億は、 現在の人口に換算すると 79 億である。

Slide 12

Slide 12 text

ISO 8601 日時の国際標準表記として ISO 8601 があります。 ● 国によって異なる表記揺れを統一 ○ US: Tuesday, April 3rd, 2024 ○ 日本: 2024年 4月 3日 (火) ● タイムゾーン情報を持つ協定世界時 2024-01-01T12:30:45+09:00 年 月 日 時 分 秒 TIMEZONE Offset

Slide 13

Slide 13 text

ISO 8601 以下はすべて同じ時間を表しています。 2024-01-01T00:00:00+ZZ:ZZ 年 月 日 時 分 秒 TIMEZONE Offset 2024-01-01T00:00:00+00:00 年 月 日 時 分 秒 TIMEZONE Offset 2024-01-01T09:00:00+09:00 年 月 日 時 分 秒 TIMEZONE Offset ロンドン (深夜) ロンドン (深夜) 日本 (朝)

Slide 14

Slide 14 text

日時の正規化 03

Slide 15

Slide 15 text

Timezone を意識していないと... プログラムのメモリ空間 日本時間 ロンドン時間 Linux OS (ワシントンD.C., US) Time.now() ※紆余曲折あり、 混ざっている... サーバー 時間 ずれてる やん☠ 09:00 (表記) 00:00 (表記) 20:00 (表記) UTC +9 UTC +0 UTC -4 一度失われた offset は絶対に分からない! 前日の...

Slide 16

Slide 16 text

あるべき理想(正規化) プログラムのメモリ空間 日本時間 UTC 時間 Linux OS (US, ワシントンD.C.) Time.now() offset付き時刻型 サーバー 時間 同じ基準✨ 09:00+09:00 (ISO 8601) 00:00Z (ISO 8601) 20:00-04:00 (ISO 8601) Timeオブジェクト (offset 付き) Timeオブジェクト (offset 付き) Timeオブジェクト (offset 付き) Timeオブジェクト (offset 付き) API は ISO 8601 形式のみ 出入口で 必ず正規化 UTC +9 UTC +0 UTC -4

Slide 17

Slide 17 text

JSON API { "date": "2024/01/01", "start": "10:00", "end": "12:00" } { "start": "2024-01-01T10:00:00+09:00", "end": "2024-01-01T12:00:00+09:00" } API のリクエスト・レスポンスの日時パラメータは ISO 8601 に。

Slide 18

Slide 18 text

フロントコード ISO 8601 表記の日時文字列であれば、何も考えずとも良い。 // 日本時間の朝 09:00. jst = new Date("2024-01-01T09:00:00+09:00") // 協定世界時 00:00. utc = new Date("2024-01-01T00:00:00+00:00") // false. jst < utc jst > utc. // true. jst.getTime() === utc.getTime()

Slide 19

Slide 19 text

フロントコード オフセット表記がない場合は、ブラウザのロケールが尊重される。 // Mon Jan 01 2024 09:00:00 GMT+0900 (Japan Standard Time) jst = new Date("2024/01/01 09:00:00") // ==> 9 jst.getHours() // ==> 0 jst.getUTCHours() // true. jst.getTime() === new Date("2024-01-01T09:00:00+09:00").getTime() // false. jst.getTime() === new Date("2024-01-01T09:00:00Z").getTime()

Slide 20

Slide 20 text

バックエンド (Ruby / Rails) プログラムの出入口で即座に正規化をすること。 ● 外界からの入力の直後に Time オブジェクト正規化する ○ e.g. APIリクエスト、標準入力、ファイル読み込み... ● 外界への出力の直前に ISO 8601 表記文字列に正規化する ○ e.g. API レスポンス、標準出力、ファイル書き出し ● DB から取り出した日時は ActiveSupport::TimeWithZone オブ ジェクトになっている。Ruby 標準の Time オブジェクト互換 ● 日時表現の文字列が出入口以外で存在しないこと! jst = Time.zone.parse('2024-01-01T09:00:00+09:00') utc = Time.zone.parse('2024-01-01T00:00:00+00:00') // true. jst == utc

Slide 21

Slide 21 text

バックエンド (Python 3) fromisoformat() で ISO 8601 文字列から datetime を生成。 import datetime aware_utc = datetime.datetime.fromisoformat('2024-01-01T00:00:00+00:00') aware_jst = datetime.datetime.fromisoformat('2024-01-01T09:00:00+09:00') # True. aware_utc == aware_jst # tzinfo なし. (mode=naive) naive_now = datetime.datetime.now() # tzinfo あり. (mode=aware) aware_now = datetime.datetime.now(datetime.timezone.utc) # TypeError: can't compare offset-naive and offset-aware datetimes aware_utc < naive_now # True. aware_utc < aware_now datetime オブジェクトには tzinfo のあり・なしで naive / aware の2モードがあり、異なるモード間での比較演算はできない。

Slide 22

Slide 22 text

RDB (MySQL) 一般的には次のどれかが良い。 ● offset 付き DATETIME 型 ○ 2024-01-01 09:00:00+09:00 ● UTC+0 時刻に統一した DATETIME 型 ○ 2024-01-01 00:00:00 ● UTC+0 時刻に統一した TIMESTAMP 型 ○ 2024-01-01 00:00:00.000000 Railsでは、ActiveSupport::TimeWithZone は config.time_zone で設定したタ イムゾーンの日時に自動的に変換されて DB へ保存されます。 Ruby プログラム中で日時を文字列としては扱わず、Time 型等のオブジェクト 正規化がされていれば DB 中では一つのタイムゾーン時刻で統一されます。

Slide 23

Slide 23 text

さいごに

Slide 24

Slide 24 text

出入り口で「正規化」 プログラムは入出力の処理装置です。これは今も昔も変わりません。 組み込み系だろうが、PC・モバイルアプリ、Webサービスでもそうです。 今回紹介した「日時」にかかわらず、プログラムの外界からの入力、外界への 出力は全てが「正規化」の対象です。 e.g. ● 文字エンコーディング ● 浮動小数点サイズ ● Localization ● … 不要な IF 分岐やプログラマ間での混乱・暗黙のしきたりを避けるためにも、 プログラム内部では「正規化」(※統一)がされた状態を設計しましょう。

Slide 25

Slide 25 text

Thank you for Listening ! !