SI部のr_maedaです。2025年の技術者ブログはこれが初投稿のようです。
本年もタイムインターメディアの技術者ブログをよろしくお願いいたします。
とある Ruby on Rails プロジェクトで「日付形式の文字列を受け取って処理する」クラスを作成して運用していたところ、そのクラスを対象にした自動テストが稀に失敗する事象を観測しました。
該当するプロダクションコード/テストコードは以下に示すようなものであり、「日付文字列」のパースには Date.parse
を利用していました。
# プロダクションコード
class Klass
def initialize(date_str)
@date = Date.parse(date_str)
rescue Date::Error
raise ArgumentError
end
...
end
# テストコード
RSpec.describe 'Klass.new' do
subject { Klass.new(date_str) }
context '与えられた文字列が日付として解釈できない場合' do
let(:date_str) { Faker::AlphaNumeric.alpha }
it { expect { subject }.to raise ArgumentError }
end
context '与えられた文字列が日付として解釈できる場合' do
let(:date_str) { Faker::Date.between(...).iso8601 }
...
end
end
最初は「ランダムなアルファベット文字列が『日付』として解釈できるパターンなんてある?」と、テストが失敗する理由が分からなかったのですが、調べたり検証しているうちに、1つのパターンが見えてきました。
「もしかして、Date.parse
って英語表現の日付も解釈できたりするのかな…?試しに『1月: January』を渡してみるとどうなるんだろう」
$ irb
irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date.parse('January')
=> #<Date: 2025-01-01 ((2460677j,0s,0n),+0s,2299161j)>
「とはいえ、ランダム文字列が『January』になるって、相当稀だよな。もしかして、3文字の短縮表現でも大丈夫なのか?」
「先頭が小文字でも大丈夫?」
「特定の3文字から始まる場合、その後ろにどんな文字列が続いていても、パースエラーは発生しないのか??」
irb(main):003:0> Date.parse('Jan')
=> #<Date: 2025-01-01 ((2460677j,0s,0n),+0s,2299161j)>
irb(main):004:0> Date.parse('jan')
=> #<Date: 2025-01-01 ((2460677j,0s,0n),+0s,2299161j)>
irb(main):005:0> Date.parse('Janne Da Ark')
=> #<Date: 2025-01-01 ((2460677j,0s,0n),+0s,2299161j)>
「全部パースできるやん…」
Jan
~ Dec
)Sun
~ Sat
)Date.parse
は「これらの3文字から始まる文字列」が与えられた場合、「十二月」の場合は「今年のその月の初日の日付」を、「七曜」の場合は「直近のその曜日の日付」を返すようです。
また他にも、"1st"
"2nd"
"3rd"
といった「数字から始まる序数」にも反応するようです。この場合は「当月のn日」を返しました。
irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date.parse('1st')
=> #<Date: 2025-01-01 ((2460677j,0s,0n),+0s,2299161j)>
irb(main):003:0> Date.parse('first') # これには反応しない
(irb):3:in `parse': invalid date (Date::Error)
irb(main):004:0> Date.parse('1') # 流石に「年」なのか「月」なのか「日」なのか分からず、諦める模様
(irb):4:in `parse': invalid date (Date::Error)
irb(main):005:0> Date.parse('32nd') # 32日がある月もないので、反応する上限は 31st まで
(irb):5:in `parse': invalid date (Date::Error)
受け取る「日付文字列」のフォーマットが決まっている場合は Date.iso8601
や Date.strptime
などを使いましょう。Date.parse
は「どんなフォーマットの日付文字列が渡されてもいい感じに解釈してくれる」便利なメソッドですが、裏を返すと「想定外の挙動を示すことがある」メソッドであるとも言えます。
「想定外の挙動」は少ないに越したことはないので、思考停止で Date.parse
を採用するのはやめたほうが良さそうです。
どうしても Date.parse
を使う必要がある(e.g. ユーザーの画面入力値をできる限りエラーにしたくないなど)場合は、「異常な文字列が与えられた場合」のテストに「ランダムな文字列」を使うことを諦めるしかないのかなと思います。