[california] Support time ranges with Quick Add: Bug #731485



commit 7db54944dae373afadc7adf4b3789c94606abcaf
Author: Jim Nelson <jim yorba org>
Date:   Thu Aug 14 19:09:59 2014 -0700

    Support time ranges with Quick Add: Bug #731485
    
    Time ranges w/ dashes ("6p-9p") now supported by Quick Add.

 src/calendar/calendar-wall-time.vala        |   18 +-------
 src/calendar/calendar.vala                  |   31 ++++++++++++++
 src/component/component-details-parser.vala |   61 +++++++++++++++++++++++++-
 src/tests/tests-quick-add.vala              |   58 +++++++++++++++++--------
 4 files changed, 131 insertions(+), 37 deletions(-)
---
diff --git a/src/calendar/calendar-wall-time.vala b/src/calendar/calendar-wall-time.vala
index aaa8494..781a79b 100644
--- a/src/calendar/calendar-wall-time.vala
+++ b/src/calendar/calendar-wall-time.vala
@@ -150,22 +150,8 @@ public class WallTime : BaseObject, Gee.Comparable<WallTime>, Gee.Hashable<WallT
             return new WallTime(12, 0, 0);
         }
         
-        // look for meridiem tacked on to end
-        bool pm = false;
-        bool meridiem_unknown = false;
-        if (token.has_suffix(FMT_AM.casefold())) {
-            token = token.slice(0, token.length - FMT_AM.casefold().length);
-        } else if (token.has_suffix(FMT_BRIEF_AM.casefold())) {
-            token = token.slice(0, token.length - FMT_BRIEF_AM.casefold().length);
-        } else if (token.has_suffix(FMT_PM.casefold())) {
-            token = token.slice(0, token.length - FMT_PM.casefold().length);
-            pm = true;
-        } else if (token.has_suffix(FMT_BRIEF_PM.casefold())) {
-            token = token.slice(0, token.length - FMT_BRIEF_PM.casefold().length);
-            pm = true;
-        } else {
-            meridiem_unknown = true;
-        }
+        bool meridiem_unknown, pm;
+        token = parse_meridiem(token, out meridiem_unknown, out pm);
         
         // remove colon (can be present for 12- or 24-hour time)
         bool has_colon = token.index_of(":") > 0;
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index 25ae3f8..75cc426 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -258,4 +258,35 @@ public void terminate() {
     Collection.terminate();
 }
 
+/**
+ * Detects if the string has a meridiem prefix, either brief or full, depending on the locale.
+ *
+ * The string should be casefolded and stripped of leading and trailing whitespace.
+ *
+ * Returns the string with the meridiem stripped off as well as indicators about what was found,
+ * if anything.  If no meridiem was found, the original string is returned.
+ */
+private string parse_meridiem(string str, out bool meridiem_unknown, out bool is_pm) {
+    meridiem_unknown = false;
+    is_pm = false;
+    
+    string stripped;
+    if (str.has_suffix(FMT_AM.casefold())) {
+        stripped = str.slice(0, str.length - FMT_AM.casefold().length);
+    } else if (str.has_suffix(FMT_BRIEF_AM.casefold())) {
+        stripped = str.slice(0, str.length - FMT_BRIEF_AM.casefold().length);
+    } else if (str.has_suffix(FMT_PM.casefold())) {
+        stripped = str.slice(0, str.length - FMT_PM.casefold().length);
+        is_pm = true;
+    } else if (str.has_suffix(FMT_BRIEF_PM.casefold())) {
+        stripped = str.slice(0, str.length - FMT_BRIEF_PM.casefold().length);
+        is_pm = true;
+    } else {
+        stripped = str;
+        meridiem_unknown = true;
+    }
+    
+    return stripped;
+}
+
 }
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index bda7631..3f6b854 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -326,13 +326,25 @@ public class DetailsParser : BaseObject {
             return add_date(saturday) && add_date(sunday);
         }
         
-        // look for fully numeric date specifier
+        // look for fully numeric date specifier (i.e. "7/2/14")
         {
             Calendar.Date? date = parse_numeric_date(specifier);
             if (date != null && add_date(date))
                 return true;
         }
         
+        // look for time range (i.e. "6p-9p", "6-9p")
+        {
+            Calendar.WallTime start, end;
+            bool strictly_parsed;
+            if (parse_time_range(specifier, out start, out end, out strictly_parsed)) {
+                if (!strict || (strict && strictly_parsed)) {
+                    if (add_wall_time(start, strictly_parsed) && add_wall_time(end, strictly_parsed))
+                        return true;
+                }
+            }
+        }
+        
         // look for day/month specifiers, in any order
         stack.mark();
         {
@@ -753,9 +765,9 @@ public class DetailsParser : BaseObject {
         // punctuation removed
         int a, b, c;
         char[] separator = new char[token.original.length];
-        if (token.original.scanf("%d%[/.-]%d%[/.-]%d", out a, separator, out b, separator, out c) == 5) {
+        if (token.original.scanf("%d%[/.]%d%[/.]%d", out a, separator, out b, separator, out c) == 5) {
             // good to go
-        } else if (token.original.scanf("%d%[/.-]%d", out a, separator, out b) == 3) {
+        } else if (token.original.scanf("%d%[/.]%d", out a, separator, out b) == 3) {
             // -1 means two-number date was found, i.e. year must be determined manually
             c = -1;
         } else {
@@ -842,6 +854,49 @@ public class DetailsParser : BaseObject {
         }
     }
     
+    // strictly parsed means *both* were strictly parsed
+    private bool parse_time_range(Token token, out Calendar.WallTime start, out Calendar.WallTime end,
+        out bool strictly_parsed) {
+        start = null;
+        end = null;
+        strictly_parsed = false;
+        
+        string[] separated = token.original.split("-");
+        if (separated.length != 2)
+            return false;
+        
+        // fixup meridiems: if one has a specifier, assume for both
+        
+        string start_string = separated[0].casefold().strip();
+        bool start_meridiem_unknown, is_start_pm;
+        Calendar.parse_meridiem(start_string, out start_meridiem_unknown, out is_start_pm);
+        
+        string end_string = separated[1].casefold().strip();
+        bool end_meridiem_unknown, is_end_pm;
+        Calendar.parse_meridiem(end_string, out end_meridiem_unknown, out is_end_pm);
+        
+        if (!start_meridiem_unknown && end_meridiem_unknown)
+            end_string += is_start_pm ? Calendar.FMT_PM : Calendar.FMT_AM;
+        else if (start_meridiem_unknown && !end_meridiem_unknown)
+            start_string += is_end_pm ? Calendar.FMT_PM : Calendar.FMT_AM;
+        
+        // parse away
+        
+        bool start_strictly_parsed;
+        start = Calendar.WallTime.parse(start_string, out start_strictly_parsed);
+        if (start == null)
+            return false;
+        
+        bool end_strictly_parsed;
+        end = Calendar.WallTime.parse(end_string, out end_strictly_parsed);
+        if (end == null)
+            return false;
+        
+        strictly_parsed = start_strictly_parsed && end_strictly_parsed;
+        
+        return true;
+    }
+    
     // Parses a potential date specifier into a calendar date relative to today
     private Calendar.Date? parse_relative_date(Token token) {
         // attempt to parse into common words for relative dates
diff --git a/src/tests/tests-quick-add.vala b/src/tests/tests-quick-add.vala
index 19efdce..c902c37 100644
--- a/src/tests/tests-quick-add.vala
+++ b/src/tests/tests-quick-add.vala
@@ -45,11 +45,14 @@ private class QuickAdd : UnitTest.Harness {
         add_case("numeric-mdyyyy", numeric_mdyyyy);
         add_case("numeric-dmyyyy", numeric_dmyyyy);
         add_case("numeric-dot", numeric_dot);
-        add_case("numeric-dash", numeric_dash);
         add_case("numeric-leading-zeros", numeric_leading_zeroes);
         add_case("street-address_3", street_address_3);
         add_case("street-address_3a", street_address_3a);
         add_case("street-address_4", street_address_4);
+        add_case("time-range-both-meridiem", time_range_both_meridiem);
+        add_case("time-range-one-meridiem", time_range_one_meridiem);
+        add_case("time-range-24hr", time_range_24hr);
+        add_case("time-range-no-meridiem", time_range_no_meridiem);
     }
     
     protected override void setup() throws Error {
@@ -504,27 +507,11 @@ private class QuickAdd : UnitTest.Harness {
             && parser.event.date_span.start_date.year.value == 2014;
     }
     
-    private bool numeric_dash(out string? dump) throws Error {
-        Calendar.System.date_ordering = Calendar.DateOrdering.MDY;
-        Calendar.System.date_separator = "-";
-        Component.DetailsParser parser = new Component.DetailsParser(
-            "7-2-14 Offsite", null);
-        
-        dump = parser.event.source;
-        
-        return parser.event.summary == "Offsite"
-            && parser.event.is_all_day
-            && parser.event.date_span.duration.days == 1
-            && parser.event.date_span.start_date.month == Calendar.Month.JUL
-            && parser.event.date_span.start_date.day_of_month.value == 2
-            && parser.event.date_span.start_date.year.value == 2014;
-    }
-    
     private bool numeric_leading_zeroes(out string? dump) throws Error {
         Calendar.System.date_ordering = Calendar.DateOrdering.MDY;
         Calendar.System.date_separator = "/";
         Component.DetailsParser parser = new Component.DetailsParser(
-            "07-02-14 Offsite", null);
+            "07/02/14 Offsite", null);
         
         dump = parser.event.source;
         
@@ -576,6 +563,41 @@ private class QuickAdd : UnitTest.Harness {
             && parser.event.exact_time_span.start_exact_time.hour == 18
             && parser.event.exact_time_span.start_exact_time.minute == 30;
     }
+    
+    private bool test_time_range(string details, out string? dump) throws Error {
+        Component.DetailsParser parser = new Component.DetailsParser(details, null);
+        
+        dump = parser.event.source;
+        
+        return parser.event.summary == "Opus Affair"
+            && !parser.event.is_all_day
+            && parser.event.exact_time_span.start_exact_time.hour == 18
+            && parser.event.exact_time_span.start_exact_time.minute == 0
+            && parser.event.exact_time_span.end_exact_time.hour == 21
+            && parser.event.exact_time_span.end_exact_time.minute == 0;
+    }
+    
+    private bool time_range_both_meridiem(out string? dump) throws Error {
+        return test_time_range("6p-9p Opus Affair", out dump);
+    }
+    
+    private bool time_range_one_meridiem(out string? dump) throws Error {
+        return test_time_range("6-9p Opus Affair", out dump);
+    }
+    
+    private bool time_range_24hr(out string? dump) throws Error {
+        return test_time_range("18:00-21:00 Opus Affair", out dump);
+    }
+    
+    private bool time_range_no_meridiem(out string? dump) throws Error {
+        Component.DetailsParser parser = new Component.DetailsParser(
+            "6-9 Opus Affair", null);
+        
+        dump = parser.event.source;
+        
+        return parser.event.summary == "6-9 Opus Affair"
+            && !parser.event.is_valid(false);
+    }
 }
 
 }


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