重复发生的日历事件

时间:2013-02-14 01:24:09

标签: php mysql

我正在尝试构建自己的日历,并且我有一个用于输入以下所示事件的表单。

关于如何设计MySQL表以及如何以可以轻松提取的方式记录这些信息,我一直困扰着?

calendar event form

以下是我的尝试,它有效,但真的很难看,我相信它可以改进:

CREATE TABLE events (
  event_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  title VARCHAR(100) NOT NULL,
  location VARCHAR(100) NOT NULL,
  body TEXT NOT NULL,
  start_ts DATETIME NOT NULL,
  end_ts DATETIME NOT NULL,
  valid ENUM('Y','N') DEFAULT 'Y',
  reoccurring ENUM('Y','N') DEFAULT 'N',
  every ENUM('day','week','month','year',''),
  bymonth ENUM('day','weekday',''),
  end_date DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',

  PRIMARY KEY (event_id)
);

CREATE TABLE events_byweek (
  event_id INTEGER UNSIGNED NOT NULL,
  weekday TINYINT UNSIGNED NOT NULL,

  FOREIGN KEY (event_id)
    REFERENCES events(event_id)
);

-- returns all dates, reoccurring or otherwise within specified time range
-- by month
SELECT * FROM events WHERE (:year = YEAR(start_ts) AND :month = MONTH(start_ts))
OR (reoccurring = 'Y' AND ((YEAR(end_date) >= :year AND MONTH(end_date) >= :month) OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'

-- by day
SELECT * FROM events WHERE DATE(start_ts) = :date
OR (reoccurring = 'Y' AND (DATE(end_date) >= :date OR end_date = '0000-00-00 00:00:00')
AND (every != 'year' OR MONTH(start_ts) = :month)) AND valid = 'Y'

-- by week
SELECT * FROM events_byweek WHERE event_id = :event_id

我真的很感激你的建议!

2 个答案:

答案 0 :(得分:6)

我认为你不需要任何关系数组。

您的数据库表可能是:

event_id UINT(10) auto_increment NOT NULL
/* not important fields omitted */
start DATETIME not null
end DATETIME not null
reoccurring ENUM('NO', 'WEEKDAY', 'MONTHDAY', 'MONTH_REL', 'YEARLY') DEFAULT 'NO';
weekdays UINT(1) DEFAULT 0;
until TIMESTAMP DEFAULT NULL

我使用DATETIME因为它更容易查询(见下文),但实际上并不重要。如果您愿意,可以保留TIMESTAMP

weekdays中,可以保留一个字节,其位为2^0:星期日,2^1:星期一,依此类推。因此,如果事件每天重新出现,您可以放置​​127

如果untilNULL,则该事件将永远重复。

因为很难通过SQL找到“本月的第3个星期三”是否在特定范围内,我认为即使不是不可能也没有用户定义的函数,但是非常难以且代码不清楚,我建议你把你所有的事件,在php中取出并过滤掉。

仅加载必要事件(预过滤)的查询将是:

SELECT ... FROM events
  WHERE 
     /* We take all non-reoccuring events in period */
    ((reoccurring = 'NO') AND (start >= :start) AND (end <= :end))
  OR
     /* We take some part of reoccurring events */
    ((reoccuring <> 'NO') AND ((start <= :end) OR (end >= :start)) AND ((until >= :start) OR until IS NULL)
  ORDER BY start ASC;

因此,在提取时,您可以测试记录是否符合以下条件:

  1. 不再发生,所以保持
  2. 是在特定的工作日(例如每周三和每个星期五)重复出现 - 检查在这段时间之间是否存在这样的工作日,如果至少有一个工作日,则该事件保持不变,否则,将其扔掉
  3. 如果重复发生在月份日(例如12月21日),请执行相同操作。
  4. 如果每个月 - 相对日(如第三个星期三)重复出现,请执行相同的操作 等
  5. 如果您的记录不符合条件,请从阵列中删除它,而不是数据库: - )。

    将一些任务放入SQL查询也很容易。例如,您可以按SELECT ... WHERE ... OR ... (start LIKE '%-:month-:day %)过滤特定月 - 日的重复事件(假设事件的开始和结束相同,如问题中的图片所示)。这是DATETIME字段的一些优点,您可以轻松地搜索它们,就像它们是字符串一样,因此%-12-21 %查找具有第12个月和第21天的所有记录(它们应始终为两位数字,课程)。 (TIMESTAMP的优点是很容易计算日期差异等。)

    如果事件在每个工作日重复,请使用... LIKE% - % - :day%`,依此类推。

    所以最后你需要两个返回boolean的函数来检查两种情况:

    1. 是特定时段的工作日
    2. 是该期间的第n个工作日(请注意,如果第一个失败,则不需要运行第二个)
    3. 您甚至可以使用foreach或其他东西蛮力对其进行编码。

      此外,如果字段值为127,则无需执行工作日检查 - 因此它每天都会发生。

      2013-03-29编辑 - 解释如何将比特用作工作日

      在字段weekdays中,可以将天数保留为比特,因为在一个(无符号)INT(1)数字中有7天和8位。因此,您不必添加更多列,例如“occurrence _monday”,“happen_tuesday”等,也不必使用任何关系。我这样做是因为我认为事件可能发生在“每个星期一”和“每个星期五”。如果没有,请在那里保留一个数字(0 =星期日,1 =星期一等)。

      此外,每天发生的事件也是每周7天发生的事件的特例,因此我在ENUM列中不需要另外reoccurring值。

      现在如何在PHP中使用它?

      您只需要测试特定工作日的位是否已设置。您可以使用按位AND运算符执行此操作。如果你定义一些常量,它会更简单:

      define("WEEKDAY_SUNDAY",1); // 2^0
      define("WEEKDAY_MONDAY",2); // 2^1
      define("WEEKDAY_TUESDAY",4); // 2^2
      // ....
      define("WEEKDAY_SATURDAY",64); // 2^6
      
      // Does the event occur on Friday, maybe also other weekdays?
      if($row['weekdays'] & WEEKDAY_FRIDAY){
      
      // Does the event occurs only on Friday, and no other day?
      if($row['weekdays'] == WEEKDAY_FRIDAY){
      
      // Let's make the event occur on Friday and the day(s) that it already occurs
      $row['weekdays'] = $row['weekdays'] | WEEKDAY_FRIDAY;
      
      // Make the event occur only on Friday and no other weekday
      $row['weekdays'] = WEEKDAY_FRIDAY;
      
      // Let's check if the event occurs today:
      if(pow(2,date('w')) & $row['weekdays']){ //...
      

      带有“w”参数的date函数返回从0到6的星期几数字,这就是我使用它的原因。

答案 1 :(得分:2)

之前我已解决过这个问题而且很复杂。但是,我确实想出了一个我觉得有意义的解决方案(在堆栈溢出的帮助下)。我最后做的是创建事件表和事件重复表。活动表全权负责举办个人活动。事件重复表负责保存重复实例。

然后我使用事件重复表来生成进入事件表的事件。这允许的是显示事件的简化方法。这是因为已经为您完成了重复计算。创建重复时,我会生成两年的事件。如果用户在该两年范围之外导航,那么我会根据需要生成更多事件。

每晚都会运行一个cron作业,以确保每次重复都会生成至少两年的事件。然后,每次重复都需要生成直到字段,结束重复,重复类型以及标题,描述,时间等信息。

如果你决定走这条路,需要代码示例,请务必告诉我。

相关问题