【问题标题】:How to use Chronic to parse dates in a datetime text_field如何使用 Chronic 解析日期时间 text_field 中的日期
【发布时间】:2010-07-21 07:12:22
【问题描述】:

我正在尝试获取一个文本字段,我的用户可以在 Chronic gem 可解析的内容中输入该文本字段。这是我的模型文件:

require 'chronic'

class Event < ActiveRecord::Base
  belongs_to :user

  validates_presence_of :e_time
  before_validation :parse_date

  def parse_date
    self.e_time = Chronic.parse(self.e_time_before_type_cast) if self.e_time_before_type_cast
  end
end

我认为它被调用是因为如果我在 parse_date 中拼错了某些内容,它会抱怨它不存在。我也尝试过 before_save :parse_date,但这也不起作用。

我怎样才能让它工作?

谢谢

【问题讨论】:

    标签: ruby-on-rails parsing chronic


    【解决方案1】:

    这种情况看起来很适合在 Event 模型中使用 虚拟属性 来表示视图目的的自然语言日期和时间,同时将真实属性备份到数据库.通用技术在此screencast

    中进行了描述

    所以你的模型中可能有:

    class Event < ActiveRecord::Base
      validates_presence_of :e_time
    
      def chronic_e_time
        self.e_time // Or whatever way you want to represent this
      end
    
      def chronic_e_time=(s)
        self.e_time = Chronic.parse(s) if s
      end
    end
    

    在你看来:

    <% form_for @event do |f| %>
    
      <% f.text_field :chronic_e_time %>
    
    <% end %>
    

    如果解析失败,则e_time 将保持为nil,并且您的验证将停止保存记录。

    【讨论】:

    • 我必须做的一个更改:将 self.e_time = Chronic.parse(s) if s 更改为 self.e_time = Chronic.parse(s.to_date) if s 你给的那个不会解析真正的 UTC 日期时间,所以当我编辑表单时,它永远不会解析像 2010-07-22 12:00:00 -0700 这样的东西因为时区的-700 是正确的日期(它将返回nil)。
    【解决方案2】:

    在 @bjg 所做的基础上,这里有一个可行的解决方案,您可以将其放入 config/initializers/active_record_extend.rb 中

    module ActiveRecord
      class Base
        # Defines natural language getters/setters for date/time fields.
        #
        #   chronic_attr :published_at
        #
        # ...will get you c_published_at & c_published_at=
    
        def self.chronic_attr(*arguments)
          arguments.each do |arg|
    
            define_method "c_#{arg}=".to_sym do |dt|
              self[arg] = Chronic::parse(dt)
            end
    
            define_method "c_#{arg}".to_sym do 
              if self[arg]
                self[arg].to_s(:picker)
              else
                ''
              end
            end
          end
        end
      end
    end
    

    【讨论】:

      【解决方案3】:

      我知道猴子补丁现在已经过去了,但我认为这是集成 Ruby、Rails 和 Chronic 最直接的方法。我将this gist 放入我的初始化程序中:

      # https://gist.github.com/eric1234/3739149
      #
      # Mass monkey-patching! Provides integration between Chronic, Ruby and
      # Rails. So now these all work:
      #
      #     Date.parse "next summer"
      #     DateTime.parse "in 3 hours"
      #     Time.parse "3 months ago saturday at 5:00 pm"
      #
      # In addition we override String#to_date, String#to_datetime, String#to_time.
      # These methods are used by older version of ActiveRecord when parsing time.
      # For newer versions of ActiveRecord, Date::_parse is overridden to also
      # use Chronic. This means you can assign a simple string to a ActiveRecord
      # attribute:
      #
      #     my_obj.starts_at = "thursday last week"
      #
      # Also since the String method are redefined you can easily create dates
      # from strings. For example if you want tomorrow at 2pm you can just do:
      #
      #     'tomorrow at 2pm'.to_time
      #
      # This is more readable than the following IMHO:
      #
      #     1.day.from_now.change hour: 14
      
      module Chronic::Extensions
        module String
          def to_date
            parsed = Chronic::Extensions.safe_parse self
            return parsed.to_date if parsed
            super
          end
      
          def to_datetime
            parsed = Chronic::Extensions.safe_parse self
            return parsed.to_datetime if parsed
            super
          end
      
          def to_time
            parsed = Chronic::Extensions.safe_parse self
            return parsed.to_time if parsed
            super
          end
        end
        ::String.prepend String
      
        module DateTime
          def parse datetime, *args
            parsed = Chronic::Extensions.safe_parse datetime
            return parsed.to_datetime if parsed
            super
          end
        end
        ::DateTime.singleton_class.prepend DateTime
      
        module Date
          def _parse date, *args
            parsed = Chronic::Extensions.safe_parse(date).try :to_datetime
            if parsed
              %i(year mon mday hour min sec sec_fraction offset).inject({}) do |result, fld|
                value = case fld
                  when :offset then (parsed.offset * 86400).to_i
                  else parsed.public_send fld
                end
                result[fld] = value if value && value != 0
                result
              end
            else
              super
            end
          end
      
          def parse date, *args
            parsed = Chronic::Extensions.safe_parse date
            return parsed.to_date if parsed
            super
          end
        end
        ::Date.singleton_class.prepend Date
      
        module Time
          def parse time, now=self.now
            parsed = Chronic::Extensions.safe_parse time, now: now
            return parsed if parsed
            super
          end
      
          def zone
            super.tap do |cur|
              Chronic.time_class = cur
            end
          end
      
          def zone= timezone
            super.tap do
              Chronic.time_class = zone
            end
          end
        end
        ::Time.singleton_class.prepend Time
      
        def self.safe_parse value, options={}
          without_recursion { Chronic.parse value, options }
        end
      
        # There are cases where Chronic actually uses the Ruby date/time libraries.
        # This leads to infinate recursion as our monkey-patch will intercept the
        # built-in libraries to hand off to Chronic which in turn hands back to the
        # built-in libraries.
        #
        # To avoid this we have this function which acts as a guard to prevent the
        # recursion. If we have already proxied off to Chronic we won't proxy again.
        def self.without_recursion &blk
          unless in_recursion
            self.in_recursion = true
            ret = blk.call
            self.in_recursion = false
          end
          ret
        end
        mattr_accessor :in_recursion
      end
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-01-29
        • 2023-03-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-05-07
        • 1970-01-01
        相关资源
        最近更新 更多