[california/wip/725785-create-recurring] Firm up encoding/decoding BYDAY values w/ tests
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [california/wip/725785-create-recurring] Firm up encoding/decoding BYDAY values w/ tests
- Date: Fri, 20 Jun 2014 23:35:46 +0000 (UTC)
commit 14865b836dd152f27d141f21df2fd7f7381f5422
Author: Jim Nelson <jim yorba org>
Date: Fri Jun 20 13:25:16 2014 -0700
Firm up encoding/decoding BYDAY values w/ tests
src/collection/collection-iterable.vala | 45 ++++++++-
src/component/component-recurrence-rule.vala | 128 +++++++++++++++++---------
src/tests/tests-quick-add-recurring.vala | 109 +++++++++++++++++++++-
3 files changed, 229 insertions(+), 53 deletions(-)
---
diff --git a/src/collection/collection-iterable.vala b/src/collection/collection-iterable.vala
index 1465a49..55863d8 100644
--- a/src/collection/collection-iterable.vala
+++ b/src/collection/collection-iterable.vala
@@ -215,7 +215,11 @@ public class Iterable<G> : Object {
return (Gee.TreeSet<G>) add_all_to(new Gee.TreeSet<G>((owned) compare_func));
}
- public Gee.Map<K, G> add_all_to_map<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
+ /**
+ * Add this { link Iterable}'s values to an existing Gee.Map, with this Iterable's values as
+ * values for the map.
+ */
+ public Gee.Map<K, G> add_to_map_values<K>(Gee.Map<K, G> c, Gee.MapFunc<K, G> key_func) {
while (i.next()) {
G g = i get();
c set(key_func(g), g);
@@ -223,16 +227,49 @@ public class Iterable<G> : Object {
return c;
}
- public Gee.HashMap<K, G> to_hash_map<K>(Gee.MapFunc<K, G> key_func,
+ /**
+ * Add this { link Iterable}'s values to an existing Gee.Map, with this Iterable's values as
+ * keys for the map.
+ *
+ * @see add_to_map_keys
+ */
+ public Gee.Map<G, V> add_to_map_keys<V>(Gee.Map<G, V> map, Gee.MapFunc<V, G> value_func) {
+ while (i.next()) {
+ G g = i.get();
+ map.set(g, value_func(g));
+ }
+
+ return map;
+ }
+
+ /**
+ * Transform the { link Iterable} into a Gee.HashMap, with this Iterable's values as values
+ * for the map.
+ *
+ * @see add_to_map_values
+ */
+ public Gee.HashMap<K, G> to_hash_map_as_values<K>(Gee.MapFunc<K, G> key_func,
owned Gee.HashDataFunc<K>? key_hash_func = null,
owned Gee.EqualDataFunc<K>? key_equal_func = null,
owned Gee.EqualDataFunc<G>? value_equal_func = null) {
- return (Gee.HashMap<K, G>) add_all_to_map<K>(new Gee.HashMap<K, G>(
+ return (Gee.HashMap<K, G>) add_to_map_values<K>(new Gee.HashMap<K, G>(
(owned) key_hash_func, (owned) key_equal_func, (owned) value_equal_func), key_func);
}
/**
- * Convert the { link Iterable} into a plain string.
+ * Transform the { link Iterable} into a Gee.HashMap, with this Iterable's values as keys
+ * for the map.
+ */
+ public Gee.HashMap<G, V> to_hash_map_as_keys<V>(Gee.MapFunc<V, G> value_func,
+ owned Gee.HashDataFunc<G>? key_hash_func = null,
+ owned Gee.EqualDataFunc<G>? key_equal_func = null,
+ owned Gee.EqualDataFunc<V>? value_equal_func = null) {
+ return (Gee.HashMap<G, V>) add_to_map_keys<V>(new Gee.HashMap<G, V>(
+ (owned) key_hash_func, (owned) key_equal_func, (owned) value_equal_func), value_func);
+ }
+
+ /**
+ * Convert the { link Iterable}'s values into a single plain string.
*
* If { link ToString} returns null or an empty string, nothing is appended to the final string.
*
diff --git a/src/component/component-recurrence-rule.vala b/src/component/component-recurrence-rule.vala
index 1ee658a..59b6f6a 100644
--- a/src/component/component-recurrence-rule.vala
+++ b/src/component/component-recurrence-rule.vala
@@ -270,50 +270,19 @@ public class RecurrenceRule : BaseObject {
}
/**
- * Returns a read-only sorted set of BY rule settings for the specified { link ByRule}.
- */
- public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
- switch (by_rule) {
- case ByRule.SECOND:
- return by_second.read_only_view;
-
- case ByRule.MINUTE:
- return by_minute.read_only_view;
-
- case ByRule.HOUR:
- return by_hour.read_only_view;
-
- case ByRule.DAY:
- return by_day.read_only_view;
-
- case ByRule.MONTH_DAY:
- return by_month_day.read_only_view;
-
- case ByRule.YEAR_DAY:
- return by_year_day.read_only_view;
-
- case ByRule.WEEK_NUM:
- return by_week_num.read_only_view;
-
- case ByRule.MONTH:
- return by_month.read_only_view;
-
- case ByRule.SET_POS:
- return by_set_pos.read_only_view;
-
- default:
- assert_not_reached();
- }
- }
-
- /**
- * Encode a { link Calendar.DayOfWeek} and its position (i.e. Second Thursday of the month,
- * last Wednesday of the yar) into a value for { link set_by_rule} when using
+ * Encode a { link Calendar.DayOfWeek} and its position (i.e. second Thursday of the month,
+ * last Wednesday of the year) into a value for { link set_by_rule} when using
* { link ByRule.DAY}.
*
+ * For position, 1 = first, 2 = second, -1 = last, -2 = second to last, etc.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+ * operate according to this RRULE's { link freq}.
+ *
* Use null for DayOfWeek and zero for position to mean "any" or "every".
*
* @see encode_days
+ * @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;
@@ -322,17 +291,45 @@ public class RecurrenceRule : BaseObject {
}
/**
- * Encode a Gee.Map of { link Calendar.DayOfWeek} and its position (i.e. Second Thursday of
- * the month, last Wednesday of the year) into a value for { link set_by_rule} when using
- * { link ByRule.DAY}.
+ * Decode the integer returned by { link get_by_rule} when { link ByRule.DAY} passed in.
+ *
+ * If null is returned for DayOfWeek or zero for position, that indicates "any" or "every".
+ * See { link encode_day} for more information.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+ * operate according to this RRULE's { link freq}.
+ *
+ * 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;
+
+ dow = null;
+ int dow_value = value % 7;
+ 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;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Encode a Gee.Map of { link Calendar.DayOfWeek} and its position into a value for
+ * { link set_by_rule} when using { link ByRule.DAY}.
*
* Use null for DayOfWeek and zero for position to mean "any" or "every".
*
- * @encode_day
+ * @see encode_day
*/
- public static Gee.Collection<int>? encode_days(Gee.Map<Calendar.DayOfWeek?, int>? day_values) {
+ public static Gee.Collection<int> encode_days(Gee.Map<Calendar.DayOfWeek?, int>? day_values) {
if (day_values == null || day_values.size == 0)
- return null;
+ return Gee.Collection.empty<int>();
Gee.Collection<int> encoded = new Gee.ArrayList<int>();
Gee.MapIterator<Calendar.DayOfWeek?, int> iter = day_values.map_iterator();
@@ -342,6 +339,31 @@ public class RecurrenceRule : BaseObject {
return encoded;
}
+ /**
+ * Decode a Gee.Collection of encoded { link ByRule.DAY} values into their positions and
+ * { link Calendar.DayOfWeek}.
+ *
+ * Invalid values are skipped.
+ *
+ * @see encode_day
+ * @see encode_days
+ * @see decode_day
+ */
+ public static Gee.Map<Calendar.DayOfWeek?, int> decode_days(Gee.Collection<int>? values) {
+ if (values == null || values.size == 0)
+ return Gee.Map.empty<Calendar.DayOfWeek?, int>();
+
+ Gee.Map<Calendar.DayOfWeek?, int> decoded = new Gee.HashMap<Calendar.DayOfWeek?, int>();
+ foreach (int value in values) {
+ Calendar.DayOfWeek? dow;
+ int position;
+ if (decode_day(value, out dow, out position))
+ decoded.set(dow, position);
+ }
+
+ return decoded;
+ }
+
private Gee.SortedSet<int> get_by_set(ByRule by_rule) {
switch (by_rule) {
case ByRule.SECOND:
@@ -377,8 +399,21 @@ public class RecurrenceRule : BaseObject {
}
/**
+ * Returns a read-only sorted set of BY rule settings for the specified { link ByRule}.
+ *
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+ * operate according to their associated ByRule and this RRULE's { link freq}.
+ */
+ public Gee.SortedSet<int> get_by_rule(ByRule by_rule) {
+ return get_by_set(by_rule).read_only_view;
+ }
+
+ /**
* Replaces the existing set of values for the BY rules with the supplied values.
*
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+ * operate according to their associated ByRule and this RRULE's { link freq}.
+ *
* Pass null or an empty Collection to clear the by-rules values.
*
* Use { link encode_days} when passing values for { link ByRule.DAY}.
@@ -399,6 +434,9 @@ public class RecurrenceRule : BaseObject {
/**
* Adds the supplied values to the existing set of values for the BY rules.
*
+ * See [[https://tools.ietf.org/html/rfc5545#section-3.3.10]] for information how these values
+ * operate according to their associated ByRule and this RRULE's { link freq}.
+ *
* Null or an empty Collection is a no-op.
*
* Use { link encode_days} when passing values for { link ByRule.DAY}.
diff --git a/src/tests/tests-quick-add-recurring.vala b/src/tests/tests-quick-add-recurring.vala
index c6ff4cd..4273a03 100644
--- a/src/tests/tests-quick-add-recurring.vala
+++ b/src/tests/tests-quick-add-recurring.vala
@@ -8,6 +8,12 @@ namespace California.Tests {
private class QuickAddRecurring : UnitTest.Harness {
public QuickAddRecurring() {
+ // ByRule.DAY encoding/decoding tests
+ add_case("encode-decode-day-every-week", encode_decode_day_every_week);
+ 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);
+
// DAILY tests
add_case("every-day", every_day);
add_case("every-day-10-days", every_day_10_days);
@@ -19,6 +25,7 @@ private class QuickAddRecurring : UnitTest.Harness {
// WEEKLY
add_case("every-tuesday", every_tuesday);
add_case("every-tuesday-thursday", every_tuesday_thursday);
+ add_case("every-tuesday-and-thursday", every_tuesday_and_thursday);
}
protected override void setup() throws Error {
@@ -31,6 +38,61 @@ private class QuickAddRecurring : UnitTest.Harness {
Calendar.terminate();
}
+ private bool encode_decode_day_every_week(out string? dump) throws Error {
+ int value = Component.RecurrenceRule.encode_day(Calendar.DayOfWeek.THU, 0);
+
+ dump = "THU 0 -> %d".printf(value);
+
+ Calendar.DayOfWeek? dow;
+ int position;
+ return Component.RecurrenceRule.decode_day(value, out dow, out position)
+ && dow != null
+ && dow.equal_to(Calendar.DayOfWeek.THU)
+ && position == 0;
+ }
+
+ private bool encode_decode_day_every_month(out string? dump) throws Error {
+ int value = Component.RecurrenceRule.encode_day(Calendar.DayOfWeek.MON, 3);
+
+ dump = "MON 3 -> %d".printf(value);
+
+ Calendar.DayOfWeek? dow;
+ int position;
+ return Component.RecurrenceRule.decode_day(value, out dow, out position)
+ && dow != null
+ && dow.equal_to(Calendar.DayOfWeek.MON)
+ && position == 3;
+ }
+
+ private bool encode_decode_days_every_week(out string? dump) throws Error {
+ Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+ iterate<Calendar.DayOfWeek?>(Calendar.DayOfWeek.TUE,
Calendar.DayOfWeek.THU).to_hash_map_as_keys<int>(dow => 0));
+ Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+
+ dump = "values.size=%d size=%d".printf(values.size, dows.size);
+
+ return dows.size == 2
+ && dows.contains(Calendar.DayOfWeek.TUE)
+ && dows.contains(Calendar.DayOfWeek.THU)
+ && dows[Calendar.DayOfWeek.TUE] == 0
+ && dows[Calendar.DayOfWeek.THU] == 0;
+ }
+
+ private bool encode_decode_days_every_month(out string? dump) throws Error {
+ int iter = 1;
+ Gee.Collection<int> values = Component.RecurrenceRule.encode_days(
+ iterate<Calendar.DayOfWeek?>(Calendar.DayOfWeek.MON,
Calendar.DayOfWeek.WED).to_hash_map_as_keys<int>(dow => iter++));
+ Gee.Map<Calendar.DayOfWeek?, int> dows = Component.RecurrenceRule.decode_days(values);
+
+ dump = "values.size=%d size=%d".printf(values.size, dows.size);
+
+ return dows.size == 2
+ && dows.contains(Calendar.DayOfWeek.MON)
+ && dows.contains(Calendar.DayOfWeek.WED)
+ && dows[Calendar.DayOfWeek.MON] == 1
+ && dows[Calendar.DayOfWeek.WED] == 2;
+ }
+
// Checks that an RRULE was generated,
// the summary is meeting at work
// the location is work
@@ -44,6 +106,7 @@ private class QuickAddRecurring : UnitTest.Harness {
return event.rrule != null
&& event.summary == "meeting at work"
&& event.location == "work"
+ && !event.is_all_day
&& event.exact_time_span.start_exact_time.to_wall_time().equal_to(new Calendar.WallTime(10, 0,
0));
}
@@ -107,25 +170,63 @@ private class QuickAddRecurring : UnitTest.Harness {
// WEEKLY
//
+ private bool check_byrule_day(Component.Event event, Gee.Map<Calendar.DayOfWeek?, int> by_days) {
+ Gee.SortedSet<int> values = event.rrule.get_by_rule(Component.RecurrenceRule.ByRule.DAY);
+ if (values.size != by_days.size)
+ return false;
+
+ foreach (int value in values) {
+ Calendar.DayOfWeek? dow;
+ int position;
+ if (!Component.RecurrenceRule.decode_day(value, out dow, out position))
+ return false;
+
+ if (!by_days.has_key(dow) || by_days.get(dow) != position)
+ return false;
+ }
+
+ return true;
+ }
+
private bool every_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("meeting at work at 10am every tuesday", out event, out dump)
&& event.rrule.is_weekly
&& event.rrule.interval == 1
&& !event.rrule.has_duration
- && !event.is_all_day
- && 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.TUE)
+ && 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);
+
Component.Event event;
return basic("meeting at work at 10am every tuesday, thursday", out event, out dump)
&& event.rrule.is_weekly
&& event.rrule.interval == 1
&& !event.rrule.has_duration
- && !event.is_all_day
&& (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));
+ || 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(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", 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)
+ || event.exact_time_span.start_date.day_of_week.equal_to(Calendar.DayOfWeek.THU))
+ && check_byrule_day(event, by_days);
}
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]