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

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

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

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

DateTime

多くのプログラミング言語におけるDateTimeオブジェクトは、日付と時間に関する演算と出力を行います。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

タイムゾーン

タイムゾーンは地球の各地域ごとに定義されている標準時間です。

Q&A

解決済

1回答

910閲覧

UTC より早いタイムゾーンを持たせたユーザーが日付を選択すると date < Date.current が正しく判定されなくなる

punchan36

総合スコア105

Ruby on Rails 5

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

DateTime

多くのプログラミング言語におけるDateTimeオブジェクトは、日付と時間に関する演算と出力を行います。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

タイムゾーン

タイムゾーンは地球の各地域ごとに定義されている標準時間です。

0グッド

0クリップ

投稿2022/04/28 07:31

編集2022/04/30 01:35

前提

ユーザーがイベントの内容(タイトル、日時など)を投稿できる SNS を作成しております。
投稿機能は完成しており、その中に「昨日以前の日付を選択した場合はエラーメッセージを出力する」と言う記述があります。

「昨日以前の日付」を date < Date.current と記述しているのですが、今日を選択しても post.rb で設定した "Date must be greater than today" のエラーがなぜか出てしまいます。明日以降を選択すると無事投稿できます。
そしてこの記述を date <= Date.yesterday に変えるとエラーはなくなります。上のコードと意味は同じだと思うのですが、なぜこのような挙動になるのか不思議です。

そしてこのエラーが発生する原因は、恐らくタイムゾーンが関係していると言う所まで分かりました。
User モデルには time_zone カラムも持たせており、ユーザーはタイムゾーンを選択できるようになっております。
UTC (GMT+00:00) を含み UTC よりも遅いタイムゾーン (メキシコ GMT-06:00 など) をユーザーが選択している場合は問題ないのですが、UTC よりも早いタイムゾーン (東京 GMT+09:00 など) を選択した場合、ユーザーがイベント日時を「今日」と選択したにも関わらず上記のエラーがなぜか作動してしまします。

本日4月28日を選択して保存を試みた時のパラメーターですが、ちゃんと4月28日として認識はされているようです…。

Started POST "/posts/create" for ::1 at 2022-04-28 16:27:04 +0900 Processing by PostsController#create as HTML Parameters: {"utf8"=>"✓", "authenticity_token"=>"省略", "date"=>"2022-04-28", "commit"=>"Save"}

またタイムゾーンの取り扱いにつきましてはtime_zone_options_for_selectを使用しております。
画像のようなセレクトボックスで、保存される値は Tokyo と言った地名のみで (GMT+09:00) などの文言は保存されません。

イメージ説明

[1] pry(main)> User.find(1).time_zone User Load (3.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => "Tokyo"

実現したいこと

  • エラーの原因を究明したい。
  • どのタイムゾーンを選択しても、日時選択時に今日は今日として判定して欲しい。

該当のソースコード

users/new.html.erb

Ruby

1<%= form_with url: users_create_path, local: true do |form| %> 2 <div>Time zone</div> 3 <div> 4   <%= form.select :time_zone, time_zone_options_for_select(@user.time_zone, nil, ActiveSupport::TimeZone) %> 5 </div> 6<% end %>

users_controller.rb

Ruby

1def create 2 @user = User.new( 3 # 他は省略 4 time_zone: params[:time_zone] 5 ) 6 if @user.save 7 session[:user_id] = @user.id 8 redirect_to("/users/#{@user.id}") 9 else 10 render("users/new") 11 end 12end

posts/new.html.erb

Ruby

1<%= form_with url: "/posts/create", local: true do |form| %> 2 <div>Schedule</div> 3 <div><%= form.datetime_field :date, type: "date", id: "inputDate", value: @post.date&.strftime("%Y-%m-%d"), placeholder: "Y-m-d" %></div> 4<%= form.submit id: "button", type: "submit", value: "Save", onclick: "myfunk()" %> 5<% end %>

posts_controller.rb

Ruby

1def create 2 @post = Post.new( 3 # 他は省略 4 date: params[:date] 5 user_id: @current_user.id 6 ) 7 if @post.save 8 redirect_to("/posts/#{@post.id}") 9 else 10 render("posts/new") 11 end 12end

post.rb

Ruby

1validate :pretend_ago 2 3def pretend_ago 4 errors.add(:date, 'must be greater than today') if date.present? && date < Date.current 5end

config/application.rb

Ruby

1module アプリ名 2 class Application < Rails::Application 3 # config.time_zone = 'Asia/Tokyo' 4 config.active_record.default_timezone = :local 5 end 6end

scheme.rb

Ruby

1create_table "users", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| 2 # 他は省略 3 t.datetime "created_at", null: false 4 t.string "time_zone" 5end 6 7create_table "posts", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", force: :cascade do |t| 8 # 他は省略 9 t.datetime "created_at", null: false 10 t.datetime "date" 11end

タイムゾーンのデータ型は string ですので、time_zone_options_for_selectにて言及されている以下の文言も満たしているかと思います。

The selected parameter must be either nil, or a string that names an ActiveSupport::TimeZone.

試したこと

attributes_before_type_cast メソッドを使い、以下の2点を型が変換される前と後でどのように変わっているかを調べてみました。

  • ユーザーが新規作成された日時
  • 投稿が新規作成された日時
# ユーザーが新規作成された日時 [1] pry(main)> User.find(1).created_at User Load (1.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => Thu, 28 Apr 2022 05:50:11 UTC +00:00 [2] pry(main)> User.find(1).created_at_before_type_cast User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 => 2022-04-28 14:50:11 +0900
# 投稿が新規作成された日時 [3] pry(main)> Post.find(1).created_at Post Load (0.6ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 => Thu, 28 Apr 2022 06:08:25 UTC +00:00 [4] pry(main)> Post.find(1).created_at_before_type_cast Post Load (0.6ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 => 2022-04-28 15:08:25 +0900

UTC から JST に変換され、書式も変わっています。しかしこれは以下の2点が動いた結果かと思いますので、問題はないのではないかと思いました。

  • application.rb で指定したタイムゾーン:config.active_record.default_timezone = :local
  • schema.rb にある datetime 型: t.datetime "created_at", null: false

こちら数日前に調べた時は created_at は JST、 created_at_before_type_cast+0900 で表示されていたのですが、今日調べると created_at はなぜか UTC で表示されていました…。

またサーバーの再起動、 rails tmp:clearRails.cache.clear も試しましたが挙動は変わりませんでした。

どなたかご助言を頂けますと有難いです。

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

ruby 2.6.8p205 (2021-07-07 revision 67951) [x64-mingw32]
RubyGems 3.0.3.1
Rails 5.2.6
mysql Ver 14.14 Distrib 5.7.36, for Win64 (x86_64)

夜中の 00:00 を回って明らかになった点

1. ビューのエラーメッセージに関する場合分け

  • GMT+01:00よりも早いタイムゾーンを選択した場合、いかなるタイムゾーンであっても「今日」を選択すると Date must be greater than today のエラーが出る。 4月28日23:59までは「28日」を選択するとエラーが発生。4月29日00:00 (夜中) を回ると「28日」ではエラーが消えるが「29日」を選択するとエラーが発生するようになる(GMT+01:00 でも GMT+09:00 でも同じ)。
  • GMT+00:00よりも遅いタイムゾーンを選択した場合、いかなるタイムゾーンであっても「今日」を選択しても問題なく動く。4月29日00:00 (夜中) を回った後「28日」は「昨日」になるので、これを選択するとエラーが発生するかと思いきや、 Date must be greater than today が出ない。何時を過ぎるとエラーが出るようになるのか、要確認。

2. Date.current がタイムゾーン毎にどのような表示になっているか

タイムゾーンに東京(GMT+09:00)を選択した場合(01:00AM に確認)。

[1] pry(#<Post>)> Date.current => Fri, 29 Apr 2022 [2] pry(#<Post>)> Time.current => Fri, 29 Apr 2022 01:00:39 JST +09:00 [3] pry(#<Post>)> Time.now => 2022-04-29 01:00:43 +0900

タイムゾーンにモスクワ(GMT+03:00)を選択した場合(01:02AM に確認)。

[1] pry(#<Post>)> Date.current => Thu, 28 Apr 2022 [2] pry(#<Post>)> Time.current => Thu, 28 Apr 2022 19:02:28 MSK +03:00 [3] pry(#<Post>)> Time.now => 2022-04-29 01:02:32 +0900

これを見ると Date.current は Rails sysem の timezone +09:00 で動いている訳ではなく、各タイムゾーンのリアルな時間(モスクワであれば +03:00)で判定されているようです。
ですが以下が希望の仕様ですので、これは希望していた動きです。

海外にいるユーザーはそれぞれのタイムゾーンの時間で(UTC や JST を気にすることなく)イベント開始時間を設定出来る。他のユーザーにはタイムゾーン毎に変換された日時で各々のビューに表示される。

3. タイムゾーンを東京 (GMT+09:00) に設定してイベントを投稿してみた結果

データベースには UTC で保存されておりました。

# イベントを作成した日時 [1] pry(main)> Post.find(1).created_at Post Load (1.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 => Thu, 28 Apr 2022 15:43:13 UTC +00:00

↑ 日本時間で 4月29日0:43 にイベントを作成したので、9時間遅い UTC に直すと 4月28日15:43 という表示で合っています。

# イベントが開始される日時 [2] pry(main)> Post.find(1).date Post Load (0.7ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 => Fri, 29 Apr 2022 15:00:00 UTC +00:00

↑ 日本時間 (GMT+09:00) で 4月30日6:00(AM) にイベントを開始すると投稿したので、9時間遅い UTC に直すと 4月29日21:00(PM) になるはず…。15:00 とは何の時間なのかが分かりません。

タイムゾーンに関する設定

タイムゾーンに関しては以下の流れで実装しております。
こちらの記事を参考に記述致しました。

user.rb

Ruby

1 validates :time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map(&:name) }, presence: true, on: :create

application_controller.rb

Ruby

1class ApplicationController < ActionController::Base 2 before_action :set_current_user 3 4 # タイムゾーン関連。 5 around_action :user_time_zone, if: @current_user 6 7 def set_current_user 8 @current_user = User.find_by(id: session[:user_id]) 9 end 10 11 # タイムゾーン関連。 12 private 13 def user_time_zone(&block) 14 Time.use_zone(@current_user.time_zone, &block) 15 end 16end

タイムゾーンでの日付の確認 (2)

  • (GMT-01:00) で 4/29 になるタイムゾーン:Cape Verde (GMT-01:00)

(日本時間 4/30 09:06 に確認、現地時間は 4/29 23:06 (PM))

[1] pry(#<Post>)> Date.current => Fri, 29 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Thu, 28 Apr 2022 [4] pry(#<Post>)> Time.current => Fri, 29 Apr 2022 23:06:53 -01 -01:00
  • (GMT+01:00) で 4/30 になるタイムゾーン:Amsterdam (GMT+01:00)

(日本時間 4/30 09:09 に確認、現地時間は 4/30 01:09 (AM))
*サマータイム中で実際は (GMT+2:00) の為、現地時間は 4/30 02:09 (AM)。

[1] pry(#<Post>)> Date.current => Sat, 30 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Fri, 29 Apr 2022 [4] pry(#<Post>)> Time.current => Sat, 30 Apr 2022 02:09:21 CEST +02:00
  • (GMT-04:00) で 4/29 になるタイムゾーン:Samara (GMT-04:00)

(日本時間 4/30 09:16 に確認、現地時間は 4/30 20:16 (PM))

[1] pry(#<Post>)> Date.current => Fri, 29 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Thu, 28 Apr 2022 [4] pry(#<Post>)> Time.current => Fri, 29 Apr 2022 20:16:06 -04 -04:00
  • (GMT+04:00) で 4/30 になるタイムゾーン:Samara (GMT+04:00)

(日本時間 4/30 09:17 に確認、現地時間は 4/30 04:17 (AM))

[1] pry(#<Post>)> Date.current => Sat, 30 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Fri, 29 Apr 2022 [4] pry(#<Post>)> Time.current => Sat, 30 Apr 2022 04:17:58 +04 +04:00
  • (GMT-09:00) で 4/29 になるタイムゾーン:Alaska (GMT-09:00)

(日本時間 4/30 09:19 に確認、現地時間は 4/29 15:19 (PM))
*サマータイム中で実際は (GMT-8:00) の為、現地時間は 4/29 16:19 (PM)。

[1] pry(#<Post>)> Date.current => Fri, 29 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Thu, 28 Apr 2022 [4] pry(#<Post>)> Time.current => Fri, 29 Apr 2022 16:19:52 AKDT -08:00
  • (GMT+09:00) で 4/30 になるタイムゾーン:Tokyo (GMT+09:00)

(日本時間 4/30 09:22 に確認、現地時間は 4/30 09:22 (AM))

[1] pry(#<Post>)> Date.current => Sat, 30 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Fri, 29 Apr 2022 [4] pry(#<Post>)> Time.current => Sat, 30 Apr 2022 09:22:22 JST +09:00

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

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

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

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

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

winterboum

2022/04/28 12:11

user.time_zone がどこでどの様に使われるのか、が読みきれないのですが、そこ教えてください。 time zone によってかつ何時に行ったかによって、Date.current が 27日になったり28日になったりしますよね?それの影響は確認しましたか
punchan36

2022/04/28 13:16

失礼致しました。 ユーザーが time_zone カラムにタイムゾーンを保存する事で、投稿されたイベントの日時が各々のタイムゾーンで表示されるようになります。 こちらは既に上手く動いておりまして、例えばタイムゾーンを東京 (GMT+09:00) に設定しているユーザーが 「4月29日05:00 (AM) にイベントを開催します」と投稿しますと、東京より6時間遅れているモスクワ (GMT+03:00) をタイムゾーンに設定しているユーザーには「4月28日23:00 (PM) にイベントを開催します」とビューで表示されます。日時は投稿一覧ページである posts/index や、投稿詳細ページである posts/show で使用しております。上記の挙動は問題ないのですが、今回は新規投稿作成ページである posts/new でエラーが発生していると言う状況です。 またタイムゾーンによって…の部分ですが、今のところ確認しておりますのは以下の2点のみです。 ・GMT+01:00よりも早いタイムゾーンを選択した場合、いかなるタイムゾーンであっても「今日」を選択しても "Date must be greater than today" のエラーが出る。 ・GMT+00:00よりも遅いタイムゾーンを選択した場合、いかなるタイムゾーンであっても「今日」を選択しても 問題なく動く。 ただしこれらをテストしたのは日本時間の10:00頃から22:00頃にかけてのみです。日付が変わった瞬間や朝9:00 (UTC の日付が変わる瞬間) の確認はしておりませんでしたので、明日にかけて確かめてみようと思います。
winterboum

2022/04/28 13:26

今日 というのは today ではなく current ですね? current と yesterday の挙動の違いの問題があるので、today と current では動きが異なる可能性があるので あと、 time zone は表示だけに使ってるということは、Date.current の実行時の timezone は「いかなるtimezoneを選択したuserであっても」Rails sysem のtimezone +0900 ですね?
punchan36

2022/04/28 14:09

はい。質問させて頂いた内容は全て、 post.rb で today ではなく if date.present? && date < Date.current と記述した上で起こった現象についてのみです(today と記述してもエラーは出てしまうのですが、その事は置いておいて current のみについて言及しております)。また posts/new で「今日」を選択する画面は、追記致しましたがカレンダーにて選択します。 また Date.current についてですが、errors.add(:date, 'must be greater than today') if date.present? && date < Date.current の後ろに binding.pry を置いて確認致しました。 Date.current は日付しか表示されませんでしたので、どのタイミングでこの日付が変わるのかが分かりません…。これも日を跨いで確認しようと思います。 # タイムゾーンに東京(GMT+09:00)を選択した場合。 [1] pry(#<Post>)> Date.current => Thu, 28 Apr 2022 [2] pry(#<Post>)> Time.current => Thu, 28 Apr 2022 23:01:25 JST +09:00 [3] pry(#<Post>)> Time.now => 2022-04-28 23:01:28 +0900 # タイムゾーンにモスクワ(GMT+03:00)を選択した場合。 [1] pry(#<Post>)> Date.current => Thu, 28 Apr 2022 [2] pry(#<Post>)> Time.current => Thu, 28 Apr 2022 16:50:41 MSK +03:00 [3] pry(#<Post>)> Time.now => 2022-04-28 22:54:58 +0900
punchan36

2022/04/28 16:26

「夜中の 00:00 を回って明らかになった点」を追記致しました。 まだ解決の糸口を見つけられずにおります。必要な情報がありましたら追記致しますので、是非ご助言頂けますと有難いです。
winterboum

2022/04/28 22:40

pry の結果を見るとuser.time_zoneは「表示だけに」ではないようです。 time_zoneをどの様に扱っているか、を自然言語ではなくプログラムで見せてください。関わってるところを全部。
punchan36

2022/04/29 09:12

本来 UTC で保存されるはずの所が、タイムゾーンカラムによって変に影響されてしまっているのですかね。。 それから申し訳ありません。string で time_zone カラムを用意してからビューにて time_zone_options_for_select を使用するまでの間に、 user.rb と application_controller.rb でタイムゾーンの設定もしておりました。質問文に載せておりませんでしたので追記致しました。 尚、設定は古い記事ですがこちらのページ中ほどから始まる「タイムゾーン」を参考に実装致しました。 https://www.bokukoko.info/entry/2018/01/06/141041
winterboum

2022/04/29 09:23

どこで設定しているのかわかりにくいので、どのfileのどこなのか書いてください
punchan36

2022/04/29 09:46

失礼致しました。 「タイムゾーンに関する設定」と言う項目で、行ったことを質問文の一番下に追記致しました。
winterboum

2022/04/29 10:00

if: :set_current_user は if: current_user のが良くないかな。 ということは、login したら タイムゾーンはきりかわってるわけですね。 ただ、この方法だめだな。User毎の設定ではなくRails全体の設定になってる。 Aさんが Tokyo でloginしてまだ作業してるときに、Bさんが Los_Angeles でアクセスするとAさんもLos_Angeles での扱いになってしまう。 で、 テストする時間に 4/30 になるタイムゾーンと4/29になるタイムゾーンで Date.current,Date.today, Date.yesterday, Time.current を調べてください。それを + のと ー のと合わせて 4タイムゾーン
punchan36

2022/04/29 12:01

有難うございます。if: :set_current_user は if: @current_user に直しました。 なるほどです…ちゃんとしたコードだと思っておりましたが、ログイン(アクセス)がある度に上書きのようになってしまうのですね。。 個別の設定にすべく改良が必要そうですが、まだ記述の仕方が分からないため先に4タイムゾーンのテストを行いました。 詳細は質問文の最下部に再度追記致しましたが、ご依頼の意図に沿っておりましたでしょうか…? Date.current と Date.today が差異が明示されたくらいで、他には特に気になる点は見受けられませんでした。
winterboum

2022/04/29 13:23 編集

4/29 になる time zone の場合がないですが
winterboum

2022/04/29 13:24

あと、4/30 4/29 というのは例で、日をまたぐ2つの time zone で試していただければ良いです
punchan36

2022/04/29 14:04

理解が間違っておりましたら申し訳ありません。 4/30 になるタイムゾーンの2つ目が書かれていない、と言うご指摘でしょうか? 現在 4/28 の場所は存在しないため、4/29 と 4/30 の日を跨いだタイムゾーンで検証しようと思っております。 (GMT+**)のタイムゾーンであれば現在 4/29 の Tokyo や 4/30 になった Samoa など比較が出来ますが、 (GMT-**)のタイムゾーンは現在全て 4/29 であり、日本時間で 4/30 の昼過ぎにならないと 4/29 と 4/30 の比較が出来ません。 ですので3つのタイムゾーンの比較になっております。。 また明日の昼過ぎになりますと、 (GMT-**)のタイムゾーンは 4/29 と 4/30 の比較が出来るようになりますが、 (GMT+**)のタイムゾーンは全て 4/30 になってしまいます。
winterboum

2022/04/29 14:13

マイナス のがまだだめなのはわかります「 プラスの方の 4/29 になるやつがないです。
punchan36

2022/04/29 14:34

Tokyo (GMT+09:00) は確認時点で 4/29 でしたのでそちらを載せておりましたが、Tokyo 以外でもう一つ…と言う事でしょうか。 Moscow (GMT+03:00) も現在 4/29 ですので、こちらも今確認して追記致しました。 また、時間の遅いタイムゾーン → 時間の早いタイムゾーンの順に項目を並べ直しました。 長引いてしまい申し訳ありません。恐れ入ります。
winterboum

2022/04/29 22:49

ごめんなさい、意図をきちんと伝えてなかったですね。 ある瞬間に 片方は 4/29、片方は4/30 となっているような組み合わせを、+ と ー で用意して、 違いを見たいのです。考察するときに考慮する要素を減らしたかったので。 いまの 時刻が全く違うときにバラバラにやってもなんとかなるかな、見てみます。 そろそろ9時か、 +1:00 と -1:00 での比較ができるかな。
punchan36

2022/04/30 01:36

とんでもないことです。中々意図を汲み取れず申し訳ありません。 今朝9:00~9:30頃にかけて、下記8個のタイムゾーンで表示を確認してみました。 (UTC (GMT+00:00) も確認しましたので、計9か所です。) UTC から見て時差が下記のペア: (GMT-01:00) と (GMT+01:00) (GMT-04:00) と (GMT+04:00) (GMT-09:00) と (GMT+09:00) (GMT-11:00) と (GMT+11:00) またサマータイムの内容も絡んでおりましたが、そちらも考慮された結果になっておりました。 例えばセレクトボックスの表示は「Amsterdam (GMT+01:00)」となっていても、 現在 Amsterdam はサマータイム中ですから、Time.current はきちんと (GMT+2:00) で認識されておりました。 詳細は質問文の最下部に「タイムゾーンでの日付の確認 (2)」の項目で追記致しました。 また字数制限になりましたので昨日の「4つのタイムゾーンでの日付の確認」は一旦削除致しましたが、必要になりましたら再度記入致します。
punchan36

2022/04/30 01:37

また、字数制限で書ききれなかった (GMT-11:00) と (GMT+11:00) のペアの結果はこちらです。 - (GMT-11:00) で 4/29 になるタイムゾーン:American Samoa (GMT-11:00) (日本時間 4/30 09:25 に確認、現地時間は 4/29 13:25 (PM)) ``` [1] pry(#<Post>)> Date.current => Fri, 29 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Thu, 28 Apr 2022 [4] pry(#<Post>)> Time.current => Fri, 29 Apr 2022 13:25:22 SST -11:00 ``` - (GMT+11:00) で 4/30 になるタイムゾーン:New Caledonia (GMT+11:00) (日本時間 4/30 09:26 に確認、現地時間は 4/30 11:26 (AM)) ``` [1] pry(#<Post>)> Date.current => Sat, 30 Apr 2022 [2] pry(#<Post>)> Date.today => Sat, 30 Apr 2022 [3] pry(#<Post>)> Date.yesterday => Fri, 29 Apr 2022 [4] pry(#<Post>)> Time.current => Sat, 30 Apr 2022 11:26:57 +11 +11:00 ```
winterboum

2022/04/30 02:46

なるほど、Date.current はUserのtime zone での日付になってますね。 すると質問。 1. date < Date.current の date は UTCを期待してますか、Tokyo ? time_zone ? 2. で実際にはどうなってるのでしょう。そこに logger.debug で date と Date.current を書き出してみてください
punchan36

2022/04/30 03:42

1. 期待している date は time_zone の値…だと思います。UTC や JST (Tokyo) が基準になっていると、それよりも遅いタイムゾーンにいるユーザーが「今日」を選択しても「昨日以前は選択できません」のような判定になってしまう可能性が出てきてしまうと思うので。 2. 実際には「選択した日にち + タイムゾーン」が表示されましたが、これは time_zone に変換されていると考えて良いのでしょうか…? また日付とともに「時間 (datetime 型)」を選ぶ項目もありますので、そちらも追々確認する必要がありそうです。 新規投稿作成ページで 4/30 を選択した時のログ American Samoa (GMT-11:00) ``` logger.debug(date) > 2022-04-30 00:00:00 -1100 logger.debug(Date.current) > 2022-04-29 ``` UTC (GMT+00:00) ``` logger.debug(date) > 2022-04-30 00:00:00 UTC logger.debug(Date.current) > 2022-04-30 ``` Tokyo (GMT+09:00) ``` logger.debug(date) > 2022-04-30 00:00:00 +0900 logger.debug(Date.current) > 2022-04-30 ```
winterboum

2022/04/30 03:53

GMT ー だと通るが GMT+だと通るはずが通らない、というときのデータください。
punchan36

2022/04/30 04:09

上で試しました American Samoa や Tokyo でその問題が発生しておりました。 UTC (GMT+00:00) を含み、(GMT-**) は無事保存されます。(GMT+**)は全てエラーメッセージが表示されます。 ログを見ても理由が謎です…。 American Samoa (GMT-11:00) →上手く保存される。 ``` logger.debug(date) > 2022-04-30 00:00:00 -1100 logger.debug(Date.current) > 2022-04-29 ``` Tokyo (GMT+09:00) → 4/30 に 4/30 を選んでも "Date must be greater than today" が表示される。 ``` logger.debug(date) > 2022-04-30 00:00:00 +0900 logger.debug(Date.current) > 2022-04-30 ```
punchan36

2022/05/01 11:45

ご回答欄のコメントは通知を受け取る方法が無いようですので、こちらのコメント欄にて失礼致します。 date < DateTime.current でも date < DateTime.now でも、"Date must be greater than today" が表示されてしまいました(date < Time は ArgumentError)。 Tokyo (GMT+09:00) をタイムゾーンに持つユーザーがイベント開始時間を 5月1日23:00 (PM) に選択した際のログはこのようになります。 logger.debug(date) > 2022-05-01 logger.debug(Date.current) > 2022-05-01 logger.debug(DateTime.current) > 2022-05-01T20:40:23+09:00 logger.debug(DateTime.now) > 2022-05-01T20:40:23+09:00 logger.debug(time) > 2000-01-01 23:00:00 +0900 logger.debug(Time.current) > 2022-05-01 20:40:23 +0900 logger.debug(Time.now) > 2022-05-01 20:40:23 +0900 logger.debug(DateTime.current) > 2022-05-01T20:08:51+09:00 (Time.nowDateTime.now はタイムゾーンによらず日本時間が表示される。日本時間ではなく現地時間と比較したいので却下。) DateTime.current も DateTime.now も、 date の表示方式に合っていないようです。 ですが見てみると 「date と Date.current」、「time と Time.current」であれば比較出来そうに思えたので、条件文を以下のように変えてみました。 errors.add(:date, 'must be greater than today') if date.present? && time.present? || date < Date.current && time < Time.current しかし変わらず "Date must be greater than today" は表示されてしまいます。 time はデフォルトで 2000年が表示されるようですので、Time.current と比べた時に、日時以前に 2000 < 2022 となってしまうのですね。。 何か良い打開策はありませんでしょうか…?
punchan36

2022/05/01 13:13

上のログは date を date 型のままで試した物でございました。 datetime 型に再度直すと以下のようになります(日時を決める time カラム は time 型にしております)。 いずれにせよ、"Date must be greater than today" は表示されてしまいます。 logger.debug(date) > 2022-05-01 00:00:00 +0900 logger.debug(Date.current) > 2022-05-01 logger.debug(DateTime.current) > 2022-05-01T22:06:35+09:00 logger.debug(DateTime.now) > 2022-05-01T22:06:35+09:00 logger.debug(time) > 2000-01-01 23:00:00 +0900 logger.debug(Time.current) > 2022-05-01 22:06:35 +0900 logger.debug(Time.now) > 2022-05-01 22:06:35 +0900
winterboum

2022/05/01 13:37

post.date は日付だけでよいのですか? でしたら 「解決済」の状態( date の型を dateにする)でOKです。よね? 「日本でのイベント開始日時が5月1日6:00 (AM) の時」とあったので、時刻もしていするのかな、と思ったのですが、上の例では 00:00になってます。すなわち日付しか指定していない。 でしたら 2022-05-01 00:00:00 +0900 < 2022-05-01T22:06:35+09:00 ですから 当然です。 時刻も指定するのですか? 指定しないのですか? 指定したとき、日付だけで比較? 時刻も含めて比較?
punchan36

2022/05/01 14:31

申し訳ありません。 整理致しますと、イベント開始時間は「時刻」も指定致します。また日時も時刻も含めて「開始日時は現在以降を指定してください」という意味合いで "Time must be greater than now" とエラーメッセージを表示したいです(エラー文も改善しました)。 ですので型は現在以下のようになっております。 create_table "posts", options: "hoge", force: :cascade do |t| t.string "title" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "user_id" t.datetime "date" t.time "time" end ご指摘を受けて初めて気付きましたが、 先ほどお送りいたしましたログに「logger.debug(date) > 2022-05-01 00:00:00 +0900」とありましたが、「logger.debug(time) > 2000-01-01 23:00:00 +0900」にもありますように「5月1日23:00(PM)」を指定しておりました。以下の条件式で先ほどのログが出されましたが、 date は time で指定した時刻までは(当然ですが)拾ってくれないのですね。 errors.add(:time, 'must be greater than now') if date.present? && time.present? || date < Date.current && time < Time.current そこで今回こちらを参考に、今度はフォーマットを揃えた上での比較を試みました。 https://qiita.com/pli8xxx_yu/items/bb0010ad8725b2393fb6 条件式はこちらです。 errors.add(:time, 'must be greater than now') if date.present? && time.present? || date.strftime( "%Y-%m-%d" ) < Date.current && time.strftime( "%H:%M" ) < Time.current.strftime( "%H:%M" ) イベント開始時刻に「5月1日23:59(PM)」を指定、「5月1日23:00(PM)」に保存を試みた際のログがこちらです。 logger.debug(date.strftime( "%Y-%m-%d" )) > 2022-05-01 logger.debug(Date.current) > 2022-05-01 logger.debug(DateTime.current) > 2022-05-01T23:00:06+09:00 logger.debug(DateTime.now) > 2022-05-01T23:00:06+09:00 logger.debug(time.strftime( "%H:%M" )) > 23:59 logger.debug(Time.current.strftime( "%H:%M" )) > 23:00 logger.debug(Time.now) > 2022-05-01T23:00:06+09:00 しかしそれでも "Time must be greater than now" が表示されてしまいます…。フォーマットは揃えたつもりですが、どこがまずいでしょうか…?
winterboum

2022/05/01 21:15

date と time を分けずに、date に日時を入れるのが順当なのですが、なぜそうしないのですか?
winterboum

2022/05/01 22:39

にしても命名が悪いですね。 私でしたら open_at にします。 で ここで日時をまとめて設定すれば「日付変更線を跨いだ時に日付までは変わってくれないのです」も解決です。
punchan36

2022/05/03 01:20

有難うございます!無事希望通りの実装が出来ました。 「date と time を分けずに、date に日時を入れる」事が出来るとは知らず、それぞれを独立させ無意味に複雑にしてしまっておりましたね…。 動いたコードもシェアさせて頂きます。 schema.rb ``` create_table "posts", options: "省略", force: :cascade do |t| # 他は省略 t.datetime "created_at", null: false t.datetime "updated_at", null: false t.datetime "open_at" t.datetime "close_at" end ``` post.rb ``` class Post < ApplicationRecord validate :open_at_presence validate :close_at_presence validate :open_at_time validate :close_at_time validate :open_close_at_time def open_at_presence if open_at.blank? return errors.add(:base, "Opening time can't be blank") end end def close_at_presence if close_at.blank? return errors.add(:base, "Closing time can't be blank") end end def open_at_time if open_at.present? && open_at < Time.current return errors.add(:base, "Opening time must be greater than now") end end def close_at_time if close_at.present? && close_at < Time.current return errors.add(:base, "Closing time must be greater than now") end end def open_close_at_time if open_at.present? && close_at.present? && close_at < open_at return errors.add(:base, "Closing time must be greater than the opening time") end end end ``` posts/new.html.erb ``` <%= form_with url: "/posts/create", local: true do |form| %> <%= form.datetime_field :open_at, value: @post.open_at&.strftime("%Y-%m-%dT%H:%M") %> <%= form.datetime_field :close_at, value: @post.close_at&.strftime("%Y-%m-%dT%H:%M") %> <% end %> ``` posts_controller.rb ``` def create @post = Post.new( # 他は省略 open_at: params[:open_at], close_at: params[:close_at] ) if @post.save redirect_to("/posts/#{@post.id}") else render("posts/new") end end ```
punchan36

2022/05/03 01:21

また長くなってしまい大変申し訳ないのですが、「タイムゾーンを Rails 全体の設定ではなく User 毎の設定にする」為にはどのような記述が望ましいかだけ、最後にご助言頂けませんでしょうか? application_controller.rb ``` private def user_time_zone(&block) Time.use_zone(@current_user.time_zone, &block) end ```
winterboum

2022/05/03 02:46

なにが望ましいか、はわかりません。 グローバルに変更しない という前提で行うなら、表示、入力、比較 のところで time zone を意識した書き方にする。 なにも timezone自体を変更する必要もないのでは、と。 比較は UTC に変換して行えば良いから簡単ですね。もしかしたら 比較自身がtimezoneを意識してくれてるかも。 表示は https://docs.ruby-lang.org/ja/2.7.0/class/Time.html#I_LOCALTIME で良いでしょう。 入力は https://docs.ruby-lang.org/ja/2.7.0/class/Time.html#S_NEW で良いでしょう
punchan36

2022/05/03 03:48

有難うございます。これは土台から変えるお話ですね…。 今回は苦労してようやく実装出来ましたので、一旦この機能で様子を見ようと思います。 余力が出てきましたら根本から変更するのも良いかなと思いました。 今回は付き添って沢山ご助言を頂きまして、本当に有難うございました!
guest

回答1

0

ベストアンサー

schema がないので「おそらく」ですが、date は datetime もしくは time で作られていると思います。
それを date に change_column すれば治るでしょう。
errors.add(:date, 'must be greater than today') if date.present? && date < Date.current なので
2022-04-30 00:00:00 +0900 < 2022-04-30 を比較しています。
型が異なるので date が datetime にcast されて、2022-04-30 00:00:00 になります。このとき +00:00 になっているのだと思います。
この部分でtimezoneが考慮されていないのでしょう。
ですから + のtimezoneだとどこでも発生していた。

投稿2022/04/30 09:06

winterboum

総合スコア23329

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

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

punchan36

2022/04/30 14:20

有難うございます! schema.rb は「試したこと」の一つ上に載せておりましたが、仰る通り datetime 型を使っておりました。 これを date 型に変更する事で問題だったエラーメッセージは表示されなくなりました!(GMTプラス、GMTマイナスのタイムゾーン共に関係なく。) 今回の問題は解決したのですが、date 型にする事で一点問題が出てきます。 time_zone 自体は機能しており、各ユーザーに表示される時間は上手く変わってくれます(日本でのイベント開始日時が5月1日6:00 (AM) の時、2時間遅いタイにいるユーザーには5月1日4:00 (AM) と表示される。) しかし一点問題があり、日付変更線を跨いだ時に日付までは変わってくれないのです(日本でのイベント開始日時が5月1日6:00 (AM) の時、9時間遅いロンドンにいるユーザーには4月30日 21:00 (PM) ではなく、5月1日の 21:00 (PM) と表示されてしまう。) これは別で質問を立てた方が良いでしょうか…? また application_controller.rb で記述しておりました以下のタイムゾーンの設定は、やはりおかしい(User毎の設定ではなくRails全体の設定になってる)のでしょうか? Tokyo をタイムゾーンにしているユーザーをアクティブにしながら、別のブラウザで Hawaii をタイムゾーンに持つユーザーでログインして同時にタイムゾーン変更などの作業もしてみましたが、特に不都合は見られませんでした…。 ``` def user_time_zone(&block) Time.use_zone(@current_user.time_zone, &block) end ```
winterboum

2022/04/30 23:06

「日付変更線を跨いだ時に日付までは変わってくれない」は別立てにして下さい。 このスレッド長大になってしまったのと、その問題の詳しい説明が有ったほうがよいかと思うので。 「特に不都合は見られませんでした」 タイミングの問題なのです。 タイムゾーンの設定は リクエスト毎に行われています。 Aさんのリクエストの処理が始まってその結果が出るまでの間に、Bさんのリクエストが入った」 場合に発生します。 ただ、必ず起きるかどうかはちと。というのは サーバーでは複数にforkしたりスレッド分けたりしてうごいています。Aさんの処理とBさんの処理が同時に行われるとすると 別スレッドで、です。Rails全体のその設定が、スレッドをまたがって伝搬するのかどうか、までは知識がありません。 及び、大人気のサイトにならないと起きないですね。 グローバルなものを変更する というのに注意を払わねば、というところから書いていました
punchan36

2022/05/01 03:58

なるほどです…アクセス数も多くないのであれば緊急を要する問題では無さそうですが、理想の形と言うのは気になります。 winterboum さまであればどのような記述にされますでしょうか? 書き方は間違っていますが、意図としてはこのようなイメージでしょうか。。 「ログインしたユーザーの日時情報は、ログインしたユーザー(本人)のタイムゾーンを使用する」 @current_user.Time.use_zone(@current_user.time_zone, &block) ``` def user_time_zone(&block) Time.use_zone(@current_user.time_zone, &block) end ```
winterboum

2022/05/01 05:49

ん? date って Dateにしてよいのかな。 開催日 ですか 開催日時 ですか? 「 4月30日6:00(AM) にイベントを開始する」だと 日時 ですね。 すると Date にしてはまずい。 原因は確定できたけど、解決ではないですね。
punchan36

2022/05/01 09:34

時間まで指定しますので、開催日時になります。 となると date カラムは datetime 型にしないと time と上手く連動しなくなってしまいますかね。。
punchan36

2022/05/01 09:43

こちらのコメント欄は通知が来なくなったのですかね…。ご返答が遅くなり申し訳ありません。
winterboum

2022/05/01 10:34

すると、比較するのを Date.current ではなく DateTime.current か DateTime.now にするのかな。もしくは Time。 どれがのぞみの結果になるか試してください。 あ、 日時まででの比較の場合、日付部までで済ませるのか、時刻まで見るのか、そこも吟味です。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問