datetime是个讨厌的东西
最近基本上就在纠结这个问题了。
第一个问题是,invalid date
第二个问题是,如何传参
第三个问题是,时区
一、
关于第一个问题,当在rails中使用date_select控件的时候,以下为例
true%>
true%>
将会得到参数
"end_at"=>{"my_date(1i)"=>"2010", "my_date(2i)"=>"7", "my_date(3i)"=>"29"}, "start_at"=>{"my_date(1i)"=>"2010", "my_date(2i)"=>"2", "my_date(3i)"=>"31"}
这里存在的问题是,因为产生的控件是一个简单控件,不带有script,所以,当你选择年月日的时候,后续选择不过因为闰年或者月份的不同而改变。也就是说,你可以选择一个2月31日的日期,而它忠实地将这个参数传回。
我曾经写过一个函数来处理这些参数,将它变成一个日期的变量,如下:
def self.hash_to_date(obj,prefix)
Date.new(obj["#{prefix}(1i)"].to_i,obj["#{prefix}(2i)"].to_i,obj["#{prefix}(3i)"].to_i)
end
而这所遇到的问题是,当它接收到一个2月31日时会直接出错。
之后我使用了一个名为DatePicker的插件,它的输出结果为形如‘2010-10-10’的字符串,而合法的字符串通过to_date函数也能直接变成时间。
但这个插件在某些浏览器下并不正常,因为,我也允许手动输入时间串。
这样就同样有了如上所述的invalid date的情况,除非使用更加严格的验证函数。
然而有一点是可以肯定的,当你所选的内容将被存入数据库的时候,rails能够很好的处理它。
我翻找了很久并没有找到它是如何处理的,所以做了一个折中的做法,新建一个model,专门处理时间。
起初,我设想,这个model有且只有一个字段,也就是时间,名为my_date。最简单的方法是使用attr_accessor。
然后,attr_accessor似乎无法处理datetime这种复杂的数据类型,是故一直报错:1 error(s) on assignment of multiparameter attributes
并且,因为没有相关表,必须tableless掉,于是也可能有一定影响,深入未考。
后来我想到了tableless时候的做法。
我可以首先tableless掉,然后添加一个column。
这段代码我在上次探索tableless的时候已经找到了,其内容如下:
class ConvertDate < ActiveRecord::Base
def self.columns
@columns ||= [];
@columns << ActiveRecord::ConnectionAdapters::Column.new('my_date', nil,
'datetime', true)
end
end
使用的方式为:view依旧如之前所示,在controller中处理如下:
@start_date = ConvertDate.new(params[:start_at]).my_date
@end_date = ConvertDate.new(params[:end_at]).my_date
这样就解决的得到合法时间的问题。
二、
关于第二个问题,如果使用rails的date_select控件,那么,可以看到,参数多而杂,而如果使用之前提到的DatePicker则参数相对简单。
如果在一次post中,复杂的参数也许并不如何,但是,如果在get中,特别是在结合了排序和翻页之类的功能,参数列表就会显得长而繁杂。
所以我在考虑是否在两者间进行转化,通过class函数来判断:
如果是hash,直接new,如果是string,先进行转化。
而在传参之前,也可以尝试将hash转化为string,然后向后传。
不过这一点,我还没有深入来做。
三、
关于时区问题,因为在enviroment.rb中配置了:
config.time_zone = 'Beijing'
所以就会产生时区问题。
当建立一条记录的时候,当你包含了time_stamp,你就会发现,数据库中存储的是utc时间,当你使用user.created_at取出的时候,则会得到一个包含时区信息的时间类型,时间与你所期望的吻合。
然后来看一下其他几个函数
首先是to_date和to_datetime
日期部分的合法模式是2010.1.1或2010/1/1或2010-1-1
时间部分为0:0:0
还有一些简单的时间操作为 + 1.year/month/day/hour/minute/second
可以‘+’也可以‘-’,后面可以加s,也可以不加,不过要注意,数字和点和量词连写,并且前后空格,否则容易出错。
当我们使用to_date函数一下子无法看出时区信息,那么我们加上几个小时
那么就会发现to_date产生后运算后的时间是8时区,而,如果使用to_datetime则是0时区
同样对于Date.new的结果也是8时区的,而DateTime.new的结果是0时区的。
最后才是最纠结的问题,一个我一直没有发现的大问题。
例如,当数据库的utc时间为4,也么8时区时间是12,于是我们尝试用8时区的11点和13点来筛选数据,发现一切出乎了想象。
>> User.last
=> #
>> User.last.created_at
=> Thu, 29 Jul 2010 12:23:06 CST +08:00
>> b='2010.7.29'.to_date + 11.hour
=> Thu Jul 29 11:00:00 +0800 2010
>> c='2010.7.29'.to_date + 13.hour
=> Thu Jul 29 13:00:00 +0800 2010
>> User.last(:conditions=>['created_at > ?',b])
=> nil
>> User.last(:conditions=>['created_at #
>> User.last(:conditions=>['created_at > ?',c])
=> nil
>> User.last(:conditions=>['created_at #
我曾经认为是用12点多,和11点13点这三个时间比较,结果发现它才没有那么智能。
也就是说,大于11点,无,小于11点,有,大于13点,无,小于13点,有。
也就是说,其实它是用4点多和11点和13点在比较。
解决这个问题的方法,要么:
1、不设时区,在显示时转换
2、使用to_date和Date.new,并在选择的时候使用selected_time.utc来转换时区
另外,补充
Date只有today,没有now
DateTime只有now,没有today
两个的yesterday都是8时区。
于是,纠结暂完。







