【问题标题】:iCalendar "Field" list (for database schema based on iCalendar standard)iCalendar“字段”列表(用于基于 iCalendar 标准的数据库模式)
【发布时间】:2010-11-06 10:53:53
【问题描述】:

我的应用程序必须处理日历信息(包括单次出现、重复等)。为了轻松与其他应用程序交互,我认为直接基于 iCalendar 格式(字段、关系、约束)创建我的数据库模式是一个好主意,这样我就可以通过 ORM 获得 iCalendar 兼容的对象,我可以在何时轻松公开需要。

我知道 RFC 是可用的,但它有点复杂,因为其中包含我目前不使用的所有附加信息。

有人能指出一个更简单的来源来创建基于 iCal 标准的数据库架构(即字段/字段名称的列表及其与 iCal 条目的关系)吗?

谢谢!

【问题讨论】:

    标签: sql icalendar


    【解决方案1】:

    我已经这样做了(仅适用于 VEvents,不支持 TODO 项目或期刊全文或类似的东西)。我的实现看起来像这样(在删除不特定于问题的列之后):

    -- One table for each event.  An event may have multiple rRules.
    Create Table [vEvent]
        (vEventID Integer Identity(1, 1) Not Null
         Constraint [vEvent.pk]
         Primary Key
         Clustered
        ,title nVarChar(200) Not Null);
    
    -- One table for rRules.
    -- My application does NOT support the "bySetPos" rule, so that is not included.
    Create Table [rRule]
        (rRuleID Integer Identity(1, 1) Not Null
         Constraint [rRule.pk]
         Primary Key
         Clustered
        ,vEventID Integer Not Null
         Constraint [fk.vEvent.rRules]
         Foreign Key
         References [vEvent] (vEventID)
         On Update Cascade
         On Delete Cascade
        ,[class]            varChar(  12) Not Null Default('public')
        ,[created]         DateTime       Not Null Default(getUTCDate())
        ,[description]     nVarChar(max)      Null
        ,[dtStart]         DateTime       Not Null
        ,[dtEnd]           DateTime           Null
        ,[duration]         varChar(  20)     Null
        ,[geoLat]          Float              Null
        ,[geoLng]          Float              Null
        ,[lastModified]    DateTime       Not Null Default(getUTCDate())
        ,[location]        nVarChar(max)      Null
        ,[organizerCN]     nVarChar(  50)     Null
        ,[organizerMailTo] nVarChar( 100)     Null
        ,[seq]             Integer        Not Null Default(0)
        ,[status]           varChar(   9) Not Null Default('confirmed')
        ,[summary]         nVarChar(  75)     Null
        ,[transparent]     Bit            Not Null Default(0)
        ,[freq]             varChar(   8) Not Null Default('daily')
        ,[until]           DateTime           Null
        ,[count]           Integer            Null
        ,[interval]        Integer        Not Null Default(1)
        ,[bySecond]         varChar( 170)     Null
        ,[byMinute]         varChar( 170)     Null
        ,[byHour]           varChar(  61)     Null
        ,[byDay]            varChar(  35)     Null
        ,[byMonthDay]       varChar( 200)     Null
        ,[byYearDay]        varChar(3078)     Null
        ,[byWeekNo]         varChar( 353)     Null
        ,[byMonth]          varChar(  29)     Null
        ,[wkSt]             Char   (   2)     Null Default('mo'));
    
    -- Class must be one of "Confidential", "Private", or "Public"
    Alter Table [rRule]
    Add Constraint [rRule.ck.Class]
    Check ([class] In ('confidential', 'private', 'public'));
    
    -- Start date must come before End date
    Alter Table [rRule]
    Add Constraint [rRule.ck.dtStart]
    Check ([dtEnd] Is Null Or [dtStart] <= [dtEnd]);
    
    -- dtEnd and duration may not both be present
    Alter Table [rRule]
    Add Constraint [rRule.ck.duration]
    Check (Not ([dtEnd] Is Not Null And [duration] Is Not Null));
    
    -- Check valid values for [freq]. Note that 'single' is NOT in the RFC;
    -- it is an optimization for my particular iCalendar calculation engine.
    -- I use it as a clue that this pattern has only a single date (dtStart),
    -- and there is no need to perform extra calculations on it.
    Alter Table [rRule]
    Add Constraint [rRule.ck.freq]
    Check ([freq] In
        ('yearly'
        ,'monthly'
        ,'weekly'
        ,'daily'
        ,'hourly'
        ,'minutely'
        ,'secondly'
        ,'single')); -- Single is NOT part of the spec!
    
    -- If there is a latitude, there must be a longitude, and vice versa.
    Alter Table [rRule]
    Add Constraint [rRule.ck.geo]
    Check (([geoLat] Is Null And [geoLng] Is Null)
           Or ([geoLat] Is Not Null And [geoLng] Is Not Null));
    
    -- Interval must be positive.
    Alter Table [rRule]
    Add Constraint [rRule.ck.interval]
    Check ([interval] > 0);
    
    -- Status has a set of defined values.
    Alter Table [rRule]
    Add Constraint [rRule.ck.status]
    Check ([status] In ('cancelled', 'confirmed', 'tentative'));
    
    -- Until and Count may not coexist in the same rule.
    Alter Table [rRule]
    Add Constraint [rRule.ck.until and count]
    Check (Not ([until] Is Not Null And [count] Is Not Null));
    
    
    -- One table for exceptions to rRules.  In my application, this covers both
    -- exDate and rDate.  I do NOT support extended rule logic here;  The RFC says
    -- you should support the same sort of date calculations here as are supported
    -- in rRules: exceptions can recur, etc.  I don't do that; mine is simply a
    -- set of dates that are either "exceptions" (dates which don't appear, even
    -- if the rule otherwise says they should) or "extras" (dates which do appear,
    -- even if the rule otherwise wouldn't include them).  This has proved
    -- sufficient for my application, and something that can be exported into a
    -- valid iCalendar file--even if I can't import an iCalendar file that makes
    -- use of recurring rules for exceptions to recurring rules.
    Create Table [exDate]
        (exDateID Integer Identity(1, 1) Not Null
         Constraint [exDate.pk]
         Primary Key
         Clustered
        ,rRuleID Integer Not Null
         Constraint [fk.rRule.exDates]
         Foreign Key
         References [rRule] (rRuleID)
         On Update Cascade
         On Delete Cascade
        ,[date] DateTime Not Null
        ,[type] varChar(6) Not Null);  -- Type = "exDate" or "rDate" for me; YMMV.
    

    为此,我有几个 SQL Server 2005+ CLR 函数可用于计算各种事件的日期。我发现以下表格非常有用:

    Select * From dbo.getDatesByVEventID(@id, @startDate, @endDate)
    Select * From dbo.getEventsByDateRange(@startDate, @endDate, @maxCount)
    

    上面的实现非常有趣!

    【讨论】:

    • 我知道这已经超过 7 年了,这仍然是您处理这个问题的方式吗?新版本的 SQL 是否为您提供了更好的查询选项?
    【解决方案2】:

    是的,有点。 Sunbird(开源 mozilla 日历)基于 sqlite,我刚刚下载并解压缩了他们的源代码。它里面有 .sql 文件。

    ftp://ftp.mozilla.org/pub/mozilla.org/calendar/sunbird/releases/0.9/source/

    mozilla\calendar\providers\storage\schema-7.sql -- 这是 sunbird 用来制作有效 iCal 文件的架构,所以它不会太糟糕。

    【解决方案3】:

    非常感谢 Chris Nielsen 提供的上述出色解决方案。但是,我遇到了一些问题,所以我对其进行了修改。请注意,上面的解决方案是在 python sqlalchemy 中。我很快就会转换它。

    我对 Chris 的解决方案的主要困难(而且它们可能并不适用于所有人)是

    1. 我不需要他的解决方案中的许多列。我只需要可以帮助我处理事件和重复的列。这是 iCalendar 规范的错,而不是 Chris 的错。我下面的解决方案仅根据日历限制和顺序限制考虑重复规则。

    2. 某些列——最重要的是 dtStart 和 dtEnd——属于 VEVENT,而不是 RRULE,但 Chris 将它们放在 RRULE 中。这让我很困惑。 事件:https://www.rfc-editor.org/rfc/rfc5545#section-3.6.1 规则:https://www.rfc-editor.org/rfc/rfc5545#section-3.3.10

    3. 我还需要弄清楚如何包含可能具有多种模式的时间表。例如,一个事件可能会在每周周五的下午 6 点至晚上 9 点发生,但也可能会在五一节的全天发生。这需要 dtStart 和 dtEnd 的灵活性。为此,我创建了一个包含表“SCHEDULE”,它与 EVENTS 保持多对多关系,而 EVENTS 与 RRULES 具有包含关系。

    以下是我在 sqlalchemy 中的解决方案。我会尽快将其转换为 SQL。

    from app import db
    from sqlalchemy import CheckConstraint
    from sqlalchemy.ext.associationproxy import association_proxy
    
    
    class Schedule(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        subtypes_relation = db.relationship('Event', secondary=schedule_event_association,
                                            backref=db.backref('Schedule', lazy='dynamic'))
    
    schedule_event_association = db.Table(
        'schedule_event_association',
        db.Column('schedule_id', db.Integer, db.ForeignKey('schedule.id')),
        db.Column('event_id', db.Integer, db.ForeignKey('event.id')))
    
    class Event(db.Model):
        id = db.Column(db.Integer, primary_key=True)
        dt_start = db.Column(db.DateTime)  # start time
        dt_end = db.Column(db.DateTime) # end time
        tz_id = db.Column(db.String) # Time Zone
    
        recurrence_rule = db.Column('RecurrenceRule_id',  db.Integer, db.ForeignKey('RecurrenceRule.id'))
    
    # Start date must come before End date
        CheckConstraint('dtEnd is NULL OR dtStart <= dtEnd', name='Valid: Time Period')
    
    class RecurrenceRule(db.Model):
        id = db.Column(db.Integer, primary_key=True)
    
        # Frequency Type
        freq = db.Column(db.String(8), nullable=False, default='weekly') # type of recurrence
    
        # Calendar-Based Rules
        byDay = db.Column(db.String(35))   # List of Day of the Week
                                            # "mo,tu,we" for weekly
                                            # "+2MO, -1MO" = second monday, last monday for yearly or monthly
        byMonthDay = db.Column(db.String(200)) # List of Day of the Month
                                                # +1,-1"
                                                # Only for Monthly or Yearly
        byYearDay = db.Column(db.String(3078)) # List Day of the Year
                                                #"+1, -1"
                                                # Only for yearly
                                                # Take care with leap years
        byWeekNo = db.Column(db.String(353)) # Which week of Mon`enter code here`th
                                                # "+5, -3" for fifth and third-to-last
                                                # Only for yearly
        byMonth = db.Column(db.String(29))   # Month of year.
    
        # Sequence-Based Rules
        until = db.Column(db.DateTime)   # last day of occurence
        count = db.Column(db.Integer)    # number of occurences
        interval = db.Column(db.Integer, nullable=False, default=1) # interval between recurrences
        bysetpos = db.Column(db.String()) # Specifies specific instances of recurrence
    
    
    # Valid Values
        CheckConstraint(freq in ('yearly', 'monthly', 'weekly', 'daily', 'single'),
                        name='Valid: Frequency Value')
        CheckConstraint(interval > 0, name='Valid: Positive Interval')
        CheckConstraint(byDay is not None and freq in ('daily', 'yearly', 'monthly'))
        CheckConstraint(byWeekNo is not None and freq in ('yearly', 'monthly'))
        CheckConstraint(byYearDay is not None and freq == 'yearly')
    
    # Until and Count may not coexist in the same rule.
        CheckConstraint(not (until is not None and count is not None),
                        name='Valid: Not Both Until and Count')
    

    【讨论】:

      【解决方案4】:

      【讨论】:

      【解决方案5】:

      iCal 是一个 Apple 应用程序,它遵循目前称为 Icalendar(以前的 Vcalendar 的继承者)的标准。我认为 wikipedia entry 包含您所需的所有信息,并且格式简单易懂,但是,如果需要,请随时寻求更多指导!!!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-21
        • 1970-01-01
        • 2014-11-26
        • 1970-01-01
        • 2012-05-26
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多