[california/wip/725785-create-recurring: 13/16] YEARLY quick-add and tests



commit 91bdf5b343c9a781ab1eb8f3c759865c4fbe396c
Author: Jim Nelson <jim yorba org>
Date:   Tue Jun 24 16:27:05 2014 -0700

    YEARLY quick-add and tests

 src/component/component-details-parser.vala |   86 ++++++-----
 src/component/component.vala                |    7 +-
 src/tests/tests-quick-add-recurring.vala    |  215 +++++++++++++++++++++++++++
 3 files changed, 267 insertions(+), 41 deletions(-)
---
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 88133ae..13b4297 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -451,7 +451,7 @@ public class DetailsParser : BaseObject {
         return String.is_numeric(amount.casefolded) ? int.parse(amount.casefolded) : -1;
     }
     
-    // Returns negative value if ordinalis invalid
+    // Returns negative value if ordinal is invalid
     private int parse_ordinal(Token? ordinal) {
         if (ordinal == null)
             return -1;
@@ -499,11 +499,18 @@ public class DetailsParser : BaseObject {
             return set_rrule_daily(1);
         
         if (specifier.casefolded == WEEKLY) {
-            // set the start date to today unless already known
-            if (start_date == null)
-                start_date = Calendar.System.today;
+            if (start_date != null)
+                set_rrule_weekly(iterate<Calendar.DayOfWeek>(start_date.day_of_week).to_array(), 1);
+            else
+                set_rrule(iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE, 1);
             
-            return set_rrule_weekly(iterate<Calendar.DayOfWeek>(start_date.day_of_week).to_array(), 1);
+            return true;
+        }
+        
+        if (specifier.casefolded == YEARLY) {
+            set_rrule(iCal.icalrecurrencetype_frequency.YEARLY_RECURRENCE, 1);
+            
+            return true;
         }
         
         if (specifier.casefolded in UNIT_WEEKDAYS)
@@ -562,26 +569,32 @@ public class DetailsParser : BaseObject {
         if (unit.casefolded in UNIT_WEEKENDS)
             return set_rrule_weekly(Calendar.DayOfWeek.weekend_days, interval);
         
-        // if no start date, then parse for start date, and if so, treat as yearly event
-        if (start_date == null) {
-            stack.mark();
-            {
-                Token? second = stack.pop();
-                if (second != null) {
-                    Calendar.Date? date = parse_day_month(unit, second);
-                    if (date == null)
-                        date = parse_day_month(second, unit);
-                    
-                    if (date != null)
-                        return set_rrule_yearly(date, interval);
-                }
+        //parse for date, and if so, treat as yearly event
+        stack.mark();
+        {
+            if (unit == specifier)
+                unit = stack.pop();
+            
+            if (unit != null) {
+                Calendar.Date? date = parse_day_month(specifier, unit);
+                if (date == null)
+                    date = parse_day_month(unit, specifier);
+                
+                if (date != null)
+                    return set_rrule_nth_day_of_year(date, 1);
             }
-            stack.restore();
         }
+        stack.restore();
         
         return false;
     }
     
+    private void set_rrule(iCal.icalrecurrencetype_frequency freq, int interval) {
+        rrule = new RecurrenceRule(freq);
+        rrule.interval = interval;
+        rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
+    }
+    
     // Using the supplied by days, find the first upcoming start_date that matches one of them
     // that is also the week number (unless zero, which means "any")
     private void set_byday_start_date(Calendar.DayOfWeek[]? by_days, int week_no) {
@@ -613,22 +626,17 @@ public class DetailsParser : BaseObject {
         if (start_date == null)
             start_date = Calendar.System.today;
         
-        rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE);
-        rrule.interval = interval;
-        rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
+        set_rrule(iCal.icalrecurrencetype_frequency.DAILY_RECURRENCE, interval);
         
         return true;
     }
     
     // "every tuesday"
     private bool set_rrule_weekly(Calendar.DayOfWeek[]? by_days, int interval) {
-        if (rrule == null) {
-            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE);
-            rrule.interval = interval;
-            rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
-        } else if (!rrule.is_weekly) {
+        if (rrule == null)
+            set_rrule(iCal.icalrecurrencetype_frequency.WEEKLY_RECURRENCE, interval);
+        else if (!rrule.is_weekly)
             return false;
-        }
         
         Gee.Map<Calendar.DayOfWeek?, int> map = from_array<Calendar.DayOfWeek>(by_days)
             .to_hash_map_as_keys<int>(dow => 0);
@@ -646,12 +654,10 @@ public class DetailsParser : BaseObject {
         if (week_no < 1 || week_no > 5)
             return false;
         
-        if (rrule == null) {
-            rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.MONTHLY_RECURRENCE);
-            rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
-        } else if (!rrule.is_monthly) {
+        if (rrule == null)
+            set_rrule(iCal.icalrecurrencetype_frequency.MONTHLY_RECURRENCE, 1);
+        else if (!rrule.is_monthly)
             return false;
-        }
         
         Gee.Map<Calendar.DayOfWeek?, int> map = from_array<Calendar.DayOfWeek>(by_days)
             .to_hash_map_as_keys<int>(dow => week_no);
@@ -663,16 +669,16 @@ public class DetailsParser : BaseObject {
     }
     
     // "every july 4th"
-    private bool set_rrule_yearly(Calendar.Date date, int interval) {
-        if (rrule != null)
+    private bool set_rrule_nth_day_of_year(Calendar.Date date, int interval) {
+        if (rrule == null)
+            set_rrule(iCal.icalrecurrencetype_frequency.YEARLY_RECURRENCE, interval);
+        else if (!rrule.is_yearly)
             return false;
         
-        start_date = date;
+        if (start_date == null)
+            start_date = date;
         
-        rrule = new RecurrenceRule(iCal.icalrecurrencetype_frequency.YEARLY_RECURRENCE);
-        rrule.interval = interval;
-        rrule.first_of_week = Calendar.System.first_of_week.as_day_of_week();
-        rrule.set_by_rule(RecurrenceRule.ByRule.YEAR_DAY, iterate<int>(date.day_of_year).to_array_list());
+        rrule.add_by_rule(RecurrenceRule.ByRule.YEAR_DAY, iterate<int>(date.day_of_year).to_array_list());
         
         return true;
     }
diff --git a/src/component/component.vala b/src/component/component.vala
index 23f49b2..d4521d8 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -22,6 +22,7 @@ private string TOMORROW;
 private string YESTERDAY;
 private string DAILY;
 private string WEEKLY;
+private string YEARLY;
 private string[] UNIT_WEEKDAYS;
 private string[] UNIT_WEEKENDS;
 private string[] UNIT_YEARS;
@@ -66,6 +67,10 @@ public void init() throws Error {
     // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
     WEEKLY = _("weekly").casefold();
     
+    // Used by quick-add to indicate the user wants to create a yearly recurring event
+    // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+    YEARLY = _("yearly").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)
     // Common abbreviations (without punctuation) should be included.  Each word must be separated
@@ -178,7 +183,7 @@ public void terminate() {
     
     TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES = null;
     COMMON_PREPOSITIONS = DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
-    TODAY = TOMORROW = YESTERDAY = DAILY = WEEKLY = null;
+    TODAY = TOMORROW = YESTERDAY = DAILY = WEEKLY = YEARLY = null;
     UNIT_WEEKDAYS = UNIT_WEEKENDS = UNIT_YEARS = UNIT_MONTHS = UNIT_WEEKS = UNIT_DAYS = UNIT_HOURS
         = UNIT_MINS = null;
     
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index 7c83850..ac0d7a7 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -6,6 +6,11 @@
 
 namespace California.Tests {
 
+/**
+ * Note that some tests are repeated with different days of the week to avoid false positives when
+ * the current day of the week (at time of execution) matches quick-add details.
+ */
+
 private class QuickAddRecurring : UnitTest.Harness {
     public QuickAddRecurring() {
         // ByRule.DAY encoding/decoding tests
@@ -27,7 +32,11 @@ private class QuickAddRecurring : UnitTest.Harness {
         
         // WEEKLY
         add_case("every-tuesday", every_tuesday);
+        add_case("every-friday", every_friday);
+        add_case("weekly-meeting-monday", weekly_meeting_monday);
+        add_case("weekly-meeting-tuesday", weekly_meeting_tuesday);
         add_case("tuesday_weekly", tuesday_weekly);
+        add_case("thursday-weekly", thursday_weekly);
         add_case("weekdays_to_1pm", weekdays_to_1pm);
         add_case("weekends", weekends);
         add_case("every_weekend", every_weekend);
@@ -38,6 +47,20 @@ private class QuickAddRecurring : UnitTest.Harness {
         // MONTHLY
         add_case("every-first-tuesday", every_first_tuesday);
         add_case("every-sixth-tuesday", every_sixth_tuesday);
+        
+        // YEARLY
+        add_case("every-july-4th", every_july_4th);
+        add_case("every-july-15th", every_july_15th);
+        add_case("every-4th-july", every_4th_july);
+        add_case("every-15th-july", every_15th_july);
+        add_case("july-4th-yearly", july_4th_yearly);
+        add_case("july-15th-yearly", july_15th_yearly);
+        add_case("yearly-july-4th", yearly_july_4th);
+        add_case("yearly-july-15th", yearly_july_15th);
+        add_case("yearly-meeting-july-4th", yearly_meeting_july_4th);
+        add_case("yearly-meeting-july-15th", yearly_meeting_july_15th);
+        add_case("meeting-every-july-4th-15th", meeting_every_july_4th_15th);
+        add_case("every-july-4th-3-years", every_july_4th_3_years);
     }
     
     protected override void setup() throws Error {
@@ -261,6 +284,45 @@ private class QuickAddRecurring : UnitTest.Harness {
             && check_byrule_day(event, by_days);
     }
     
+    private bool every_friday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.FRI).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work at 10am every friday", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.FRI)
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool weekly_meeting_monday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.MON).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("weekly meeting at work monday at 10am", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.MON)
+            && check_byrule_day(event, by_days);
+    }
+    
+    private bool weekly_meeting_tuesday(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.TUE).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("weekly meeting at work tuesday at 10am", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+            && check_byrule_day(event, by_days);
+    }
+    
     private bool tuesday_weekly(out string? dump) throws Error {
         Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
             Calendar.DayOfWeek.TUE).to_hash_map_as_keys<int>(dow => 0);
@@ -274,6 +336,19 @@ private class QuickAddRecurring : UnitTest.Harness {
             && check_byrule_day(event, by_days);
     }
     
+    private bool thursday_weekly(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
+        
+        Component.Event event;
+        return basic("meeting at work thursday at 10am weekly", out event, out dump)
+            && event.rrule.is_weekly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU)
+            && check_byrule_day(event, by_days);
+    }
+    
     private bool weekdays_to_1pm(out string? dump) throws Error {
         Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
             Calendar.DayOfWeek.weekdays).to_hash_map_as_keys<int>(dow => 0);
@@ -385,6 +460,146 @@ private class QuickAddRecurring : UnitTest.Harness {
         return event.rrule == null
             && event.summary == "meeting at work every 6th";
     }
+    
+    //
+    // YEARLY
+    //
+    
+    private bool check_byrule_yearday(Component.Event event, Gee.Collection<int> by_yeardays) {
+        Gee.SortedSet<int> values = event.rrule.get_by_rule(Component.RecurrenceRule.ByRule.YEAR_DAY);
+        if (values.size != by_yeardays.size)
+            return false;
+        
+        return traverse<int>(by_yeardays).all(yearday => values.contains(yearday));
+    }
+    
+    private bool every_july_4th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work at 10am every july 4th", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
+    
+    private bool every_july_15th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work at 10am every july 15th", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 15;
+    }
+    
+    private bool every_4th_july(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work at 10am every 4th july", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
+    
+    private bool every_15th_july(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work at 10am every 15th july", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 15;
+    }
+    
+    private bool july_4th_yearly(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work july 4th 10am yearly", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
+    
+    private bool july_15th_yearly(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work july 15th 10am yearly", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 15;
+    }
+    
+    private bool yearly_july_4th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work yearly july 4th 10am", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
+    
+    private bool yearly_july_15th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work yearly july 15th 10am", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 15;
+    }
+    
+    private bool yearly_meeting_july_4th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("yearly meeting at work july 4th 10am", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
+    
+    private bool yearly_meeting_july_15th(out string? dump) throws Error {
+        Component.Event event;
+        return basic("yearly meeting at work july 15th 10am", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 15;
+    }
+    
+    private bool meeting_every_july_4th_15th(out string? dump) throws Error {
+        Calendar.Date july4 = new Calendar.Date(Calendar.DayOfMonth.for(4), Calendar.Month.JUL,
+            Calendar.System.today.year);
+        Calendar.Date july15 = new Calendar.Date(Calendar.DayOfMonth.for(15), Calendar.Month.JUL,
+            Calendar.System.today.year);
+        
+        Component.Event event;
+        return basic("meeting every july 4th and july 15 10am at work", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && !event.rrule.has_duration
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && (event.exact_time_span.start_date.day_of_month.value == 15
+                || event.exact_time_span.start_date.day_of_month.value == 4)
+            && event.exact_time_span.start_date.equal_to(event.exact_time_span.end_date)
+            && check_byrule_yearday(event, iterate<Calendar.Date>(july4, july15).map<int>(d => 
d.day_of_year).to_array_list());
+    }
+    
+    private bool every_july_4th_3_years(out string? dump) throws Error {
+        Component.Event event;
+        return basic("meeting at work at 10am every july 4th for 3 years", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && event.rrule.count == 3
+            && event.exact_time_span.start_date.month == Calendar.Month.JUL
+            && event.exact_time_span.start_date.day_of_month.value == 4;
+    }
 }
 
 }


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