[california/wip/725785-create-recurring] First steps, RRULE definition and infrastructure



commit 4c8838e8cd5556acf9cebd0c3a966faa89a0aadf
Author: Jim Nelson <jim yorba org>
Date:   Fri Jun 6 16:07:31 2014 -0700

    First steps, RRULE definition and infrastructure
    
    This only starts to add recurring rule parsing to quick-add.
    Add recurring UI to create/update widget will come later.

 src/Makefile.am                              |    1 +
 src/component/component-date-time.vala       |   40 +++
 src/component/component-details-parser.vala  |   36 +++
 src/component/component-event.vala           |   45 ++++
 src/component/component-recurrence-rule.vala |  350 ++++++++++++++++++++++++++
 src/component/component.vala                 |   33 +++-
 vapi/libical.vapi                            |    5 +-
 7 files changed, 506 insertions(+), 4 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 20b2634..4f67b8d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -85,6 +85,7 @@ california_VALASOURCES = \
        component/component-event.vala \
        component/component-icalendar.vala \
        component/component-instance.vala \
+       component/component-recurrence-rule.vala \
        component/component-uid.vala \
        component/component-vtype.vala \
        \
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index f8121a5..93a0d34 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -115,6 +115,46 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
     }
     
     /**
+     * Creates a new { link DateTime} for a component's RRULE UNTIL property.
+     */
+    public DateTime.rrule_until(iCal.icalrecurrencetype rrule, DateTime dtstart) throws ComponentError {
+        if (iCal.icaltime_is_null_time(rrule.until) != 0)
+            throw new ComponentError.INVALID("DATE-TIME for RRULE UNTIL is null time");
+        
+        if (iCal.icaltime_is_valid_time(rrule.until) != 0)
+            throw new ComponentError.INVALID("DATE-TIME for RRULE UNTIL is invalid");
+        
+        bool until_is_date = (iCal.icaltime_is_date(rrule.until) != 0);
+        bool until_is_utc = (iCal.icaltime_is_utc(rrule.until) != 0);
+        
+        // "The value of the UNTIL rule part MUST have the same value type as the 'DTSTART'
+        // property
+        if (dtstart.is_date != until_is_date)
+            throw new ComponentError.INVALID("RRULE UNTIL and DTSTART must be of same type 
(DATE/DATE-TIME)");
+        
+        // "If the 'DTSTART' property is specified as a date with local time, then the UNTIL rule
+        // part MUST also be specified as a date with local time."
+        if (dtstart.is_utc != until_is_utc)
+            throw new ComponentError.INVALID("RRULE UNTIL and DTSTART must be of same time type 
(UTC/local)");
+        
+        // "if the 'DTSTART' property is specified as a date with UTC time or a date with local time
+        // and a time zone reference, then the UNTIL rule part MUST be specified as a date with
+        // UTC time."
+        if (dtstart.is_date || (!dtstart.is_utc && dtstart.zone != null)) {
+            if (!until_is_utc)
+                throw new ComponentError.INVALID("RRULE UNTIL must be UTC for DTSTART DATE or w/ time zone");
+        }
+        
+        // "If specified as a DATE-TIME value, then it MUST be specified in a UTC time format."
+        if (!until_is_date && !until_is_utc)
+            throw new ComponentError.INVALID("RRULE DATE-TIME UNTIL must be UTC");
+        
+        kind = iCal.icalproperty_kind.RRULE_PROPERTY;
+        dt = rrule.until;
+        zone = (!until_is_date || until_is_utc) ? Calendar.OlsonZone.utc : null;
+    }
+    
+    /**
      * Converts the stored iCal DATE-TIME to an { link Calendar.ExactTime}.
      *
      * Returns null if { link is_date} is true.
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index e5ad17b..ccea24d 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -158,6 +158,13 @@ public class DetailsParser : BaseObject {
                 continue;
             stack.restore();
             
+            // A recurring preposition suggests a regular occurrance is being described by the next
+            // two tokens
+            stack.mark();
+            if (token.casefolded in RECURRING_PREPOSITIONS && parse_recurring(stack.pop(), stack.pop()))
+                continue;
+            stack.restore();
+            
             // only look for location prepositions if not already adding text to the location field
             if (!adding_location && token.casefolded in LOCATION_PREPOSITIONS) {
                 // add current token (the preposition) to summary but not location (because location
@@ -337,6 +344,35 @@ public class DetailsParser : BaseObject {
         return true;
     }
     
+    private bool parse_recurring(Token? amount, Token? unit) {
+        // only one is required
+        if (amount == null && unit == null)
+            return false;
+        
+        // recurring can be specified with the amount acting as a day specifier, i.e. "every Friday"
+        // or a single unit, i.e. "every day" or "every weekday"
+        if (unit == null) {
+            Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(amount.casefolded);
+            if (dow != null) {
+                return true;
+            }
+            
+            if (amount.casefolded == DAY) {
+                return true;
+            }
+            
+            if (amount.casefolded == WEEKDAY) {
+                return true;
+            }
+            
+            if (amount.casefolded == WEEKEND) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
     // Adds the text to the summary and location field, if adding_location is set
     private void add_text(Token token) {
         // always add to summary
diff --git a/src/component/component-event.vala b/src/component/component-event.vala
index 8b157f1..6ef205f 100644
--- a/src/component/component-event.vala
+++ b/src/component/component-event.vala
@@ -20,6 +20,7 @@ public class Event : Instance, Gee.Comparable<Event> {
     public const string PROP_IS_ALL_DAY = "is-all-day";
     public const string PROP_LOCATION = "location";
     public const string PROP_STATUS = "status";
+    public const string PROP_RRULE = "rrule";
     
     public enum Status {
         TENTATIVE,
@@ -82,6 +83,11 @@ public class Event : Instance, Gee.Comparable<Event> {
     public Status status { get; set; default = Status.CONFIRMED; }
     
     /**
+     * { link RecurrenceRule} (RRULE) for { link Event}.
+     */
+    public RecurrenceRule? rrule { get; private set; default = null; }
+    
+    /**
      * Create an { link Event} { link Component} from an EDS CalComponent object.
      *
      * Throws a BackingError if the E.CalComponent's VTYPE is not VEVENT.
@@ -150,6 +156,12 @@ public class Event : Instance, Gee.Comparable<Event> {
                 status = Status.CONFIRMED;
             break;
         }
+        
+        try {
+            make_recurring(new RecurrenceRule.from_ical(ical_component));
+        } catch (ComponentError comperr) {
+            // ignored; generally means no RRULE in component
+        }
     }
     
     private void on_notify(ParamSpec pspec) {
@@ -214,6 +226,20 @@ public class Event : Instance, Gee.Comparable<Event> {
                 }
             break;
             
+            case PROP_RRULE:
+                // whether rrule is added or removed (cleared), clear from ical_component
+                unowned iCal.icalproperty? rrule_property = ical_component.get_first_property(
+                    iCal.icalproperty_kind.RRULE_PROPERTY);
+                while (rrule_property != null) {
+                    ical_component.remove_property(rrule_property);
+                    rrule_property = ical_component.get_next_property(iCal.icalproperty_kind.RRULE_PROPERTY);
+                }
+                
+                // add back if necessary
+                if (rrule != null)
+                    rrule.to_ical(ical_component);
+            break;
+            
             default:
                 altered = false;
             break;
@@ -320,6 +346,25 @@ public class Event : Instance, Gee.Comparable<Event> {
     }
     
     /**
+     * Add a { link RecurrenceRule} to the { link Event}.
+     *
+     * Pass null to make non-recurring.
+     */
+    public void make_recurring(RecurrenceRule? rrule) {
+        if (this.rrule != null)
+            this.rrule.by_rule_updated.disconnect(on_by_rule_updated);
+        
+        if (rrule != null)
+            rrule.by_rule_updated.connect(on_by_rule_updated);
+        
+        this.rrule = rrule;
+    }
+    
+    private void on_by_rule_updated() {
+        // TODO: Update ical_component with new RRULE
+    }
+    
+    /**
      * @inheritDoc
      */
     public override bool is_valid() {
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
new file mode 100644
index 0000000..4a579d3
--- /dev/null
+++ b/src/component/component-recurrence-rule.vala
@@ -0,0 +1,350 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Component {
+
+/**
+ * A mutable convenience representation of an iCalendar recurrence rule (RRULE).
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]]
+ * and [[https://tools.ietf.org/html/rfc5545#section-3.8.5.3]]
+ */
+
+public class RecurrenceRule : BaseObject {
+    /**
+     * Enumeration of various BY rules (BYSECOND, BYMINUTE, etc.)
+     */
+    public enum ByRule {
+        SECOND,
+        MINUTE,
+        HOUR,
+        DAY,
+        MONTH_DAY,
+        YEAR_DAY,
+        WEEK_NUM,
+        MONTH,
+        SET_POS
+    }
+    
+    /**
+     * Frequency.
+     *
+     * This is the only required field in an RRULE.
+     */
+    public iCal.icalrecurrencetype_frequency freq { get; set; }
+    
+    /**
+     * Until (end date), inclusive.
+     *
+     * This is mutually exclusive with { link count}.
+     *
+     * @see set_until_date_time
+     */
+    public DateTime? until { get; private set; default = null; }
+    
+    /**
+     * Total number of recurrences.
+     *
+     * Zero indicates "not set", not zero recurrences.
+     *
+     * This is mutually exclusive with { link until}.
+     *
+     * @see set_recurrence_count
+     */
+    public int count { get; private set; default = 0; }
+    
+    /**
+     * Interval between recurrences.
+     *
+     * A positive integer representing the interval (duration between) of each recurrence.  The
+     * actual amount of time elapsed is determined by the { link frequency} property.
+     */
+    private short _interval;
+    public short interval {
+        get { return _interval; }
+        set { _interval = value.clamp(0, short.MAX); }
+        default = 0;
+    }
+    
+    /**
+     * Start of work week (WKST).
+     */
+    public Calendar.DayOfWeek? start_of_week { get; set; default = null; }
+    
+    private Gee.SortedSet<int> by_second = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_minute = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_hour = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_day = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_month_day = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_year_day = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_week_num = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_month = new Gee.TreeSet<int>();
+    private Gee.SortedSet<int> by_set_pos = new Gee.TreeSet<int>();
+    
+    /**
+     * Fired when a BY rule is updated (BYSECOND, BYMINUTE, etc.)
+     */
+    public signal void by_rule_updated(ByRule by_rule);
+    
+    public RecurrenceRule(iCal.icalrecurrencetype_frequency freq) {
+        this.freq = freq;
+    }
+    
+    internal RecurrenceRule.from_ical(iCal.icalcomponent ical_component) throws ComponentError {
+        // need DTSTART for timezone purposes
+        DateTime dtstart = new DateTime(ical_component, iCal.icalproperty_kind.DTSTART_PROPERTY);
+        
+        // fetch the RRULE from the component
+        unowned iCal.icalproperty? rrule_property = ical_component.get_first_property(
+            iCal.icalproperty_kind.RRULE_PROPERTY);
+        if (rrule_property == null)
+            throw new ComponentError.UNAVAILABLE("No RRULE found in component");
+        
+        iCal.icalrecurrencetype rrule = rrule_property.get_rrule();
+        
+        freq = rrule.freq;
+        if (rrule.count > 0)
+            set_recurrence_count(rrule.count);
+        else
+            set_until_date_time(new DateTime.rrule_until(rrule, dtstart));
+        interval = rrule.interval;
+        
+        switch (rrule.week_start) {
+            case iCal.icalrecurrencetype_weekday.SUNDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.SUN;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.MONDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.MON;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.TUESDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.TUE;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.WEDNESDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.WED;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.THURSDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.THU;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.FRIDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.FRI;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.SATURDAY_WEEKDAY:
+                start_of_week = Calendar.DayOfWeek.SAT;
+            break;
+            
+            case iCal.icalrecurrencetype_weekday.NO_WEEKDAY:
+            default:
+                start_of_week = null;
+            break;
+        }
+        
+        fill_by(rrule.by_second, iCal.BY_SECOND_SIZE, by_second);
+        fill_by(rrule.by_minute, iCal.BY_MINUTE_SIZE, by_minute);
+        fill_by(rrule.by_hour, iCal.BY_HOUR_SIZE, by_hour);
+        fill_by(rrule.by_day, iCal.BY_DAY_SIZE, by_day);
+        fill_by(rrule.by_month_day, iCal.BY_MONTHDAY_SIZE, by_month_day);
+        fill_by(rrule.by_year_day, iCal.BY_YEARDAY_SIZE, by_year_day);
+        fill_by(rrule.by_week_no, iCal.BY_WEEKNO_SIZE, by_week_num);
+        fill_by(rrule.by_month, iCal.BY_MONTH_SIZE, by_month);
+        fill_by(rrule.by_set_pos, iCal.BY_SETPOS_SIZE, by_set_pos);
+    }
+    
+    private void fill_by(short[] ical_by_ar, int ical_by_ar_len, Gee.SortedSet<int> by_set) {
+        for (int ctr = 0; ctr < ical_by_ar_len; ctr++) {
+            short by = ical_by_ar[ctr];
+            if (by == iCal.RECURRENCE_ARRAY_MAX)
+                break;
+            
+            by_set.add(by);
+        }
+    }
+    
+    /**
+     * Sets the { link until} property.
+     *
+     * Also sets { link count} to zero.
+     *
+     * Passing null will clear both properties.
+     */
+    public void set_until_date_time(DateTime? date_time) {
+        until = date_time;
+        count = 0;
+    }
+    
+    /**
+     * Sets the { link count} property.
+     *
+     * Also clears { link until}.
+     *
+     * Passing zero will clear both properties.
+     */
+    public void set_recurrence_count(int count) {
+        this.count = count;
+        until = null;
+    }
+    
+    /**
+     * Returns a read-only sorted set of BY rule settings.
+     */
+    public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
+        switch (by_rule) {
+            case ByRule.SECOND:
+                return by_second.read_only_view;
+            
+            case ByRule.MINUTE:
+                return by_minute.read_only_view;
+            
+            case ByRule.HOUR:
+                return by_hour.read_only_view;
+            
+            case ByRule.DAY:
+                return by_day.read_only_view;
+            
+            case ByRule.MONTH_DAY:
+                return by_month_day.read_only_view;
+            
+            case ByRule.YEAR_DAY:
+                return by_year_day.read_only_view;
+            
+            case ByRule.WEEK_NUM:
+                return by_week_num.read_only_view;
+            
+            case ByRule.MONTH:
+                return by_month.read_only_view;
+            
+            case ByRule.SET_POS:
+                return by_set_pos.read_only_view;
+            
+            default:
+                assert_not_reached();
+        }
+    }
+    
+    /**
+     * Replaces the existing set of values for the BY rules with the supplied values.
+     *
+     * @see by_rule_updated
+     */
+    public void set_by_rule(ByRule by_rule, Gee.Collection<int>? values) {
+        Gee.SortedSet<int> by_set;
+        switch (by_rule) {
+            case ByRule.SECOND:
+                by_set = by_second;
+            break;
+            
+            case ByRule.MINUTE:
+                by_set = by_minute;
+            break;
+            
+            case ByRule.HOUR:
+                by_set = by_hour;
+            break;
+            
+            case ByRule.DAY:
+                by_set = by_day;
+            break;
+            
+            case ByRule.MONTH_DAY:
+                by_set = by_month_day;
+            break;
+            
+            case ByRule.YEAR_DAY:
+                by_set = by_year_day;
+            break;
+            
+            case ByRule.WEEK_NUM:
+                by_set = by_week_num;
+            break;
+            
+            case ByRule.MONTH:
+                by_set = by_month;
+            break;
+            
+            case ByRule.SET_POS:
+                by_set = by_set_pos;
+            break;
+            
+            default:
+                assert_not_reached();
+        }
+        
+        by_set.clear();
+        if (values != null && values.size > 0)
+            by_set.add_all(values);
+        
+        by_rule_updated(by_rule);
+    }
+    
+    /**
+     * Converts a { link RecurrenceRule} into an iCalendar RRULE property and adds it to the
+     * iCal component.
+     */
+    internal void to_ical(iCal.icalcomponent ical_component) {
+        iCal.icalrecurrencetype rrule = { 0 };
+        rrule.freq = freq;
+        rrule.until = until.dt;
+        rrule.count = count;
+        rrule.interval = interval;
+        
+        if (start_of_week == null)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.NO_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.SUN)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.SUNDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.MON)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.MONDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.TUE)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.TUESDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.WED)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.WEDNESDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.THU)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.THURSDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.FRI)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.FRIDAY_WEEKDAY;
+        else if (start_of_week == Calendar.DayOfWeek.SAT)
+            rrule.week_start = iCal.icalrecurrencetype_weekday.SATURDAY_WEEKDAY;
+        else
+            assert_not_reached();
+        
+        fill_ical_by(by_second, rrule.by_second, iCal.BY_SECOND_SIZE);
+        fill_ical_by(by_minute, rrule.by_minute, iCal.BY_MINUTE_SIZE);
+        fill_ical_by(by_hour, rrule.by_hour, iCal.BY_HOUR_SIZE);
+        fill_ical_by(by_day, rrule.by_day, iCal.BY_DAY_SIZE);
+        fill_ical_by(by_month_day, rrule.by_month_day, iCal.BY_MONTHDAY_SIZE);
+        fill_ical_by(by_year_day, rrule.by_year_day, iCal.BY_YEARDAY_SIZE);
+        fill_ical_by(by_week_num, rrule.by_week_no, iCal.BY_WEEKNO_SIZE);
+        fill_ical_by(by_month, rrule.by_month, iCal.BY_MONTH_SIZE);
+        fill_ical_by(by_set_pos, rrule.by_set_pos, iCal.BY_SETPOS_SIZE);
+        
+        iCal.icalproperty rrule_property = new iCal.icalproperty(iCal.icalproperty_kind.RRULE_PROPERTY);
+        rrule_property.set_rrule(rrule);
+        
+        // TODO: Remove any existing RRULE properties
+        
+        ical_component.add_property(rrule_property);
+    }
+    
+    private void fill_ical_by(Gee.SortedSet<int> by_set, short[] ical_by_ar, int ical_by_ar_len) {
+        int index = 0;
+        foreach (int by in by_set)
+            ical_by_ar[index++] = (short) by;
+        
+        if (index < ical_by_ar_len)
+            ical_by_ar[index] = (short) iCal.RECURRENCE_ARRAY_MAX;
+    }
+    
+    public override string to_string() {
+        return "RRULE %s".printf(freq.to_string());
+    }
+}
+
+}
+
diff --git a/src/component/component.vala b/src/component/component.vala
index 9c917c8..82b9975 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -20,10 +20,14 @@ private int init_count = 0;
 private string TODAY;
 private string TOMORROW;
 private string YESTERDAY;
+private string DAY;
+private string WEEKDAY;
+private string WEEKEND;
 private string[] TIME_PREPOSITIONS;
 private string[] LOCATION_PREPOSITIONS;
 private string[] DURATION_PREPOSITIONS;
 private string[] DELAY_PREPOSITIONS;
+private string[] RECURRING_PREPOSITIONS;
 private string[] ORDINAL_SUFFIXES;
 
 public void init() throws Error {
@@ -46,6 +50,20 @@ public void init() throws Error {
     // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
     YESTERDAY = _("yesterday").casefold();
     
+    // Used by quick-add to indicate the user wants to create an event for every day
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    DAY = _("day").casefold();
+    
+    // Used by quick-add to indicate the user wants to create an event for every weekday
+    // (in most Western countries, this means Monday through Friday, i.e. the work week)
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    WEEKDAY = _("weekday").casefold();
+    
+    // Used by quick-add to indicate the user wants to create an event for every weekend
+    // (in most Western countries, this means Saturday and Sunday, i.e. non-work days)
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    WEEKEND = _("weekend").casefold();
+    
     // Used by quick-add to determine if the word is a TIME preposition (indicating a
     // specific time of day, not a duration).  Each word must be separated by semi-colons.
     // It's allowable for some or all of these words to
@@ -73,6 +91,15 @@ public void init() throws Error {
     // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
     DELAY_PREPOSITIONS = _("in;").casefold().split(";");
     
+    // Used by quick-add to determine if the word is a RECURRING preposition (indicating a
+    // regular occurrance in time).  Each word must be separated by semi-colons.
+    // It's allowable for some or all of these words to be duplicated in the location
+    // prepositions list (elsewhere) but not another time list.
+    // The list can be empty, but that will limit the parser.
+    // Example: "every 3 days", "every Friday"
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    RECURRING_PREPOSITIONS = _("every;").casefold().split(";");
+    
     // Used by quick-add to determine if the word is a LOCATION preposition (indicating a
     // specific place).  Each word must be separated by semi-colons.
     // It's allowable for some or all of these words to be duplicated in
@@ -95,9 +122,9 @@ public void terminate() {
     if (!Unit.do_terminate(ref init_count))
         return;
     
-    TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES =
-        DELAY_PREPOSITIONS =null;
-    TODAY = TOMORROW = YESTERDAY = null;
+    TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES = null;
+    DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
+    TODAY = TOMORROW = YESTERDAY = DAY = WEEKDAY = WEEKEND = null;
     
     Calendar.terminate();
     Collection.terminate();
diff --git a/vapi/libical.vapi b/vapi/libical.vapi
index c583a74..b1429e7 100644
--- a/vapi/libical.vapi
+++ b/vapi/libical.vapi
@@ -1706,7 +1706,10 @@ namespace iCal {
                [CCode (cname = "icalperiodtype_null_period")]
                public static iCal.icalperiodtype null_period ();
        }
-       [CCode (cheader_filename = "libical/ical.h")]
+       public const int RECURRENCE_ARRAY_MAX;
+       public const int RUCURRENCE_ARRAY_MAX_BYTE;
+       [CCode (cheader_filename = "libical/ical.h", cname="struct icalrecurrencetype")]
+       [SimpleType]
        public struct icalrecurrencetype {
                public iCal.icalrecurrencetype_frequency freq;
                public iCal.icaltimetype until;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]