この記事は、リンクバルアドベントカレンダー2022の24日目の記事です。
はじめに
改めまして技術部のダットです!
machicon JAPANという自社のメインサービスの開発をしています。
開発するとき、よくRailsのActiveSupport::Timezoneのparse
メソッドを使っています。データベースに保存している日付の価値はUTC(協定世界時)ですから、ユーザーに日付を表示させるとき、そのユーザーのタイムゾーンの日付に変換する必要があります。ですが、Timezoneのparse
を使用するとき、何を注意すべきか、この記事を通じて説明します。
Timezoneのparse、正規表現は何か?
Timezoneのparse
を使用すると、指定したタイムゾーンに日付を変換できます。
# 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
だと呼ばれています。
正規表現を利用すると、文字列が指定したパターンを含んでいるかどうかを判定できます。その故に、日付のフォマットをバリデーションできるようになります。
# 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メソッドを使って簡単に日付を渡せますが、利用するとき日付のフォマットを注意すべきです。
日付を限らず、値を渡す前にインプットのフォマットをちゃんと確認しないと意外な不具合が発生する場合もあります。これを心掛けて欲しいです。
ここまで読んでいただ、きありがとうございました。
この記事は皆さんに役立つ知識であれば幸いです。