[california/wip/725785-create-recurring] More weekly tests, fixed encoding/decode days (again)
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725785-create-recurring] More weekly tests, fixed encoding/decode days (again)
- Date: Fri, 20 Jun 2014 23:35:51 +0000 (UTC)
commit 6bbea57ac297c142a31cc5b1e773eb618d2d79a0
Author: Jim Nelson <jim yorba org>
Date: Fri Jun 20 15:15:31 2014 -0700
More weekly tests, fixed encoding/decode days (again)
src/calendar/calendar-day-of-week.vala | 27 ++++--
src/collection/collection-iterable.vala | 11 +++
src/component/component-details-parser.vala | 67 +++++++++++++-
src/component/component-recurrence-rule.vala | 33 +++++---
src/component/component.vala | 23 +++++-
src/tests/tests-quick-add-recurring.vala | 125 ++++++++++++++++++++++++++
6 files changed, 259 insertions(+), 27 deletions(-)
---
diff --git a/src/calendar/calendar-day-of-week.vala b/src/calendar/calendar-day-of-week.vala
index ee29481..4fc8500 100644
--- a/src/calendar/calendar-day-of-week.vala
+++ b/src/calendar/calendar-day-of-week.vala
@@ -173,16 +173,7 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
if (index < 0 || index >= COUNT)
throw new CalendarError.INVALID("Invalid day of week value %d", value);
- switch (first_of_week) {
- case FirstOfWeek.MONDAY:
- return days_of_week_monday[index];
-
- case FirstOfWeek.SUNDAY:
- return days_of_week_sunday[index];
-
- default:
- assert_not_reached();
- }
+ return all(first_of_week)[index];
}
/**
@@ -197,6 +188,22 @@ public class DayOfWeek : BaseObject, Gee.Hashable<DayOfWeek> {
}
}
+ /**
+ * Return all { link DayOfWeeks} ordered by the { link FirstOfWeek}.
+ */
+ public static unowned DayOfWeek[] all(FirstOfWeek first_of_week) {
+ switch (first_of_week) {
+ case FirstOfWeek.MONDAY:
+ return days_of_week_monday;
+
+ case FirstOfWeek.SUNDAY:
+ return days_of_week_sunday;
+
+ default:
+ assert_not_reached();
+ }
+ }
+
internal static DayOfWeek from_gdate(GLib.Date date) {
assert(date.valid());
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 55863d8..b551cd2 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -192,6 +192,17 @@ public class Iterable<G> : Object {
return new GeeIterable<G>(i);
}
+ /**
+ * Convert the { link Iterable} into a flat array of elements.
+ */
+ public G[] to_array() {
+ G[] ar = new G[0];
+ while (i.next())
+ ar += i.get();
+
+ return ar;
+ }
+
public Gee.Collection<G> add_all_to(Gee.Collection<G> c) {
while (i.next())
c.add(i get());
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index 4b892c3..ecebe74 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -179,6 +179,18 @@ public class DetailsParser : BaseObject {
continue;
}
+ // if a recurring rule has been started and are adding to it, drop common prepositions
+ // that indicate linkage
+ if (rrule != null && token.casefolded in COMMON_PREPOSITIONS)
+ continue;
+
+ // if a recurring rule has not been started, look for keywords which transform the
+ // event into one
+ stack.mark();
+ if (rrule == null && parse_recurring_indicator(token))
+ continue;
+ stack.restore();
+
// if a recurring rule has been started, attempt to parse into additions for the rule
stack.mark();
if (rrule != null && parse_recurring(token))
@@ -477,6 +489,43 @@ public class DetailsParser : BaseObject {
return null;
}
+ // this can create a new RRULE if the token indicates a one-time event should be recurring
+ private bool parse_recurring_indicator(Token? specifier) {
+ // rrule can't already exist
+ if (rrule != null || specifier == null)
+ return false;
+
+ if (specifier.casefolded == DAILY) {
+ set_rrule_daily(1);
+
+ return true;
+ }
+
+ if (specifier.casefolded == WEEKLY) {
+ // set the start date to today unless already known
+ if (start_date == null)
+ start_date = Calendar.System.today;
+
+ set_rrule_weekly(iterate<Calendar.DayOfWeek>(start_date.day_of_week).to_array(), 1);
+
+ return true;
+ }
+
+ if (specifier.casefolded in UNIT_WEEKDAYS) {
+ set_rrule_weekly(Calendar.DayOfWeek.weekdays, 1);
+
+ return true;
+ }
+
+ if (specifier.casefolded in UNIT_WEEKENDS) {
+ set_rrule_weekly(Calendar.DayOfWeek.weekend_days, 1);
+
+ return true;
+ }
+
+ return false;
+ }
+
// this can create a new RRULE or edit an existing one, but will not create multiple RRULEs
// for the same VEVENT
private bool parse_recurring(Token? specifier) {
@@ -509,10 +558,7 @@ public class DetailsParser : BaseObject {
// "day"
if (unit.casefolded in UNIT_DAYS) {
- 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_daily(interval);
return true;
}
@@ -554,6 +600,19 @@ public class DetailsParser : BaseObject {
return false;
}
+ private void set_rrule_daily(int interval) {
+ if (rrule != null)
+ return;
+
+ // no start_date at this point, then today is it
+ 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();
+ }
+
private void set_rrule_weekly(Calendar.DayOfWeek[]? by_days, int interval) {
Gee.Map<Calendar.DayOfWeek?, int> map = new Gee.HashMap<Calendar.DayOfWeek?, int>();
if (by_days != null) {
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 59b6f6a..289a4b3 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -285,9 +285,13 @@ public class RecurrenceRule : BaseObject {
* @see decode_day
*/
public static int encode_day(Calendar.DayOfWeek? dow, int position) {
- int dow_value = (dow != null) ? dow.ordinal(Calendar.System.first_of_week) : 0;
+ // these encodings are mapped to iCal.icalrecurrencetype_weekday, which is SUNDAY-based
+ int dow_value = (dow != null) ? dow.ordinal(Calendar.FirstOfWeek.SUNDAY) : 0;
- return (position.clamp(0, int.MAX) * 7) + dow_value;
+ position = position.clamp(short.MIN, short.MAX);
+ int value = (position * 8) + (position >= 0 ? dow_value : 0 - dow_value);
+
+ return value;
}
/**
@@ -302,17 +306,16 @@ public class RecurrenceRule : BaseObject {
* Returns false if the supplied value is definitely not encoded correctly.
*/
public static bool decode_day(int value, out Calendar.DayOfWeek? dow, out int position) {
- position = value / 7;
+ position = iCal.icalrecurrencetype.day_position((short) value);
dow = null;
- int dow_value = value % 7;
+ int dow_value = (int) iCal.icalrecurrencetype.day_day_of_week((short) value);
if (dow_value != 0) {
try {
- dow = Calendar.DayOfWeek.for(dow_value, Calendar.System.first_of_week);
- } catch (CalendarError cal_err) {
- debug("Unable to decode %d: %d is invalid day of week", value, dow_value);
-
- return false;
+ // iCal.icalrecurrencetype_weekday is SUNDAY-based
+ dow = Calendar.DayOfWeek.for(dow_value, Calendar.FirstOfWeek.SUNDAY);
+ } catch (CalendarError calerr) {
+ debug("Unable to decode day of week value %d: %s", dow_value, calerr.message);
}
}
@@ -408,6 +411,10 @@ public class RecurrenceRule : BaseObject {
return get_by_set(by_rule).read_only_view;
}
+ private bool is_int_short(int value) {
+ return value >= short.MIN && value <= short.MAX;
+ }
+
/**
* Replaces the existing set of values for the BY rules with the supplied values.
*
@@ -416,6 +423,8 @@ public class RecurrenceRule : BaseObject {
*
* Pass null or an empty Collection to clear the by-rules values.
*
+ * Any value greater than short.MAX or less than short.MIN will be dropped.
+ *
* Use { link encode_days} when passing values for { link ByRule.DAY}.
*
* @see add_by_rule
@@ -426,7 +435,7 @@ public class RecurrenceRule : BaseObject {
by_set.clear();
if (values != null && values.size > 0)
- by_set.add_all(values);
+ by_set.add_all(traverse<int>(values).filter(is_int_short).to_array_list());
by_rule_updated(by_rule);
}
@@ -439,6 +448,8 @@ public class RecurrenceRule : BaseObject {
*
* Null or an empty Collection is a no-op.
*
+ * Any value greater than short.MAX or less than short.MIN will be dropped.
+ *
* Use { link encode_days} when passing values for { link ByRule.DAY}.
*
* @see set_by_rule
@@ -448,7 +459,7 @@ public class RecurrenceRule : BaseObject {
Gee.SortedSet<int> by_set = get_by_set(by_rule);
if (values != null && values.size > 0)
- by_set.add_all(values);
+ by_set.add_all(traverse<int>(values).filter(is_int_short).to_array_list());
by_rule_updated(by_rule);
}
diff --git a/src/component/component.vala b/src/component/component.vala
index 7bac566..23f49b2 100644
--- a/src/component/component.vala
+++ b/src/component/component.vala
@@ -20,6 +20,8 @@ private int init_count = 0;
private string TODAY;
private string TOMORROW;
private string YESTERDAY;
+private string DAILY;
+private string WEEKLY;
private string[] UNIT_WEEKDAYS;
private string[] UNIT_WEEKENDS;
private string[] UNIT_YEARS;
@@ -28,6 +30,7 @@ private string[] UNIT_WEEKS;
private string[] UNIT_DAYS;
private string[] UNIT_HOURS;
private string[] UNIT_MINS;
+private string[] COMMON_PREPOSITIONS;
private string[] TIME_PREPOSITIONS;
private string[] LOCATION_PREPOSITIONS;
private string[] DURATION_PREPOSITIONS;
@@ -55,6 +58,14 @@ 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 a daily recurring event
+ // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+ DAILY = _("daily").casefold();
+
+ // Used by quick-add to indicate the user wants to create a weekly recurring event
+ // 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 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
@@ -99,6 +110,14 @@ public void init() throws Error {
// For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
UNIT_MINS = _("minute;minutes;min;mins").casefold().split(";");
+ // Used by quick-add to determine if the word is a COMMON preposition (indicating linkage or a
+ // connection). Each word must be separate by semi-colons.
+ // These words should not be duplicated in another other preposition list.
+ // This list can be empty but that will limit the parser or cause unexpected results.
+ // Examples: "wednesday and thursday", "monday or friday"
+ // For more information see https://wiki.gnome.org/Apps/California/TranslatingQuickAdd
+ COMMON_PREPOSITIONS = _("and;or;").casefold().split(";");
+
// 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
@@ -158,8 +177,8 @@ public void terminate() {
return;
TIME_PREPOSITIONS = LOCATION_PREPOSITIONS = DURATION_PREPOSITIONS = ORDINAL_SUFFIXES = null;
- DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
- TODAY = TOMORROW = YESTERDAY = null;
+ COMMON_PREPOSITIONS = DELAY_PREPOSITIONS = RECURRING_PREPOSITIONS = null;
+ TODAY = TOMORROW = YESTERDAY = DAILY = WEEKLY = 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 4273a03..8478dca 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -13,9 +13,12 @@ private class QuickAddRecurring : UnitTest.Harness {
add_case("encode-decode-day-every-month", encode_decode_day_every_month);
add_case("encode-decode-days-every-week", encode_decode_days_every_week);
add_case("encode-decode-days-every-month", encode_decode_days_every_month);
+ add_case("encode-decode-all", encode_decode_all);
+ add_case("encode-decode-all_negative", encode_decode_all_negative);
// DAILY tests
add_case("every-day", every_day);
+ add_case("daily", daily);
add_case("every-day-10-days", every_day_10_days);
add_case("every-2-days", every_2_days);
add_case("every-3rd-day", every_3rd_day);
@@ -24,8 +27,13 @@ private class QuickAddRecurring : UnitTest.Harness {
// WEEKLY
add_case("every-tuesday", every_tuesday);
+ add_case("tuesday_weekly", tuesday_weekly);
+ add_case("weekdays_to_1pm", weekdays_to_1pm);
+ add_case("weekends", weekends);
+ add_case("every_weekend", every_weekend);
add_case("every-tuesday-thursday", every_tuesday_thursday);
add_case("every-tuesday-and-thursday", every_tuesday_and_thursday);
+ add_case("every-tuesday-and-thursday-for-3-weeks", every_tuesday_and_thursday_for_3_weeks);
}
protected override void setup() throws Error {
@@ -93,6 +101,46 @@ private class QuickAddRecurring : UnitTest.Harness {
&& dows[Calendar.DayOfWeek.WED] == 2;
}
+ private bool encode_decode_all() throws Error {
+ Gee.Collection<Calendar.DayOfWeek> all =
+
from_array<Calendar.DayOfWeek?>(Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)).to_array_list();
+
+ int iter = 0;
+ Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+ traverse<Calendar.DayOfWeek>(all).to_hash_map_as_keys<int>(dow => iter++));
+ Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+
+ return dows.size == 7
+ && dows.has_key(Calendar.DayOfWeek.SUN)
+ && dows[Calendar.DayOfWeek.SUN] == 0
+ && dows[Calendar.DayOfWeek.MON] == 1
+ && dows[Calendar.DayOfWeek.TUE] == 2
+ && dows[Calendar.DayOfWeek.WED] == 3
+ && dows[Calendar.DayOfWeek.THU] == 4
+ && dows[Calendar.DayOfWeek.FRI] == 5
+ && dows[Calendar.DayOfWeek.SAT] == 6;
+ }
+
+ private bool encode_decode_all_negative() throws Error {
+ Gee.Collection<Calendar.DayOfWeek> all =
+
from_array<Calendar.DayOfWeek?>(Calendar.DayOfWeek.all(Calendar.FirstOfWeek.SUNDAY)).to_array_list();
+
+ int iter = -1;
+ Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+ traverse<Calendar.DayOfWeek>(all).to_hash_map_as_keys<int>(dow => iter--));
+ Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+
+ return dows.size == 7
+ && dows.has_key(Calendar.DayOfWeek.SUN)
+ && dows[Calendar.DayOfWeek.SUN] == -1
+ && dows[Calendar.DayOfWeek.MON] == -2
+ && dows[Calendar.DayOfWeek.TUE] == -3
+ && dows[Calendar.DayOfWeek.WED] == -4
+ && dows[Calendar.DayOfWeek.THU] == -5
+ && dows[Calendar.DayOfWeek.FRI] == -6
+ && dows[Calendar.DayOfWeek.SAT] == -7;
+ }
+
// Checks that an RRULE was generated,
// the summary is meeting at work
// the location is work
@@ -122,6 +170,14 @@ private class QuickAddRecurring : UnitTest.Harness {
&& !event.rrule.has_duration;
}
+ private bool daily(out string? dump) throws Error {
+ Component.Event event;
+ return basic("meeting at work daily at 10am", out event, out dump)
+ && event.rrule.is_daily
+ && event.rrule.interval == 1
+ && !event.rrule.has_duration;
+ }
+
private bool every_day_10_days(out string? dump) throws Error {
Component.Event event;
return basic("meeting at work every day at 10am for 10 days", out event, out dump)
@@ -181,6 +237,8 @@ private class QuickAddRecurring : UnitTest.Harness {
if (!Component.RecurrenceRule.decode_day(value, out dow, out position))
return false;
+ debug("value %d -> %s %d", value, (dow != null) ? dow.to_string() : "null", position);
+
if (!by_days.has_key(dow) || by_days.get(dow) != position)
return false;
}
@@ -201,6 +259,59 @@ private class QuickAddRecurring : UnitTest.Harness {
&& 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);
+
+ Component.Event event;
+ return basic("meeting at work tuesday 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.TUE)
+ && 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);
+
+ Component.Event event;
+ return basic("meeting at work weekdays from 10am to 1pm", out event, out dump)
+ && event.rrule.is_weekly
+ && event.rrule.interval == 1
+ && !event.rrule.has_duration
+ && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+ && event.exact_time_span.end_exact_time.to_wall_time().equal_to(new Calendar.WallTime(13, 0, 0))
+ && check_byrule_day(event, by_days);
+ }
+
+ private bool weekends(out string? dump) throws Error {
+ Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
+ Calendar.DayOfWeek.weekend_days).to_hash_map_as_keys<int>(dow => 0);
+
+ Component.Event event;
+ return basic("meeting weekends at work at 10am", out event, out dump)
+ && event.rrule.is_weekly
+ && event.rrule.interval == 1
+ && !event.rrule.has_duration
+ && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+ && check_byrule_day(event, by_days);
+ }
+
+ private bool every_weekend(out string? dump) throws Error {
+ Gee.Map<Calendar.DayOfWeek?, int> by_days = from_array<Calendar.DayOfWeek?>(
+ Calendar.DayOfWeek.weekend_days).to_hash_map_as_keys<int>(dow => 0);
+
+ Component.Event event;
+ return basic("meeting at work every weekend at 10am", out event, out dump)
+ && event.rrule.is_weekly
+ && event.rrule.interval == 1
+ && !event.rrule.has_duration
+ && by_days.keys.contains(event.exact_time_span.start_date.day_of_week)
+ && check_byrule_day(event, by_days);
+ }
+
private bool every_tuesday_thursday(out string? dump) throws Error {
Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
@@ -228,6 +339,20 @@ private class QuickAddRecurring : UnitTest.Harness {
|| event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
&& check_byrule_day(event, by_days);
}
+
+ private bool every_tuesday_and_thursday_for_3_weeks(out string? dump) throws Error {
+ Gee.Map<Calendar.DayOfWeek?, int> by_days = iterate<Calendar.DayOfWeek?>(
+ Calendar.DayOfWeek.TUE, Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0);
+
+ Component.Event event;
+ return basic("meeting at work at 10am every tuesday and thursday for 3 weeks", out event, out dump)
+ && event.rrule.is_weekly
+ && event.rrule.interval == 1
+ && (event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.TUE)
+ || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
+ && check_byrule_day(event, by_days)
+ && event.rrule.count == 3;
+ }
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]