はじめに/エラー発生時の状況
作成したカレンダーアプリ的なものを手直し中、
time_select
で送った時刻データの保存時に、ズレが生じていることに気付きました。
<div class="start-date-input">
<div class="form-title">
開始時間
<span class="indispensable">必須</span>
</div>
<div class='input-date-wrap'>
<%= f.time_select(:start_time, {class:'select-date', id:"start-time", prompt:'--', time_separator: ':'}, ignore_data: true) %>
</div>
</div>
<div class="start-date-input">
<div class="form-title">
終了時間
<span class="indispensable">必須</span>
</div>
<div class='input-date-wrap'>
<%= f.time_select(:start_time, {class:'select-date', id:"start-time", prompt:'--', time_separator: ':'}, ignore_data: true) %>
</div>
</div>
上記の様に日付と時刻をそれぞれフォームに入力し、データを送信すると…
ja:
time:
formats:
default: "%Y/%m/%d %H:%M:%S"
short: "%H:%M"
<div class="event-info-left">
<p><%= event.day %></p>
<p><strong><%= l event.start_time, format: :short %></strong></p>
<p><strong>↓</strong></p>
<p><strong><%= l event.finish_time, format: :short %></strong></p>
</div>
!?!?!!!??!?!?
開始時間08:00・終了時間09:00で先程入力したはずが、19分ズレています。
何が起きたのでしょうか…
原因の考察・検証
ちなみに、このズレた時刻を編集フォームで修正すると…
直ります。どうやら新規作成時に何か問題がある様です。
まず、新規作成フォームから送られるパラメーターと、作成されるインスタンスを確認します。
[1] pry(#<EventsController>)> event_params
=> <ActionController::Parameters {
(中略)
"day(1i)"=>"2021", "day(2i)"=>"1", "day(3i)"=>"1",
"start_time(1i)"=>"1", "start_time(2i)"=>"1", "start_time(3i)"=>"1", "start_time(4i)"=>"08", "start_time(5i)"=>"00",
"finish_time(1i)"=>"1", "finish_time(2i)"=>"1", "finish_time(3i)"=>"1", "finish_time(4i)"=>"09", "finish_time(5i)"=>"00",
} permitted: true>
[2] pry(#<EventsController>)> @event = Event.new(event_params)
(中略)
day: Fri, 01 Jan 2021,
start_time: Mon, 01 Jan 0001 08:00:00 LMT +09:18,
finish_time: Mon, 01 Jan 0001 09:00:00 LMT +09:18,
時刻に**「LMT」がついています。
パラメーターで適用されている「LMT」を調べたところ、「地方時」**と呼ばれるものでした。
LMTとは
※日本史や天文学的な話題ですので、下記リンクを参照し、ここでは簡単な説明とさせていただきます。
18分59秒をめぐって日本標準時の歴史をひもとくことに
暦Wiki/日本の本初子午線
暦Wiki/地方時
LMT(Local Mean Time) …地方時
東西に広い国では、標準時を使用するよりも都市毎に基準となる時刻が設定されている方が便利な場合があります。
経度の差に応じて、標準時から補正した時刻を地方時といいます。
現在の日本の標準時は兵庫県明石の東経135度を基準とし、時差は本初子午線のイギリス:グリニッジから+09:00で制定されているのは、皆様よくご存知かと思います。
こちらは1884年の国際子午線会議で制定され、1888年1月1日から使用され始めました。
それまでは各都市で地方時が定められていたため、日本の基準としてはお江戸東京の地方時を使用していました。
この東京の地方時が、グリニッジから約+09:19前後の時差であったとされています。
何故LMT時刻になっているのか
現状のタイムゾーン設定
ちなみに、タイムゾーンの設定は下記の通り、アプリケーション本体もデータベースもJSTになる様に記述しています。
module TestApp
class Application < Rails::Application
config.load_defaults 6.0
config.time_zone = 'Asia/Tokyo'
config.i18n.default_locale = :ja
config.active_record.default_timezone = :local
(中略)
end
end
JSTでタイムゾーン設定をしていても、LMTが出てくるのは何故か考えてみます。
time型データも年月日を持つ
time型のデータもActive Recordの仕様としては一旦、年月日の値を持ちます。
※以前の記事でも軽く触れています
(Time型データから時刻のみを取得したい…!【strftime】※追記あり)
[1] pry(#<EventsController>)> event_params
=> <ActionController::Parameters {
(中略)
"day(1i)"=>"2021", "day(2i)"=>"1", "day(3i)"=>"1",
"start_time(1i)"=>"1", "start_time(2i)"=>"1", "start_time(3i)"=>"1", "start_time(4i)"=>"08", "start_time(5i)"=>"00",
"finish_time(1i)"=>"1", "finish_time(2i)"=>"1", "finish_time(3i)"=>"1", "finish_time(4i)"=>"09", "finish_time(5i)"=>"00",
} permitted: true>
新規作成フォームから送られるパラメーターでも、
(1i)…年
(2i)…月
(3i)…日
(4i)…時
(5i)…分
というように、各時間と共に年月日も付与されています。
新規作成されたインスタンスで、time型の時刻が仮に持っている年月日を確認します。(分かりやすいようにstrftimeメソッド
を使用)
[2] pry(#<EventsController>)> @event.start_time
=> Mon, 01 Jan 0001 08:00:00 LMT +09:18
[3] pry(#<EventsController>)> @event.start_time.strftime("%Y/%m/%d")
=> "0001/01/01"
今回、フォームで入力した時刻はそれぞれ、**「西暦0001年01月01日」**の時刻と扱われています。こちらの年月日ですと1888年以前のため、LMT時間が適用されてしまったと考えられます。
どのタイミングで時刻がズレるのか
試しにコンソールでstrftimeメソッドを使用すると
[3] pry(#<EventsController>)> @event.start_time
=> Mon, 01 Jan 0001 08:00:00 LMT +09:18
[4] pry(#<EventsController>)> @event.start_time.strftime("%H:%M")
=> "08:00"
[5] pry(#<EventsController>)>
ここでは問題なく時刻が表示されている様に見られます。
しかし、こちらを保存してDBを見てみると…
問題のズレ時刻で保存されているのが確認出来ます。
ここから、
-
パラメーターを元に作成したインスタンス
→LMTで08:00 -
データベースに保存する時点
→LMTで08:00を、データベースの設定タイムゾーン(JST)に計算し直して保存
と言う動きをしていると考えられます。
この、インスタンス→データ保存の段階での変換が今回のエラーを引き起こしていると分かります。ズレた時間でデータベースに保存されているので、ビューにもズレた時間が表示されていました。
編集時は何故ズレないのか
編集時にはフォームの値どおりに修正出来たのも一緒に検証します。
編集フォームには、DBに保存されているデータを引っ張ってこれる様にしています。
引っ張ってきた時点のデータを確認します。
[1] pry(#<EventsController>)>
(中略)
=> #<Event:0x00007fc64dfd9038
(中略)
day: Fri, 01 Jan 2021,
start_time: Sat, 01 Jan 2000 07:41:01 JST +09:00,
finish_time: Sat, 01 Jan 2000 08:41:01 JST +09:00,
(中略)
>
新規作成でDBに保存された時点で、LMTを変換した「JST」になっています。
この時の年月日は、西暦2000年01月01日になっています。(恐らくJSTの場合のデフォルト値)
この時点で1888年以降の年月日になので、改めてDBに保存される際もJSTで扱ってもらえると考えられます。編集時にはズレが生じなかったと言う訳です。
対策/結果
原因は**「新規作成時の年月日」**であることがわかりました。
この古からのLMTを修正するには、モデルにロジックを追加し、時刻にそれぞれLMT以降の年月日を与えてからデータベースに送れるようにしたいと思います。
set_time_zone
としてメソッドを定義し、
時刻の年月日にも、年月日フォームで入力した値(dayカラム)を適用します。
(スケジュールアプリなので、1888年より以前の年月日は扱わない想定です)
そしてこちらのメソッドを、berofe_save
でデータベース保存前に呼び出します。
class Event < ApplicationRecord
before_save :set_time_zone
(中略)
# new時のタイムゾーン対策(LMTになってしまい時間がズレるのを避ける)
def set_time_zone
year = self.day.year
month = self.day.month
day = self.day.day
self.start_time = self.start_time.change(year: year, month: month, day: day)
self.finish_time = self.finish_time.change(year: year, month: month, day: day)
end
end
修正後、改めて新規作成してみました。
時刻のズレが解消し、無事に保存されました!!
終わりに/感想
プログラミングでまさか天文学や歴史について調べるとは思いませんでした…
まずは年内に解決してほっとしています
直接同様な事例が検索ではなかなか見つからず…
何が原因か、どこで引き起こったエラーなのかを論理的に考えてどうにか辿り着きました。(数日かかりました…)
系統立てて考えることの重要性を、改めて実感したエラーでした。
また、タイムゾーンの扱い方、フォームでどんなデータがパラメーターで送られるのか、今一度しっかり学びたいと思います。
初学者で拙い記事ですが、少しでもお役に立てると嬉しく思います。
最後まで読んでいただき、誠にありがとうございました。
開発環境
Ruby 2.6.5
Rails 6.0.3.4
MySQL
Visual Studio Code
(GoogleChrome)
参考記事
【Qiita】Railsのtime_selectでデフォルトで入力される日付を、画面上にある別のフィールドの入力値にする
【Qiita】time_selectでも日付の設定できるっぽい
【TECHSCORE】Railsのコールバックまとめ
【公式】Railsドキュメント/time_select