#ruby #parsing #datetime
#ruby #синтаксический анализ #дата- время
Вопрос:
У меня есть два параметра даты в действии контроллера, которые я хотел бы вернуть к значению по умолчанию, если они равны нулю или синтаксический анализ завершается неудачей.
К сожалению, кажется, что DateTime.strptime
при сбое синтаксического анализа возникает исключение, что вынуждает меня написать это чудовище:
starting = if params[:starting].present?
begin
DateTime.strptime(params[:starting], "%Y-%m-%d")
rescue
@meeting_range.first
end
else
@meeting_range.first
end
Чувствует себя плохим человеком. Есть ли какой-либо способ проанализировать дату с помощью Ruby stdlib, для которого не требуется begin...rescue
блок? Хронический кажется излишним в этой ситуации.
Комментарии:
1. Вы можете избавиться от обусловленности,
present?
если вы сохраняете исключение.2. Имеет ли отношение к вопросу то, что у вас есть два параметра даты вместо, скажем, одного? Если нет, удаление ненужной информации из вопроса поможет читателю.
3. @sawa цель вопроса — полностью устранить
begin...rescue
блок. И ваш другой момент — это просто придирки.4. Идея
strptime
заключается в том, что вы уже знаете, что синтаксический анализ будет успешным, потому что вы уже определили этот формат даты по мере выполнения кода, прежде чемstrptime
его увидите.Date#parse
,DateTime#parse
илиTime#parse
подходят для определения правильного формата, когда вы не уверены, что получаете, хотя они натыкаются на даты в форматах%m/%d/%Y
и%d/%m/%Y
.5. @железный дровосек, чего я хочу, так это функции синтаксического анализа, которая возвращает ноль при сбое вместо выдачи исключения.
Date#parse
,DateTime#parse
иTime#parse
все выбрасываютArgumentError
. Я не могу вспомнить ни одной ситуации, когда я хотел бы, чтобы параметр date вел себя таким образом в контроллере.
Ответ №1:
В общем, я не могу согласиться с другим решением, использовать rescue
таким образом — плохая практика. Я думаю, это стоит упомянуть на случай, если кто-то другой попытается применить концепцию к другой реализации.
Меня беспокоит то, что какое-то другое исключение, которое вас может заинтересовать, будет скрыто этим rescue
, нарушая правило раннего обнаружения ошибок.
Следующее для Date
не DateTime
, но вы поймете идею:
Date.parse(home.build_time) # where build_time does not exist or home is nil
Date.parse(calculated_time) # with any exception in calculated_time
Столкнувшись с той же проблемой, я закончил тем, что исправил Ruby следующим образом:
# date.rb
class Date
def self.safe_parse(value, default = nil)
Date.parse(value.to_s)
rescue ArgumentError
default
end
end
Любое исключение в значении будет увеличено перед входом в метод, и будет перехвачено только ArgumentError
(хотя я не знаю ни о каких других возможных).
Единственное правильное использование inline rescue
— это нечто подобное этому:
f(x) rescue handle($!)
Обновить
В эти дни я предпочитаю не исправлять Ruby с помощью monkey. Вместо этого я оборачиваю свой Date
в Rich
модуль, который я вставляю lib/rich
, затем вызываю его с помощью:
Rich::Date.safe_parse(date)
Комментарии:
1. Действительно, каждый раз, когда я использовал встроенное спасение, я сожалел об этом. Я уже давно перенес все свои потребности в анализе даты / времени на использование вместо этого своевременности , которая ведет себя так, как я хочу.
Ответ №2:
Почему бы просто не:
starting = DateTime.strptime(params[:starting], '%Y-%m-%d') rescue @meeting_range.first
Комментарии:
1. Это выглядит как приемлемый компромисс, если я должен смириться с использованием
rescue
. Тем не менее, все еще похоже на антипаттерн.2. Совершенно очевидно, что сбой при анализе значения вызывает исключение. Аналогично, если вы ожидаете значение, то отсутствие значения также является исключительным состоянием. Ruby этого не делает, но на многих языках даже функция, которая ищет в массиве определенное значение, вызывает исключение, если значения нет в массиве, и обращение к элементу хэша с помощью неправильного ключа также приводит к этому (что в Ruby возвращает
nil
). Исключения следует рассматривать не как ошибки, а как неожиданное состояние, отличное от того, что было бы нормальным в данной ситуации. Если вы принимаете это представление, а не антипаттерн.3. Я считаю антишаблоном несоответствие поведения, а не само исключение. Вы правы в том, что создание исключения в некоторых случаях совершенно разумно. Я просто хотел бы, чтобы здесь была альтернатива, поскольку сбой синтаксического анализа не является неожиданностью в этом контексте.
4. Код значительно очищается с помощью завершающего элемента
rescue
, но также может открыть новую банку с червями, если что-то помимо синтаксического анализа не удается слева отrescue
. Этот случай выглядит довольно безобидным, но с ним следует быть осторожным. Отладка такого рода проблем раздражает / расстраивает / сложна: выберите любые две.5. @TinMan: Это правда. Вы должны быть уверены, что ваша одна строка вызовет только исключение одного типа (или что вы хотите одинаковую обработку для любого исключения). Но в этом случае единственная неисправная часть — это
strptime
, и в исходном коде также было неограниченноеrescue
, поэтому я полагаю, что здесь у нас все должно получиться.
Ответ №3:
В наши дни мой предпочтительный подход заключается в использовании Dry::Types
для приведения к типу и Dry::Monads
для представления ошибок.
require "dry/types"
require "dry/monads"
Dry::Types.load_extensions(:monads)
Types = Dry::Types(default: :strict)
Types::Date.try("2021-07-27T12:23:19-05:00")
# => Success(Tue, 27 Jul 2021)
Types::Date.try("foo")
# => Failure(ConstraintError: "foo" violates constraints (type?(Date, "foo"))
Ответ №4:
Все существующие ответы где-то есть rescue
. Однако мы можем использовать некоторые «уродливые» методы, которые были доступны с версии Ruby 1.9.3 (они были там раньше, но официального описания нет).
Метод уродлив, потому что он начинается с подчеркивания. Тем не менее, это соответствует цели.
С помощью этого можно записать вызов метода в вопросе
starting = if params[:starting].present?
parsed = DateTime._strptime(params[:starting], "%Y-%m-%d") || {}
if parsed.count==3 amp;amp; Date.valid_date?(parsed[:year], parsed[:month], parsed[:mday])
@meeting_range.first
end
else
@meeting_range.first
end
- Если строка даты соответствует входному формату,
_strptime
вернет хэш со всеми 3 частями даты. этоparsed.count==3
означает, что все 3 части существуют. - Однако дальнейшая проверка того, что три части образуют действительную дату в календаре, по-прежнему необходима, поскольку
_strptime
вам не скажут, что они недействительны.
Ответ №5:
Когда вы хотите получить дату как объект, проанализированный из строковой переменной, иногда передаваемое строковое значение может быть нулевым, или пустым, или недопустимой строкой даты. Я бы хотел для краткости написать безопасные методы:
def safe_date(string_date)
::Date.parse(string_date)
rescue TypeError, ::Date::Error
::Date.today
end
Например — проверка в консоли irb:
3.0.2 :001 > safe_date
=> #<Date: 2022-08-29 ((2459821j,0s,0n), 0s,2299161j)>
3.0.2 :001 > safe_date('')
=> #<Date: 2022-08-29 ((2459821j,0s,0n), 0s,2299161j)>
3.0.2 :002 > safe_date('29.12.2022')
=> #<Date: 2022-12-29 ((2459943j,0s,0n), 0s,2299161j)>
3.0.2 :003 > safe_date('29.13.2022')
=> #<Date: 2022-08-29 ((2459821j,0s,0n), 0s,2299161j)>
Комментарии:
1. Молчаливое игнорирование подобных сбоев неизбежно вызовет проблемы. Я рекомендую использовать типы результатов, подобные моему принятому ответу.