[california/wip/725785-create-recurring: 16/16] Fix dealing with day-of-week position in month for DTSTART



commit 509c8fb81756483a83b0b6d3ed048ec7e25f8f7f
Author: Jim Nelson <jim yorba org>
Date:   Tue Jun 24 19:12:41 2014 -0700

    Fix dealing with day-of-week position in month for DTSTART

 src/calendar/calendar-date.vala             |   26 ++++++++++++++++++
 src/calendar/calendar-week.vala             |   13 +++++++++
 src/component/component-details-parser.vala |   21 ++++++++------
 src/tests/tests-calendar-date.vala          |   38 +++++++++++++++++++++++++++
 src/tests/tests-quick-add-recurring.vala    |   37 +++++++++++++++++++++++++-
 5 files changed, 125 insertions(+), 10 deletions(-)
---
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 9f0e71f..7c800b4 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -196,6 +196,32 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
     }
     
     /**
+     * Returns the 1-based position of the current { link day_of_week} in the { link month}.
+     *
+     * For example, if this is the first Monday of the month, returns 1.
+     */
+    public int day_of_week_position_in_month() {
+        MonthOfYear moy = month_of_year();
+        
+        // Walk every week of this month ... first day of week doesn't matter here, first Monday
+        // is first Monday whether or not the week starts with Sunday or Monday
+        // TODO: Would be better to find a non-iterative algorithm for this
+        int position = 1;
+        foreach (Week week in moy.to_week_span(FirstOfWeek.SUNDAY)) {
+            // if this date is in the week, found it
+            if (week.contains(this))
+                break;
+            
+            // only increase position counter if this day of week is present in the week that is
+            // also in this month (weeks can span months)
+            if (week.date_at(day_of_week).month_of_year().equal_to(moy))
+                position++;
+        }
+        
+        return position;
+    }
+    
+    /**
      * Returns the { link MonthOfYear} the { link Date} falls in.
      */
     public MonthOfYear month_of_year() {
diff --git a/src/calendar/calendar-week.vala b/src/calendar/calendar-week.vala
index 65f3eaa..f33292c 100644
--- a/src/calendar/calendar-week.vala
+++ b/src/calendar/calendar-week.vala
@@ -64,6 +64,19 @@ public class Week : Unit<Week>, Gee.Comparable<Week>, Gee.Hashable<Week> {
     }
     
     /**
+     * Returns the { link Date} for the { link DayOfWeek}.
+     */
+    public Date date_at(DayOfWeek dow) {
+        // although mixing FirstOfWeek is dangerous, don't trust simple math here because of this issue
+        foreach (Date date in to_date_span()) {
+            if (date.day_of_week.equal_to(dow))
+                return date;
+        }
+        
+        assert_not_reached();
+    }
+    
+    /**
      * @inheritDoc
      */
     public override Week adjust(int quantity) {
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 0c067a6..4be57a2 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -544,7 +544,8 @@ public class DetailsParser : BaseObject {
         if (dow != null) {
             Calendar.DayOfWeek[] by_days = iterate<Calendar.DayOfWeek>(dow).to_array();
             
-            // if interval is an ordinal, the rule is for "nth day of the month", so it's a week number
+            // if interval is an ordinal, the rule is for "nth day of the month", so it's a position
+            // (i.e. "1st tuesday")
             if (!is_ordinal)
                 return set_rrule_weekly(by_days, interval);
             else
@@ -590,14 +591,16 @@ public class DetailsParser : BaseObject {
     }
     
     // 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) {
+    // that is also the position (unless zero, which means "any")
+    private void set_byday_start_date(Calendar.DayOfWeek[]? by_days, int position) {
+        assert(position >= 0);
+        
         // find the earliest date in the by_days; if it's earlier than the start_date or the
         // start_date isn't defined, use the earliest
         if (by_days != null) {
             Gee.Set<Calendar.DayOfWeek> dows = from_array<Calendar.DayOfWeek>(by_days).to_hash_set();
              Calendar.Date earliest = Calendar.System.today.upcoming(true, (date) => {
-                if (week_no != 0 && date.week_of(Calendar.System.first_of_week).week_of_month != week_no)
+                if (position != 0 && date.day_of_week_position_in_month() != position)
                     return false;
                 
                 return dows.contains(date.day_of_week);
@@ -641,11 +644,11 @@ public class DetailsParser : BaseObject {
         return true;
     }
     
-    // "every first tuesday"
-    private bool set_rrule_nth_day_of_week(Calendar.DayOfWeek[]? by_days, int week_no) {
+    // "every 1st tuesday"
+    private bool set_rrule_nth_day_of_week(Calendar.DayOfWeek[]? by_days, int position) {
         // Although a month can span 6 calendar weeks, a day of a week never appears in more than
         // five of them
-        if (week_no < 1 || week_no > 5)
+        if (position < 1 || position > 5)
             return false;
         
         if (rrule == null)
@@ -654,10 +657,10 @@ public class DetailsParser : BaseObject {
             return false;
         
         Gee.Map<Calendar.DayOfWeek?, int> map = from_array<Calendar.DayOfWeek>(by_days)
-            .to_hash_map_as_keys<int>(dow => week_no);
+            .to_hash_map_as_keys<int>(dow => position);
         rrule.add_by_rule(RecurrenceRule.ByRule.DAY, RecurrenceRule.encode_days(map));
         
-        set_byday_start_date(by_days, week_no);
+        set_byday_start_date(by_days, position);
         
         return true;
     }
diff --git a/src/tests/tests-calendar-date.vala b/src/tests/tests-calendar-date.vala
index 2d3b085..22c5205 100644
--- a/src/tests/tests-calendar-date.vala
+++ b/src/tests/tests-calendar-date.vala
@@ -20,6 +20,11 @@ private class CalendarDate : UnitTest.Harness {
         add_case("prior-exclusive", prior_exclusive);
         add_case("upcoming-today", upcoming_today);
         add_case("upcoming-next-week", upcoming_next_week);
+        add_case("day-of-week-position-1", day_of_week_position_1);
+        add_case("day-of-week-position-2", day_of_week_position_2);
+        add_case("day-of-week-position-3", day_of_week_position_3);
+        add_case("day-of-week-position-4", day_of_week_position_4);
+        add_case("day-of-week-position-5", day_of_week_position_5);
     }
     
     protected override void setup() throws Error {
@@ -161,6 +166,39 @@ private class CalendarDate : UnitTest.Harness {
         
         return diff == 7;
     }
+    
+    private bool test_dow_position(Calendar.Date date, int expected, out string? dump) throws Error {
+        int position = date.day_of_week_position_in_month();
+        
+        dump = "%s position=%d, expected=%d".printf(date.to_string(), position, expected);
+        
+        return position == expected;
+    }
+    
+    private Calendar.Date jun2014(int dom) throws Error {
+        return new Calendar.Date(Calendar.DayOfMonth.for(dom), Calendar.Month.JUN,
+            new Calendar.Year(2014));
+    }
+    
+    private bool day_of_week_position_1(out string? dump) throws Error {
+        return test_dow_position(jun2014(1), 1, out dump);
+    }
+    
+    private bool day_of_week_position_2(out string? dump) throws Error {
+        return test_dow_position(jun2014(9), 2, out dump);
+    }
+    
+    private bool day_of_week_position_3(out string? dump) throws Error {
+        return test_dow_position(jun2014(20), 3, out dump);
+    }
+    
+    private bool day_of_week_position_4(out string? dump) throws Error {
+        return test_dow_position(jun2014(23), 4, out dump);
+    }
+    
+    private bool day_of_week_position_5(out string? dump) throws Error {
+        return test_dow_position(jun2014(30), 5, out dump);
+    }
 }
 
 }
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index 3c94ee9..2db35d4 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -50,6 +50,7 @@ private class QuickAddRecurring : UnitTest.Harness {
         // MONTHLY
         add_case("every-first-tuesday", every_first_tuesday);
         add_case("every-first-tuesday-for-3-weeks", every_first_tuesday_for_3_weeks);
+        add_case("every-second-sunday-until", every_second_sunday_until);
         add_case("every-sixth-tuesday", every_sixth_tuesday);
         
         // YEARLY
@@ -65,6 +66,7 @@ private class QuickAddRecurring : UnitTest.Harness {
         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);
+        add_case("every-aug-1st-until", every_aug_1st_until);
     }
     
     protected override void setup() throws Error {
@@ -514,7 +516,7 @@ private class QuickAddRecurring : UnitTest.Harness {
             Calendar.DayOfWeek.TUE).to_hash_map_as_keys<int>(dow => 1);
         
         Component.Event event;
-        return basic("meeting at work at 10am every 1st tuesday for 3 weeks", out event, out dump)
+        return basic("meeting at work at 10am every 1st tuesday for 3 months", out event, out dump)
             && event.rrule.is_monthly
             && event.rrule.interval == 1
             && event.rrule.count == 3
@@ -523,6 +525,24 @@ private class QuickAddRecurring : UnitTest.Harness {
             && check_byrule_day(event, by_days);
     }
     
+    private bool every_second_sunday_until(out string? dump) throws Error {
+        Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+            Calendar.DayOfWeek.SUN).to_hash_map_as_keys<int>(dow => 2);
+        
+        Component.Event event;
+        return basic("meeting at work at 10am every 2nd sunday until august 1st", out event, out dump)
+            && event.rrule.is_monthly
+            && event.rrule.interval == 1
+            && event.rrule.until_date != null
+            && event.rrule.until_date.month == Calendar.Month.AUG
+            && event.rrule.until_date.day_of_month.value == 1
+            && event.rrule.until_date.year.compare_to(Calendar.System.today.year) >= 0
+            && event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.SUN)
+            && event.exact_time_span.start_date.day_of_month.value >= 7
+            && event.exact_time_span.start_date.day_of_month.value <= 14
+            && check_byrule_day(event, by_days);
+    }
+    
     // bad input
     private bool every_sixth_tuesday(out string? dump) throws Error {
         Component.DetailsParser parser = new Component.DetailsParser(
@@ -674,6 +694,21 @@ private class QuickAddRecurring : UnitTest.Harness {
             && event.exact_time_span.start_date.month == Calendar.Month.JUL
             && event.exact_time_span.start_date.day_of_month.value == 4;
     }
+    
+    private bool every_aug_1st_until(out string? dump) throws Error {
+        Component.Event event;
+        return multiday("meeting at work aug 15 yearly until sep 1", out event, out dump)
+            && event.rrule.is_yearly
+            && event.rrule.interval == 1
+            && event.rrule.until_date != null
+            && event.rrule.until_date.month == Calendar.Month.SEP
+            && event.rrule.until_date.day_of_month.value == 1
+            && event.rrule.until_date.year.compare_to(Calendar.System.today.year) >= 0
+            && event.date_span.start_date.month == Calendar.Month.AUG
+            && event.date_span.start_date.day_of_month.value == 15
+            && event.date_span.start_date.year.compare_to(Calendar.System.today.year) >= 0
+            && event.date_span.end_date.equal_to(event.date_span.start_date);
+    }
 }
 
 }


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