Rails: exifにタイムゾーンがないからどんな感じで保存しようかなと思った話

[Classi Advent Calendar 2019](https://qiita.com/advent-calendar/2019/classi) の18日目、Railsアプリケーションエンジニアをやっている@HokotaMisakiです。

今回書くお話は**Classiではなく、自宅で作っている**野鳥観察記録サービスで体験したものなのですが、マニアックでピンポイントなケースだと思ったので紹介します（テーマ的にRails絡んでるからセーフだと思ってる）。

主に、exifにタイムゾーンの有り無しを確認することになった経緯についての話になります。

※ なおexifとは、画像の中に埋め込まれているメタデータのことです。位置情報や時刻、撮影カメラの設定情報などが保存されています。

# まずは結論から
この2つのあわせ技で行くことにしました。

1. ユーザに**自身のタイムゾーンを登録**してもらう
2. アプリケーションはユーザ毎のタイムゾーンで動き、時刻を**そのままアプリケーションのタイムゾーンで保存**する

#### 「えっ、exifらしさもないし、なんか普通・・・」
って感じなんですが、ではなぜこれに行き着いたのか、なぜこれをしなければならなかったのか、を以下に記載します。


# 前提
自宅で作っている野鳥観察記録サービス「[ZooPicker](https://zoopicker.com/)」は、世界中で撮った野鳥の写真とフィールドノート（観察場所や時間、観察種類などを記録するもの）を投稿していくサービスです。
投稿したものは公開されるので、野鳥情報の共有サービスとも言えます。
ゆくゆくは**海外展開もしていきたいので、I18n系の対応**も徐々に進めています。


# ことの発端

バーダー（野鳥を追いかけている人たちのこと）にとって、野鳥が「どこで」「いつ」出たのか、は大事な情報です。
なので素敵な野鳥写真が投稿されると、「これ！！！！どうやったら見れるの？？？」となります。

撮った写真の場所を登録する機能はあったので（exifの位置情報は捨ててるので手入力なんですが）、
撮った写真の**時刻**（⇒ 「撮影現地時刻」）も登録できるようにしよう！と思いました。

なお、写真の投稿はだいたい帰宅後に行うので、投稿日時は使えません。


### やりたいこと： 世界の誰が見ても、撮った現地の時刻で表示させたい

ご存知のとおり、時刻およびデータベースの時刻系カラムにはタイムゾーンがあり、
かつ、ActiveRecordはそれをアプリケーションのタイムゾーンに応じて出してくれるという便利機能を持っています。

加えて、写真にはexif情報があるので、
この**exifにきっと時刻情報あるだろう**から、それ保存すればいいじゃーんと思いました。

下の絵のように、「撮影現地時刻」がアプリケーションのタイムゾーンに自動変換されちゃう事態は避けたかったわけです。
<img width="812" alt="スクリーンショット 2019-12-18 11.25.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/531455/e9870831-764d-f56b-0510-29c77b75de1b.png">



# つまづいた点

### exifに時刻はあるけどタイムゾーンは無い
はい。タイトルのとおりなのですが。おおよそ無いようです。
Wikipediaより：
[Exchangeable image file format](https://ja.wikipedia.org/wiki/Exchangeable_image_file_format)
> 撮影日時の情報は、UTCとタイムゾーンを組み合わせたものではなく、機種依存のローカルタイム（現地時刻）のみで記録され、タイムゾーン情報が記録されていないので、海外旅行や出張などタイムゾーンをまたいで移動、生活する際に問題となることもある。なお、タイムゾーン情報が記録できるカメラなどもあるが機種依存の機能であって、Exif共通の仕様ではタイムゾーン情報の付加には対応していない。

言われてみればそうかーという感じです。カメラがWebに繋がんないとですもんね・・・

なお、時刻情報は`OriginalTime`という項目で存在します。

### 「投稿日時」は時差計算されてもいいけど、「撮影時刻」は現在地の時刻を出したい

ActiveRecordは、アプリケーションのタイムゾーンに応じて時刻を表示してくれますね。
ところが、「撮影現地時刻」だけは自動変換されてほしくありません。
じゃあどこのタイムゾーンで表示するの！カメラに現地時刻入ってないのに！となりました。

### カメラのOriginalTimeがどこのタイムゾーンかの判定方法がわからない
わかれば、場所と照らし合わせて撮影時刻算出できるのになぁ。



# 結果
### とりあえず ユーザのタイムゾーン≒カメラのタイムゾーン ってことにしよう
今回のケースを実現するロジックは、

```
1. 何らかでタイムゾーンを保存（exifの位置情報でタイムゾーンを算出 or ユーザにタイムゾーンを設定してもらう）
2. カメラのOriginalTimeから時差計算して「撮影現地時刻」を算出
3. 保存
4. 表示するときに「保存したタイムゾーンで出す」をやる
```
と思っています。

exifからの算出は、もっとタイムゾーン付きカメラが普及してからにしよう・・・と思いました。
それまでは、多少そぐわないケースもあるかもしれないけど、だいたい一致する「ユーザの希望タイムゾーン」で。

現在は、時刻が取れたらそのまま自動入力、修正がある場合は手入力可、としています。

ちなみに1度**「文字列で時刻保存する・・・？」**と思ったです。

### できあがったコード
```ruby
datetime =
  if v[:exif][:dateTimeOriginal]
    # exifにはタイムゾーン情報が基本ない
    # とりあえずtimezone設定を使う
    timezone = Time.current.strftime('%z')
    DateTime.strptime(
      v[:exif][:dateTimeOriginal].gsub('-', ':') + " #{timezone}",
      '%Y:%m:%d %H:%M:%S' + " %z"
    )
  end
```

### 何も解決してない

実は「ユーザ自身のタイムゾーンを登録する機能」は**未実装なので、サービス的にはまだ何も解決してません！**


### 参考： システム的なこと
* Rails5.1（当時）
* 画像投稿するときは、フロントでバイナリからexifを取り出してサーバにリクエストしている
    * exifは保存してない
    * [exif-js](https://github.com/exif-js/exif-js)を使ってる

# おわりに

おおよそのケースではRailsはタイムゾーンのことをほとんど意識せずに済むような仕組みが整っていると思います。
今までの業務でも海外展開しているサービスは触ったことがなかったので、今回改めて勉強になったなぁと思いました。

明日は @hakshu さんです！