3
0

More than 1 year has passed since last update.

RailsのTimezoneのparseメソッドを使用する前に正規表現でチェックすべき

Posted at

この記事は、リンクバルアドベントカレンダー2022の24日目の記事です。

はじめに

改めまして技術部のダットです!:tangerine:
machicon JAPANという自社のメインサービスの開発をしています。
開発するとき、よくRailsのActiveSupport::Timezoneのparseメソッドを使っています。データベースに保存している日付の価値はUTC(協定世界時)ですから、ユーザーに日付を表示させるとき、そのユーザーのタイムゾーンの日付に変換する必要があります。ですが、Timezoneのparseを使用するとき、何を注意すべきか、この記事を通じて説明します。:raised_hand::raised_hand:

Timezoneのparse、正規表現は何か?

Timezoneのparseを使用すると、指定したタイムゾーンに日付を変換できます。

config/application.rb
# RailsアプリケーションのデフォルトのタイムゾーンをJST+9とする
config.time_zone = 'Tokyo'
# Time型のカラムはDBもアプリもUTCで扱う
# Rails 5.1からはJST+9として扱うことも出来る
config.active_record.time_zone_aware_types = [:datetime]
pry(main)> Time.zone.parse('2022-12-24 12:00:00')
=> Sat, 24 Dec 2022 12:00:00 JST +09:00

記事のタイトルに正規表現でチェックすべきがありますが、正規表現はなんなんですか。
正規表現は文字列のパターンを記述するための言語です。この言語で記述されたパターンも正規表現と呼ばれます。英語ならRegularExpressionで普通に省略にするとRegexpだと呼ばれています。
正規表現を利用すると、文字列が指定したパターンを含んでいるかどうかを判定できます。その故に、日付のフォマットをバリデーションできるようになります。

app/models/user.rb
# YYYY-MM-DDのフォマットを設定する
BIRTHDATE_REGEXP = /^\d{4}-\d{2}-\d{2}$/

正規表現の具体的はこの記事に書いてありませんが、もし深く把握したい方がいればこのURLが参考になるかもしれません。

どうして日付を渡す前にバリデーションが必要か?

Time.zone.parseに正しくない日付を渡した時には、nilになったり例外が起きたりします。データベースから直接に日付の値をパースしたら全てRails側に任せてもいいです。なぜかと言うと、インプットの価値を管理できますから。

pry(main)> Time.zone.parse('hoge')
=> nil

pry(main)> Time.zone.parse('2022/99/99')
ArgumentError: argument out of range

しかし、管理画面でcsvファイルのアップロード機能を使えば、第三者から値を挿入する場合もあるではないでしょうか。その時、渡す前に日時のフォマットをチェックしないと意外の不具合が発生する可能性があります。

pry(main)> Time.zone.parse('monooxide')
=> Sat, 24 Dec 2022 00:00:00 JST +09:00 # 適当な文字列を渡したが、現在の日を返せる場合もある

pry(main)> Time.zone.parse('2022-010-08') # 2022年10月8日を渡したつもりが、10月の文字に余裕な0を入れてしまった
=> Wed, 10 Aug 2022 00:00:00 JST +09:00 # 例外が起きないで2022年8月10日の間違った日時を渡してしまった

どうやって日時のフォマットをバリデーションするか?

上記の意外な不具合を防ぐために、日時をパースする前にフォマットをバリデーションする必要があります。
日付のフォーマットを正規表現でチェックして日付の値をRails側に任せても良いです。なぜなら、正規表現で日付の値までチェックしたらロジックはすごく複雑になってしまいます(閏年かどうかチェックなど)。
正規表現を作成するとき、Regexp.unionを使って複数なパータンを組み合わせられます。例を挙げます。

  • 正規表現を作成します
pry(main)> hyphen_regexp = /^\d{4}-\d{2}-\d{2}$/
=> /^\d{4}-\d{2}-\d{2}$/ # YYYY-MM-DDのフォマットを許可するパータン

pry(main)> slash_regexp = /^\d{4}\/\d{2}\/\d{2}$/ 
=> /^\d{4}\/\d{2}\/\d{2}$/ # YYYY/MM/DDのフォマットを許可するパータン

pry(main)> BIRTHDATE_REGEXP = Regexp.union(hyphen_regexp, slash_regexp) 
=> /(?-mix:^\d{4}-\d{2}-\d{2}$)|(?-mix:^\d{4}\/\d{2}\/\d{2}$)/ # YYYY-MM-DDかYYYY/MM/DDのフォマットを許可するパータン
  • 作成された正規表現でmatch?メソッドを使って日付をバリデーションします
pry(main)> '2022-12-24'.match?(BIRTHDATE_REGEXP)
=> true

pry(main)> '2022/12/24'.match?(BIRTHDATE_REGEXP)
=> true

pry(main)> 'monooxide'.match?(BIRTHDATE_REGEXP)
=> false

pry(main)> '2022-010-08'.match?(BIRTHDATE_REGEXP)
=> false

おわりに

parseメソッドを使って簡単に日付を渡せますが、利用するとき日付のフォマットを注意すべきです。
日付を限らず、値を渡す前にインプットのフォマットをちゃんと確認しないと意外な不具合が発生する場合もあります。これを心掛けて欲しいです。
ここまで読んでいただ、きありがとうございました。
この記事は皆さんに役立つ知識であれば幸いです。:pray:

参考

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0