[california/wip/725785-create-recurring] Revised Date.upcoming/prior, nth day of week (of month) now works



commit 5296060d2b251ea077f67760b21a4f8a983a4eab
Author: Jim Nelson <jim yorba org>
Date:   Fri Jun 20 16:35:32 2014 -0700

    Revised Date.upcoming/prior, nth day of week (of month) now works

 src/calendar/calendar-date.vala             |   62 ++++++-----------------
 src/calendar/calendar-week.vala             |    3 +
 src/component/component-details-parser.vala |   28 +++++++---
 src/tests/tests-calendar-date.vala          |   72 ++++++++++++++++++++++-----
 src/tests/tests-quick-add-recurring.vala    |    2 +-
 src/tests/tests-quick-add.vala              |    3 +-
 src/tests/tests.vala                        |    4 +-
 7 files changed, 103 insertions(+), 71 deletions(-)
---
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index e9b90d1..9f0e71f 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -256,67 +256,35 @@ public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
     }
     
     /**
-     * Returns the { link Date} of the upcoming (next chronological) { link DayOfWeek}.
+     * Returns the { link Date} of the upcoming (next chronological) Date that matches
+     * the predicate's requirements.
      *
-     * Set { link includes_this_day} to true if this Date is to be considered "upcoming", that is,
-     * if it falls on the day of the week, it is returned.
+     * inclusive indicates if this Date is included in the search.
      *
      * @see prior
      */
-    public Date upcoming(DayOfWeek dow, bool includes_this_day) {
-        return upcoming_prior(iterate<DayOfWeek>(dow).to_hash_set(), includes_this_day, 1);
+    public Date upcoming(bool inclusive, Gee.Predicate<Calendar.Date> predicate) {
+        return upcoming_prior(inclusive, 1, predicate);
     }
     
     /**
-     * Returns the { link Date} of the upcoming (next chronological) { link DayOfWeek} from the
-     * set provided.
+     * Returns the { link Date} of the prior (next chronological) Date that matches
+     * the predicate's requirements.
      *
-     * Set { link includes_this_day} to true if this Date is to be considered "upcoming", that is,
-     * if it falls on the day of the week, it is returned.
-     *
-     * @see prior_in_set
-     */
-    public Date upcoming_in_set(Gee.Set<DayOfWeek> dow_set, bool includes_this_day) {
-        return upcoming_prior(dow_set, includes_this_day, 1);
-    }
-    
-    /**
-     * Returns the { link Date} of the prior (previous chronological) { link DayOfWeek}.
-     *
-     * Set { link includes_this_day} to true if this Date is to be considered "prior", that is,
-     * if it falls on the day of the week, it is returned.
+     * inclusive indicates if this Date is included in the search.
      *
      * @see upcoming
      */
-    public Date prior(DayOfWeek dow, bool includes_this_day) {
-        return upcoming_prior(iterate<DayOfWeek>(dow).to_hash_set(), includes_this_day, -1);
-    }
-    
-    /**
-     * Returns the { link Date} of the prior (next chronological) { link DayOfWeek} from the
-     * set provided.
-     *
-     * Set { link includes_this_day} to true if this Date is to be considered "prior", that is,
-     * if it falls on the day of the week, it is returned.
-     *
-     * @see upcoming_in_set
-     */
-    public Date prior_in_set(Gee.Set<DayOfWeek> dow_set, bool includes_this_day) {
-        return upcoming_prior(dow_set, includes_this_day, -1);
+    public Date prior(bool inclusive, Gee.Predicate<Calendar.Date> predicate) {
+        return upcoming_prior(inclusive, -1, predicate);
     }
     
-    private Date upcoming_prior(Gee.Set<DayOfWeek> dow_set, bool includes_this_day, int adjustment) {
-        // look for current date being the one
-        if (dow_set.contains(day_of_week) && includes_this_day)
-            return this;
+    private Date upcoming_prior(bool inclusive, int adjustment, Gee.Predicate<Calendar.Date> predicate) {
+        Calendar.Date current = inclusive ? this : adjust(adjustment);
+        while (!predicate(current))
+            current = current.adjust(adjustment);
         
-        // find a Date for day of the week ... brute force isn't great, but it works
-        Date upcoming_prior = this;
-        for (;;) {
-            upcoming_prior = upcoming_prior.adjust(adjustment);
-            if (dow_set.contains(upcoming_prior.day_of_week))
-                return upcoming_prior;
-        }
+        return current;
     }
     
     /**
diff --git a/src/calendar/calendar-week.vala b/src/calendar/calendar-week.vala
index 2ff35a0..65f3eaa 100644
--- a/src/calendar/calendar-week.vala
+++ b/src/calendar/calendar-week.vala
@@ -19,6 +19,9 @@ namespace California.Calendar {
  */
 
 public class Week : Unit<Week>, Gee.Comparable<Week>, Gee.Hashable<Week> {
+    public const int MIN_WEEK_OF_MONTH = 1;
+    public const int MAX_WEEK_OF_MONTH = 6;
+    
     /**
      * The one-based week of the month (1 to 6).
      */
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index e5ee3cc..88133ae 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -541,8 +541,7 @@ public class DetailsParser : BaseObject {
         // a day of the week
         Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(unit.casefolded);
         if (dow != null) {
-            Calendar.DayOfWeek[] by_days = iterate<Calendar.DayOfWeek>(
-                Calendar.System.today.upcoming(dow, true).day_of_week).to_array();
+            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 (!is_ordinal)
@@ -589,8 +588,13 @@ public class DetailsParser : BaseObject {
         // 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) {
-             Calendar.Date earliest = Calendar.System.today.upcoming_in_set(
-                from_array<Calendar.DayOfWeek>(by_days).to_hash_set(), true);
+            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)
+                    return false;
+                
+                return dows.contains(date.day_of_week);
+            });
             if (start_date == null || earliest.compare_to(start_date) < 0)
                 start_date = earliest;
         }
@@ -630,24 +634,30 @@ public class DetailsParser : BaseObject {
             .to_hash_map_as_keys<int>(dow => 0);
         rrule.add_by_rule(RecurrenceRule.ByRule.DAY, RecurrenceRule.encode_days(map));
         
-        set_byday_start_date(by_days);
+        set_byday_start_date(by_days, 0);
         
         return true;
     }
     
     // "every first tuesday"
     private bool set_rrule_nth_day_of_week(Calendar.DayOfWeek[]? by_days, int week_no) {
+        // 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)
+            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) {
             return false;
         }
+        
         Gee.Map<Calendar.DayOfWeek?, int> map = from_array<Calendar.DayOfWeek>(by_days)
-            .to_hash_map_as_keys<int>(dow => 0);
+            .to_hash_map_as_keys<int>(dow => week_no);
         rrule.add_by_rule(RecurrenceRule.ByRule.DAY, RecurrenceRule.encode_days(map));
         
-        set_byday_start_date(by_days);
+        set_byday_start_date(by_days, week_no);
         
         return true;
     }
@@ -713,7 +723,9 @@ public class DetailsParser : BaseObject {
         // attempt to parse into day of the week
         Calendar.DayOfWeek? dow = Calendar.DayOfWeek.parse(token.casefolded);
         
-        return (dow != null) ? Calendar.System.today.upcoming(dow, true) : null;
+        return (dow != null)
+            ? Calendar.System.today.upcoming(true, date => date.day_of_week.equal_to(dow))
+            : null;
     }
     
     // Parses potential date specifiers into a specific calendar date
diff --git a/src/tests/tests-calendar-date.vala b/src/tests/tests-calendar-date.vala
index 0291696..2d3b085 100644
--- a/src/tests/tests-calendar-date.vala
+++ b/src/tests/tests-calendar-date.vala
@@ -14,8 +14,10 @@ private class CalendarDate : UnitTest.Harness {
         add_case("clamp-neither", clamp_neither);
         add_case("difference-pos", difference_pos);
         add_case("difference-neg", difference_neg);
-        add_case("upcoming", upcoming);
-        add_case("prior", prior);
+        add_case("upcoming-inclusive", upcoming_inclusive);
+        add_case("upcoming-exclusive", upcoming_exclusive);
+        add_case("prior-inclusive", prior_inclusive);
+        add_case("prior-exclusive", prior_exclusive);
         add_case("upcoming-today", upcoming_today);
         add_case("upcoming-next-week", upcoming_next_week);
     }
@@ -82,25 +84,70 @@ private class CalendarDate : UnitTest.Harness {
         return today.difference(day_before_yesterday) == -2;
     }
     
-    private bool upcoming() throws Error {
+    private bool upcoming(bool inclusive, out string? dump) throws Error {
+        dump = null;
+        
         Calendar.Date today = Calendar.System.today;
-        Calendar.Date upcoming_fri = today.upcoming(Calendar.DayOfWeek.FRI, false);
-        int diff = today.difference(upcoming_fri);
         
-        return diff > 0 && diff <= 7;
+        foreach (Calendar.DayOfWeek dow in Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)) {
+            Calendar.Date upcoming = Calendar.System.today.upcoming(inclusive,
+                date => date.day_of_week.equal_to(dow));
+            int diff = today.difference(upcoming);
+            
+            dump = "%s - %s = %d".printf(today.to_string(), upcoming.to_string(), diff);
+            
+            if (!inclusive && diff == 0)
+                return false;
+            
+            if (diff < 0 || diff > 7)
+                return false;
+        }
+        
+        return true;
+    }
+    
+    private bool upcoming_inclusive(out string? dump) throws Error {
+        return upcoming(true, out dump);
+    }
+    
+    private bool upcoming_exclusive(out string? dump) throws Error {
+        return upcoming(false, out dump);
     }
     
-    private bool prior() throws Error {
+    private bool prior(bool inclusive, out string? dump) throws Error {
+        dump = null;
+        
         Calendar.Date today = Calendar.System.today;
-        Calendar.Date prior_tue = today.prior(Calendar.DayOfWeek.TUE, false);
-        int diff = today.difference(prior_tue);
         
-        return diff < 0 && diff >= -7;
+        foreach (Calendar.DayOfWeek dow in Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)) {
+            Calendar.Date upcoming = Calendar.System.today.prior(inclusive,
+                date => date.day_of_week.equal_to(dow));
+            int diff = today.difference(upcoming);
+            
+            dump = "%s - %s = %d".printf(today.to_string(), upcoming.to_string(), diff);
+            
+            if (!inclusive && diff == 0)
+                return false;
+            
+            if (diff > 0 || diff < -7)
+                return false;
+        }
+        
+        return true;
+    }
+    
+    private bool prior_inclusive(out string? dump) throws Error {
+        return prior(false, out dump);
+    }
+    
+    private bool prior_exclusive(out string? dump) throws Error {
+        return prior(false, out dump);
     }
     
     private bool upcoming_today() throws Error {
         Calendar.Date today = Calendar.System.today;
-        Calendar.Date another_today = today.upcoming(today.day_of_week, true);
+        Calendar.Date another_today = today.upcoming(true,
+            date => date.day_of_week.equal_to(today.day_of_week));
         int diff = today.difference(another_today);
         
         return diff == 0;
@@ -108,7 +155,8 @@ private class CalendarDate : UnitTest.Harness {
     
     private bool upcoming_next_week() throws Error {
         Calendar.Date today = Calendar.System.today;
-        Calendar.Date next_week = today.upcoming(today.day_of_week, false);
+        Calendar.Date next_week = today.upcoming(false,
+            date => date.day_of_week.equal_to(today.day_of_week));
         int diff = today.difference(next_week);
         
         return diff == 7;
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index acb6ac1..07879d8 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -152,7 +152,7 @@ private class QuickAddRecurring : UnitTest.Harness {
         Component.DetailsParser parser = new Component.DetailsParser(details, null);
         event = parser.event;
         
-        dump = event.source;
+        dump = "%s\n%s".printf(details, event.source);
         
         return event.rrule != null
             && event.summary == "meeting at work"
diff --git a/src/tests/tests-quick-add.vala b/src/tests/tests-quick-add.vala
index 0972812..b111d41 100644
--- a/src/tests/tests-quick-add.vala
+++ b/src/tests/tests-quick-add.vala
@@ -179,7 +179,8 @@ private class QuickAdd : UnitTest.Harness {
         Component.DetailsParser parser = new Component.DetailsParser(
             "12:30pm Friday Lunch with Eric and Charles", null);
         
-        Calendar.Date friday = Calendar.System.today.upcoming(Calendar.DayOfWeek.FRI, true);
+        Calendar.Date friday = Calendar.System.today.upcoming(true,
+            date => date.day_of_week.equal_to(Calendar.DayOfWeek.FRI));
         
         Calendar.ExactTime start = new Calendar.ExactTime(Calendar.Timezone.local, friday,
             new Calendar.WallTime(12, 30, 0));
diff --git a/src/tests/tests.vala b/src/tests/tests.vala
index 0f88f76..22b3579 100644
--- a/src/tests/tests.vala
+++ b/src/tests/tests.vala
@@ -8,12 +8,12 @@ namespace California.Tests {
 
 public int run(string[] args) {
     UnitTest.Harness.register(new String());
-    UnitTest.Harness.register(new QuickAdd());
-    UnitTest.Harness.register(new QuickAddRecurring());
     UnitTest.Harness.register(new CalendarDate());
     UnitTest.Harness.register(new CalendarMonthSpan());
     UnitTest.Harness.register(new CalendarMonthOfYear());
     UnitTest.Harness.register(new CalendarWallTime());
+    UnitTest.Harness.register(new QuickAdd());
+    UnitTest.Harness.register(new QuickAddRecurring());
     
     return UnitTest.Harness.exec_all();
 }


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