关于ruby on rails:存储星期几和时间?

Store the day of the week and time?

关于在数据库中存储星期和时间的问题,我有两个部分的问题。 我正在使用Rails 4.0,Ruby 2.0.0和Postgres。

我有某些事件,这些事件都有一个时间表。 例如,对于"Skydiving"活动,我可能有周二,周三和下午3点。

  • 我有办法将星期二和星期三的记录存储在一行中,还是应该有两条记录?
  • 存储日期和时间的最佳方式是什么? 有没有办法存储星期几和时间(不是日期时间),还是应该是单独的列? 如果它们应该分开,我将如何存储一周中的哪一天? 我想将它们存储为整数值,0表示星期日,1表示星期一,因为这就是Time类的wday方法。
  • 任何建议都会非常有帮助。


    Is there a way for me to store the the record for Tuesday and
    Wednesday in one row or do should I have two records?

    有多种方法可以在一行中存储多个时间范围。 @bma已经提供了其中几个。这可能对于使用非常简单的时间模式节省磁盘空间很有用。干净,灵活和"标准化"的方法是每个时间范围存储一行。

    What is the best way to store the day and time?

    使用timestamp(如果必须处理多个时区,则使用timestamptz)。选择任意"分段"周,并在使用timestamp的日期和时间方面时忽略日期部分。我的经验最简单,最快速,并且所有与日期和时间相关的完整性检查都是自动内置的。我使用以1996-01-01 00:00开头的范围用于几个类似的应用程序,原因有两个:

    • 一周的前7天与该月的日期一致(sun = 7)。
    • 这是最近的闰年(同时提供2月29日的年度模式)。

    范围类型

    由于您实际上处理时间范围(不仅仅是"日期和时间"),我建议使用内置范围类型tsrange(或tstzrange)。一个主要优点:您可以使用内置范围函数和运算符的库。需要Postgres 9.2或更高版本。

    例如,您可以在此基础上建立排除约束(通过可以提供额外好处的全功能GiST索引在内部实现),以排除重叠的时间范围。请考虑此相关答案的详细信息:

    • 使用PostgreSQL中的EXCLUDE防止相邻/重叠的条目

    对于此特定排除约束(每个事件没有重叠范围),您需要在约束中包含整数列event_id,因此您需要安装附加模块btree_gist。每个数据库安装一次:

    1
    CREATE EXTENSION btree_gist;  -- once per db

    或者,您可以使用一个简单的CHECK约束来使用"范围包含"运算符<@来限制允许的时间段。

    看起来像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CREATE TABLE event (event_id serial PRIMARY KEY, ...);

    CREATE TABLE schedule (
       event_id integer NOT NULL REFERENCES event(event_id)
                        ON DELETE CASCADE ON UPDATE CASCADE
     , t_range  tsrange
     , PRIMARY KEY (event_id, t_range)
     , CHECK (t_range <@ '[1996-01-01 00:00, 1996-01-09 00:00)')  -- restrict period
     , EXCLUDE USING gist (event_id WITH =, t_range WITH &&)      -- disallow overlap
    );

    对于每周时间表,使用前七天,周一至周日或任何适合您的方式。每月或每年的时间表以类似的方式。

    如何提取星期,时间等?

    @CDub提供了一个模块来处理Ruby端的问题。我无法对此发表评论,但你也可以在Postgres中做一切,表现无可挑剔。

    1
    2
    SELECT ts::time AS t_time           -- get the time (practically no cost)
    SELECT EXTRACT(DOW FROM ts) AS dow  -- get day of week (very cheap)

    或类似的范围类型:

    1
    2
    3
    4
    5
    SELECT EXTRACT(DOW FROM lower(t_range)) AS dow_from  -- day of week lower bound
         , EXTRACT(DOW FROM upper(t_range)) AS dow_to    -- same for upper
         , lower(t_range)::time AS time_from             -- start time
         , upper(t_range)::time AS time_to               -- end time
    FROM   schedule;

    SQL小提琴。

    ISODOW而不是DOW for EXTRACT()在周日返回7而不是0。您可以提取的内容很长。

    此相关答案演示了如何使用范围类型运算符计算时间范围的总持续时间(上一章):

    • 计算PostgreSQL中两个日期之间的工作时间

    根据您的调度需求的复杂程度,您可能需要查看RFC 5545(iCalendar调度数据格式),以获取有关如何存储数据的建议。

    如果你需要的话非常简单,那可能就是矫枉过正。 Postgresql有许多功能可以将日期和时间转换为您需要的任何格式。

    对于存储相对日期和时间的简单方法,您可以将星期几存储为建议的整数,将时间存储为TIME数据类型。如果您可以在一周中有多天有效,则可能需要使用ARRAY。

    例如。

    • ARRAY [2,3] :: INTEGER [] =星期二,星期三作为星期几
    • '15:00:00'::时间=下午3点

    [编辑:添加一些简单的例子]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    /* Custom the time and timetz range types */
    CREATE TYPE timerange AS RANGE (subtype = time);

    --drop table if exists schedule;
    create table schedule (
        event_id    integer not null, /* should be an FK to"events" table */
        day_of_week integer[],
        time_of_day time,
        time_range  timerange,
        recurring   text CHECK (recurring IN ('DAILY','WEEKLY','MONTHLY','YEARLY'))
    );

    insert into schedule (event_id, day_of_week, time_of_day, time_range, recurring)
    values
    (1, ARRAY[1,2,3,4,5]::INTEGER[], '15:00:00'::TIME, NULL, 'WEEKLY'),
    (2, ARRAY[6,0]::INTEGER[], NULL, '(08:00:00,17:00:00]'::timerange, 'WEEKLY');

    select * from schedule;

     event_id | day_of_week | time_of_day |     time_range      | recurring
    ----------+-------------+-------------+---------------------+-----------
            1 | {1,2,3,4,5} | 15:00:00    |                     | WEEKLY
            2 | {6,0}       |             | (08:00:00,17:00:00] | WEEKLY

    第一个条目可以理解为:该活动在周一至周五下午3点有效,此时间表每周发生一次。
    第二个条目可以理解为:该活动周六和周日上午8点至下午5点有效,每周发生一次。

    自定义范围类型"timerange"用于表示时间范围的下边界和上边界。
    '('表示"包含",尾随']表示"独占",或换句话说"大于或等于8am且小于5pm"。


    看看ice_cube gem(链接)。

    它可以为您创建一个计划对象,您可以将其保存到数据库中。您无需创建两个单独的记录。对于第二部分,您可以根据任何规则创建计划,您无需担心如何将其保存在数据库中。您可以使用gem提供的方法从持久化的计划对象中获取所需的任何信息。


    为什么不直接存储日期戳,然后使用Date的内置功能来获取星期几?

    1
    2
    3
    4
    2.0.0p247 :139 > Date.today
     => Sun, 10 Nov 2013
    2.0.0p247 :140 > Date.today.strftime("%A")
     =>"Sunday"

    strftime听起来它可以为你做一切。以下是它的具体文档。

    特别是对于你所说的,听起来你需要一个has_many :scheduleshas_many :schedules,其中Schedule将有一个start_date时间戳......