質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Elixir

Elixirは、並列処理や関数型に特化した、Erlang VM (BEAM) 上で動作する汎用プログラミング言語です。分散システム、耐障害性、ソフトリアルタイムシステムなどの機能を持ちます。

Q&A

解決済

3回答

4348閲覧

PostgreSQL + Ecto + Timex でタイムゾーン付き日時情報の扱い方

退会済みユーザー

退会済みユーザー

総合スコア0

Elixir

Elixirは、並列処理や関数型に特化した、Erlang VM (BEAM) 上で動作する汎用プログラミング言語です。分散システム、耐障害性、ソフトリアルタイムシステムなどの機能を持ちます。

0グッド

1クリップ

投稿2017/06/02 04:48

編集2017/06/04 04:16

###前提・実現したいこと

Ecto + Timex を使って、PostgreSQL の timestamp with timezone を扱いたいんですが、Model で持っている日時とデータベースに保存される時間がずれてしまうので、理由と対処法を知りたいです。

もっと別の方法があるよ、とかでもいいです。

参考

↑を読むと、カスタム複合タイプ datetimetz つくって対処するといいよって書いてある。

###発生している問題・エラーメッセージ

インタラクションに従って、データベースに datetimetz 型をつくって、Elixir からはTimex.Ecto.DateTimeWithTimezone 型の変数を入れられるようにしました。

sql

1CREATE TYPE datetimetz AS ( 2 dt timestamptz, 3 tz varchar 4);

Timex.Ecto.DateTimeWithTimezone な変数の中身をダンプすると以下のようになっています。

elixir

1IO.inspect model.inserted_at 2# {{{2017, 6, 2}, {11, 41, 57, 93698}}, "Asia/Tokyo"}

データベースには以下のように登録されています。

sql

1("2017-06-02 20:41:57.093698+09",Asia/Tokyo)

9時間ずれている。。実際の時刻は上の方です(今日の午前11時に実行しました)。

###該当のソースコード

migrationファイル

elixir

1 def change do 2 create table(:messages) do 3 add :message, :text 4 add :inserted_at, :datetimetz 5 end 6 end

モデルクラス

elixir

1schema "messages" do 2 field :message, :string 3 field :inserted_at, Timex.Ecto.DateTimeWithTimezone 4end

インサート

elixir

1changeset = Message.changeset(%Message{}, %{message: message, inserted_at: Timex.now("Asia/Tokyo")}) 2Repo.insert(changeset)

###試したこと

すみません、何を試せばいいのかすら思いつかず。。

Ectoのコード読んだくらいです(でもよく分からなかった。。)

2017-06-04 13:00 追記

@mhashi さんのコメントにもあるように、インサートの際に UTC で入れないとダメなようです。
そもそも PostgreSQL の timestamp with timezone はタイムスタンプを UTC で持っており、それを、必要に応じて変換してから出力しているみたいなので、そこに JST の日時を入れてしまうと二重に +9 されてしまうようです。

たどり着いたコードとしては、
Ecto.Adapters.Postgres.Timestamp.encode/1 の中で

elixir

1:calendar.datetime_to_gregorian_seconds({{2017,6,4},{13,0,0}})

のようにして秒に変換しているのですが、当然ながらここではタイムゾーンは考慮されてないです。このタプルは、単純に Timex.Ecto.DateTimeWithTimezone の値 {{{2017,6,4},{13,0,0}}, "Asia/Tokyo"} のタイムスタンプ部分を取ってきているにすぎません。

2017-06-04 13:00 追記ここまで

###補足情報(言語/FW/ツール等のバージョンなど)

Elixir 1.4
ecto 2.1
timex 3.1
timex_ecto 3.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

私はElixirで開発をしてないので間違っているかもしれませんが、こちらに Timex.Ecto.DateTimeWithTimezone の使い方についての詳しい説明があるのを見つけましたので、それに基づいて回答します。

まず、DateTimeWithTimezoneは、PostgreSQLのtimestamptz (timestamp with timezone) にデータを格納することはできないようです。

上記のドキュメントに書かれている使い方を説明します。

この機能は現状PostgreSQLのみで使用できる。

対象のデータベースで必ず以下のSQLを実行しなければならない。これにより datetimetz 型が作られる。

sql

1CREATE TYPE datetimetz AS ( 2 dt timestamptz, 3 tz varchar 4);

あとは detatimetz 型を使ってテーブルを定義すればよい。

sql

1CREATE TABLE example ( 2 id integer, 3 created_at datetimetz 4);

以上です。試してみてください。

投稿2017/06/02 05:28

tatsuya6502

総合スコア2035

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/06/02 05:44

@tatsuya6502 さん、回答ありがとうございます。 本文には書いてなかったですが、書いていただいたように、複合型を作ってます。 Timex.Ecto.DateTimeWithTimezone の値を Ecto 経由で入れる場合はこの方法しかないみたいなんですよね、現状。。
tatsuya6502

2017/06/02 06:59

この通りにやっているのに期待通りに動かないということですか? とすると、timex か timex_ecto のバグの可能性が考えられますよね。使用しているバージョンは、質問に書かれているように 3.1(.0) でしょうか? それとも最新版ですか?(timex 3.1.15、timex_ecto 3.1.1) GitHub Issueをざっと眺めた所、timex 3.0.6 より前のバージョだと、タイムゾーンが + の時に問題があった(parseエラーになった)みたいです。その問題自体は3.0.6で直っているそうですが、開発者の方は恐らく北米(UTC-0800 などのマイナス圏)に居るのでしょうから、プラス圏のテストが不十分なのかもしれません。なんらかのバグが残っている可能性もありそうです。
退会済みユーザー

退会済みユーザー

2017/06/02 08:01

mix.lock を確認したところ、最新版でした(timex 3.1.15、timex_ecto 3.1.1)。 パースエラーなどは起こっておらず、insert は正常に行われるのですが、データを見ると、+9時間ずれているという現象です。 Timex -> Ecto へデータを渡すときに値が変わっていないか、デバッグしてみます。
tatsuya6502

2017/06/02 10:18

バージョンについて承知しました。すぐには無理なのですが、時間ができたら私も試してみようと思います。
退会済みユーザー

退会済みユーザー

2017/06/02 10:19

すみません、ありがとうございます!
guest

0

ベストアンサー

timestamp with time zoneのカラムにUTCではなくローカルタイムを入れているため、
日本時刻(UTC+9)に二重に+9されているのだと思います。

("2017-06-02 20:41:57.093698+09",Asia/Tokyo)

これの時刻の末尾+9なのでUTC 11:41...で登録されてますね。

without time zoneでカラムを作るか、 Timex.now("Asia/Tokyo")Timex.nowとすれば要求を満たせるかもしれません。

投稿2017/06/02 05:23

mhashi

総合スコア408

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/06/02 05:41

@mhashi さん、回答ありがとうございます。 カラムを timestamp with timezone にして、UTC でインサートしたらうまくいきました。 ```elixir INSERT INTO "test" ("ts") VALUES ($1) RETURNING "id" [{{2017, 6, 2}, {5, 39, 48, 589422}}] {:ok, %SlackBot.Test{__meta__: #Ecto.Schema.Metadata<:loaded, "test">, id: 2, ts: #<DateTime(2017-06-02T05:39:48.589422Z Etc/UTC)>}} ```
退会済みユーザー

退会済みユーザー

2017/06/02 06:15

しかし、なんとなくもやもやはしていて、アプリケーション内でも DB 内でも JST で日時を扱いたいんですが、Repo.insert のときだけ、UTC に戻してやらないといけないの?っていう素朴な疑問が残ります。 しかも、Timex.Ecto が推奨している DateTimeWithTimezone はタプルで表現すると、タイムスタンプとタイムゾーン名を持つタプルになるので、タイムスタンプには UTC の値が入り、タイムゾーン名には "Asia/Tokyo" と入っているっていう状態になってしまい、ちょっと気持ち悪いなぁ、と。 ``` iex(11)> Timex.now("Asia/Tokyo") #<DateTime(2017-06-02T15:10:51.979615+09:00 Asia/Tokyo)> iex(12)> Timex.now("Asia/Tokyo") |> Timex.Ecto.DateTimeWithTimezone.dump {:ok, {{{2017, 6, 2}, {15, 11, 19, 490587}}, "Asia/Tokyo"}} ``` 最終的には Timex.Ecto.DateTimeWithTimezone を使わないでやるしかなさそうですね。
guest

0

Elixir
{{{2017, 6, 2}, {11, 41, 57, 93698}}, "Asia/Tokyo"}

SQL
("2017-06-02 20:41:57.093698+09",Asia/Tokyo)

これらはズレていなく、一致しているかと思います。
SQLの方の

20:41:57.093698+09

の"+09"の部分は標準時間と比べて日本は9時間早いということを表しているので、データとしては

20:41:57.093698+09 = 11:41:57.093698

で、Modelの方と一致していると思います。
以下のサイトは参考になるかもしれません。
http://qiita.com/kidatti/items/272eb962b5e6025fc51e

投稿2017/06/02 05:19

Naoki_Tsuchi

総合スコア15

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/06/02 05:54

@Naoki_Tsuchi さん、回答ありがとうございます。 すみません、一致しているとのことですが、ちょっとピンとこないです。 試しに直接データベース操作して確認してみました。 ```sql testdb=# insert into test values(current_timestamp); INSERT 0 1 testdb=# select * from test; ts ------------------------------- 2017-06-02 14:29:14.588632+09 (1 row) ``` iex で確認すると、 ```elixir iex(1)> Timex.now() #<DateTime(2017-06-02T05:48:05.476850Z Etc/UTC)> iex(2)> Timex.now("Asia/Tokyo") #<DateTime(2017-06-02T14:48:18.877824+09:00 Asia/Tokyo)> ``` UTC でインサートすると、 `2017-06-02 14:39:48.589422+09` と登録されたので、@mhashi さんからのコメントにもあるように、データ型は timestamp with timezone でつくりつつ、Elixir からは UTC 時間で入れるようにしないとダメってことでしょうか?(だとすると、Timex.Ecto.DateTimeWithTimezone の意味は。。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問