[california/wip/725767-week: 4/5] Revise Calendar class heirarchy



commit de3486b138ffabb5a14fa685b1b59e7bc6d56fb0
Author: Jim Nelson <jim yorba org>
Date:   Fri May 9 14:49:34 2014 -0700

    Revise Calendar class heirarchy
    
    Too many interfaces were being used to describe spans of time, when
    it's far better (and more useful) to describe all dates as spans and
    provide more specific functions for more specific or well-recognized
    spans (like months and years).  This cleans things up immensely and
    pushes common functionality into base classes.  It also means all date
    spans have the same interface, so things like iteration and checking
    for ranges and containment are uniform.

 src/Makefile.am                             |    2 +
 src/calendar/calendar-date-span.vala        |  185 ++++-----------------------
 src/calendar/calendar-date.vala             |   74 +++++------
 src/calendar/calendar-month-of-year.vala    |   37 +++---
 src/calendar/calendar-month-span.vala       |  128 +++----------------
 src/calendar/calendar-span.vala             |  178 +++++++++++++++++++++-----
 src/calendar/calendar-unit-span.vala        |  119 +++++++++++++++++
 src/calendar/calendar-unit.vala             |  113 ++++++++++++++---
 src/calendar/calendar-week-span.vala        |  135 ++++---------------
 src/calendar/calendar-week.vala             |   47 ++++++--
 src/calendar/calendar-year-span.vala        |   56 ++++++++
 src/calendar/calendar-year.vala             |   30 ++++-
 src/calendar/calendar.vala                  |   25 ++++
 src/component/component-date-time.vala      |    2 +-
 src/component/component-details-parser.vala |    4 +-
 src/component/component-instance.vala       |    2 +-
 src/host/host-main-window.vala              |    2 +-
 src/tests/tests-calendar-date.vala          |   10 +-
 src/tests/tests-calendar-month-span.vala    |   30 +++--
 src/tests/tests-quick-add.vala              |    2 +-
 src/view/month/month-cell.vala              |    2 +-
 src/view/month/month-controller.vala        |    6 +-
 src/view/month/month-grid.vala              |    2 +-
 src/view/view-controllable.vala             |    2 +-
 24 files changed, 669 insertions(+), 524 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index 30fa900..313d7ef 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -65,10 +65,12 @@ california_VALASOURCES = \
        calendar/calendar-system.vala \
        calendar/calendar-timezone.vala \
        calendar/calendar-unit.vala \
+       calendar/calendar-unit-span.vala \
        calendar/calendar-wall-time.vala \
        calendar/calendar-week.vala \
        calendar/calendar-week-span.vala \
        calendar/calendar-year.vala \
+       calendar/calendar-year-span.vala \
        \
        collection/collection.vala \
        collection/collection-iterable.vala \
diff --git a/src/calendar/calendar-date-span.vala b/src/calendar/calendar-date-span.vala
index b9794bd..ac13827 100644
--- a/src/calendar/calendar-date-span.vala
+++ b/src/calendar/calendar-date-span.vala
@@ -7,152 +7,39 @@
 namespace California.Calendar {
 
 /**
- * Represents an immutable span of consecutive { link Date}s.
+ * Represents an immutable arbitrary span of consecutive { link Date}s.
  *
- * A DateSpan may be naturally iterated over its { link Date}s.  It also provides iterators for
- * { link Week}s.
+ * Although DateSpan is technically a { link UnitSpan} of Dates, it's such a fundamental way of
+ * representing any unit of dates that { link Span} offers a conversion method.  Thus, a
+ * { link Week} is a { link DiscreteUnit} but it can easily be converted into a { link DateSpan}.
+ * The only reason Week does not inherit from DateSpan -- indeed, all units of calendar time don't
+ * inherit from DateSpan -- is to avoid inheritance problems and circularities.  (Another way to
+ * look at things is that Span is a lightweight DateSpan.)
  */
 
-public class DateSpan : BaseObject, Collection.SimpleIterable<Date>, Span<Date>, Gee.Comparable<DateSpan>,
-    Gee.Hashable<DateSpan> {
-    private class DateSpanIterator : BaseObject, Collection.SimpleIterator<Date> {
-        public Date first;
-        public Date last;
-        public Date? current = null;
-        
-        public DateSpanIterator(DateSpan owner) {
-            first = owner.start_date;
-            last = owner.end_date;
-        }
-        
-        public new Date get() {
-            return current;
-        }
-        
-        public bool next() {
-            if (current == null)
-                current = first;
-            else if (current.compare_to(last) < 0)
-                current = current.adjust(1, DateUnit.DAY);
-            else
-                return false;
-            
-            return true;
-        }
-        
-        public override string to_string() {
-            return "DateSpanIterator %s::%s".printf(first.to_string(), last.to_string());
-        }
-    }
-    
-    /**
-     * @inheritDoc
-     */
-    private Date _start_date;
-    public Date start_date { owned get { return _start_date; } }
-    
-    /**
-     * @inheritDoc
-     */
-    private Date _end_date;
-    public Date end_date { owned get { return _end_date; } }
-    
-    /**
-     * Convenience property indicating if the { link DateSpan} spans only one day.
-     */
-    public bool is_same_day { get { return start_date.equal_to(end_date); } }
-    
-    /**
-     * Returns the { link Duration} this { link DateSpan} represents.
-     */
-    public Duration duration { owned get { return new Duration(end_date.difference(start_date)); } }
-    
+public class DateSpan : UnitSpan<Date> {
     /**
      * Create a { link DateSpan} with the specified start and end dates.
-     *
-     * DateSpan will arrange the two dates so start_date is chronologically earlier (or the same
-     * as) the end_date.
      */
     public DateSpan(Date start_date, Date end_date) {
-        init_span(start_date, end_date);
-    }
-    
-    /**
-     * Create a { link DateSpan} from the { link ExactTimeSpan}.
-     */
-    public DateSpan.from_exact_time_span(ExactTimeSpan exact_time_span) {
-        init_span(new Date.from_exact_time(exact_time_span.start_exact_time),
-            new Date.from_exact_time(exact_time_span.end_exact_time));
-    }
-    
-    /**
-     * Create an unintialized { link DateSpan).
-     *
-     * Because it's sometimes inconvenient to generate the necessary { link Date}s until the
-     * subclass's constructor completes, DateSpan allows for itself to be created empty assuming
-     * that the subclass will call { link init_span} as soon as it's finished initializing.
-     *
-     * init_span() must be called.  DateSpan will not function properly when uninitialized.
-     */
-    protected DateSpan.uninitialized() {
-    }
-    
-    /**
-     * Initialize the { link DateSpan} with s start and end date.
-     *
-     * DateSpan will sort the start and end to ensure that start is chronologically prior
-     * to end.
-     */
-    protected void init_span(Date start_date, Date end_date) {
-        if (start_date.compare_to(end_date) <= 0) {
-            _start_date = start_date;
-            _end_date = end_date;
-        } else {
-            _start_date = end_date;
-            _end_date = start_date;
-        }
-    }
-    
-    /**
-     * @inheritDoc
-     */
-    public Date start() {
-        return _start_date;
+        base (start_date, end_date, start_date, end_date);
     }
     
     /**
-     * @inheritDoc
+     * Create a { link DateSpan} from a { link Span}.
      */
-    public Date end() {
-        return _end_date;
+    public DateSpan.from_span(Span span) {
+        base (span.start_date, span.end_date, span.start_date, span.end_date);
     }
     
     /**
-     * @inheritDoc
-     */
-    public bool contains(Date date) {
-        return (start_date.compare_to(date) <= 0) && (end_date.compare_to(date) >= 0);
-    }
-    
-    /**
-     * @inheritDoc
-     */
-    public bool has(Date date) {
-        return contains(date);
-    }
-    
-    /**
-     * Returns an Iterator for all { link Date}s in the { link DateSpan}.
-     */
-    public Collection.SimpleIterator<Date> iterator() {
-        return new DateSpanIterator(this);
-    }
-    
-    /**
-     * Returns a { link WeekSpan} for each { link Week} (full and partial) in the { link DateSpan}.
+     * Create a { link DateSpan} from the { link ExactTimeSpan}.
      */
-    public WeekSpan weeks(FirstOfWeek first_of_week) {
-        return new WeekSpan(this, first_of_week);
+    public DateSpan.from_exact_time_span(ExactTimeSpan exact_time_span) {
+        Date start_date = new Date.from_exact_time(exact_time_span.start_exact_time);
+        Date end_date = new Date.from_exact_time(exact_time_span.end_exact_time);
+        
+        base(start_date, end_date, start_date, end_date);
     }
     
     /**
@@ -167,7 +54,7 @@ public class DateSpan : BaseObject, Collection.SimpleIterable<Date>, Span<Date>,
     public DateSpan adjust_start_date(Calendar.Date new_start_date) {
         int diff = start_date.difference(end_date);
         
-        return new DateSpan(new_start_date, new_start_date.adjust(diff, DateUnit.DAY));
+        return new DateSpan(new_start_date, new_start_date.adjust(diff));
     }
     
     /**
@@ -182,40 +69,14 @@ public class DateSpan : BaseObject, Collection.SimpleIterable<Date>, Span<Date>,
     public DateSpan adjust_end_date(Calendar.Date new_end_date) {
         int diff = end_date.difference(start_date);
         
-        return new DateSpan(new_end_date.adjust(diff, DateUnit.DAY), new_end_date);
-    }
-    
-    /**
-     * Returns a { link DateSpan} with starting and ending points within the boundary specified
-     * (inclusive).
-     *
-     * If this DateSpan is within the clamped dates, this object may be returned.
-     *
-     * This method will not expand a DateSpan to meet the clamp range.
-     */
-    public DateSpan clamp(DateSpan span) {
-        Date new_start = (start_date.compare_to(span.start_date) < 0) ? span.start_date : start_date;
-        Date new_end = (end_date.compare_to(span.end_date) > 0) ? span.end_date : end_date;
-        
-        return new DateSpan(new_start, new_end);
+        return new DateSpan(new_end_date.adjust(diff), new_end_date);
     }
     
     /**
-     * Compares two { link DateSpan}s by their { link start_date}.
+     * @inheritDoc
      */
-    public int compare_to(DateSpan other) {
-        return start_date.compare_to(other.start_date);
-    }
-    
-    public bool equal_to(DateSpan other) {
-        if (this == other)
-            return true;
-        
-        return start_date.equal_to(other.start_date) && end_date.equal_to(other.end_date);
-    }
-    
-    public uint hash() {
-        return start_date.hash() ^ end_date.hash();
+    public override bool contains(Date date) {
+        return has_date(date);
     }
     
     public override string to_string() {
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index 5ba97ba..0644d02 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -13,14 +13,19 @@ namespace California.Calendar {
  * this class is immutable.  This means this object is incapable of representing a DMY prior to
  * Year 1 (BCE).
  *
- * GLib.Date has many powerful features for representing a calenday day, but it's interface is
+ * GLib.Date has many powerful features for representing a calendar day, but it's interface is
  * inconvenient when working in Vala.  It can also exist in an uninitialized and an invalid
  * state.  It's desired to avoid both of those.  It is also not an Object, has no signals or
  * properties, doesn't work well with Gee, and is mutable.  This class attempts to solve these
  * issues.
  */
 
-public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
+public class Date : Unit<Date>, Gee.Comparable<Date>, Gee.Hashable<Date> {
+    public const string PROP_DAY_OF_WEEK = "day-of-week";
+    public const string PROP_DAY_OF_MONTH = "day-of-month";
+    public const string PROP_MONTH = "month";
+    public const string PROP_YEAR = "year";
+    
     /**
      * Options for { link to_pretty_string}.
      */
@@ -42,6 +47,21 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
         NO_TODAY
     }
     
+    
+    /**
+     * @inheritDoc
+     *
+     * Overridden to prevent a reference cycle in { link Span.start_date}.
+     */
+    public override Date start_date { get { return this; } }
+    
+    /**
+     * @inheritDoc
+     *
+     * Overridden to prevent a reference cycle in { link Span.end_date}.
+     */
+    public override Date end_date { get { return this; } }
+    
     public DayOfWeek day_of_week { get; private set; }
     public DayOfMonth day_of_month { get; private set; }
     public Month month { get; private set; }
@@ -55,6 +75,8 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
      * @throws CalendarError if an invalid calendar day
      */
     public Date(DayOfMonth day_of_month, Month month, Year year) throws CalendarError {
+        base.uninitialized(DateUnit.DAY);
+        
         gdate.set_dmy(day_of_month.to_date_day(), month.to_date_month(), year.to_date_year());
         if (!gdate.valid()) {
             throw new CalendarError.INVALID("Invalid day/month/year %s/%s/%s", day_of_month.to_string(),
@@ -71,6 +93,8 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
      * Creates a { link Date} for the { link ExactTime}.
      */
     public Date.from_exact_time(ExactTime exact_time) {
+        base.uninitialized(DateUnit.DAY);
+        
         // Can use for_checked() methods because ExactTime can only be created with proper values
         day_of_month = exact_time.day_of_month;
         month = exact_time.month;
@@ -90,6 +114,8 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
     }
     
     internal Date.from_gdate(GLib.Date gdate) {
+        base.uninitialized(DateUnit.DAY);
+        
         assert(gdate.valid());
         
         this.gdate = gdate;
@@ -118,7 +144,7 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
         }
         
         // add six days and that's the last day of the week
-        Date end = start.adjust(DayOfWeek.COUNT - 1, DateUnit.DAY);
+        Date end = start.adjust_by(DayOfWeek.COUNT - 1, DateUnit.DAY);
         
         // get the numeric week of the year of this date
         int week_of_year;
@@ -165,24 +191,10 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
     }
     
     /**
-     * Returns the { link Date} as the earliest ExactTime possible for the specified { link Timezone}.
-     *
-     * @see latest_exact_time
+     * @inheritDoc
      */
-    public ExactTime earliest_exact_time(Timezone tz) {
-        return new ExactTime(tz, this, WallTime.earliest);
-    }
-    
-    /**
-     * Returns the { link Date} as the latest { link ExactTime} possible for the specified
-     * { link Timezone}.
-     *
-     * By latest, the precision of ExactTime.seconds will be 59.0.
-     *
-     * @see earliest_exact_time
-     */
-    public ExactTime latest_exact_time(Timezone tz) {
-        return new ExactTime(tz, this, WallTime.latest);
+    public override Date adjust(int quantity) {
+        return adjust_by(quantity, DateUnit.DAY);
     }
     
     /**
@@ -190,7 +202,7 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
      *
      * Subtraction (adjusting to a past date) is acheived by using a negative quantity.
      */
-    public Date adjust(int quantity, DateUnit unit) {
+    public Date adjust_by(int quantity, DateUnit unit) {
         if (quantity == 0)
             return this;
         
@@ -232,11 +244,9 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
     }
     
     /**
-     * Returns the difference (in days) between this { link Date} and another Date.
-     *
-     * If the supplied Date is earlier than this one, a negative value will be returned.
+     * @inheritDoc
      */
-    public int difference(Date other) {
+    public override int difference(Date other) {
         return (this != other) ? gdate.days_between(other.gdate) : 0;
     }
     
@@ -250,20 +260,6 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
         return new Date.from_gdate(clone);
     }
     
-    /**
-     * Returns the next date;
-     */
-    public Date next() {
-        return adjust(1, DateUnit.DAY);
-    }
-    
-    /**
-     * Returns the previous date.
-     */
-    public Date previous() {
-        return adjust(-1, DateUnit.DAY);
-    }
-    
     public int compare_to(Date other) {
         return (this != other) ? gdate.compare(other.gdate) : 0;
     }
diff --git a/src/calendar/calendar-month-of-year.vala b/src/calendar/calendar-month-of-year.vala
index 7425e3b..da16504 100644
--- a/src/calendar/calendar-month-of-year.vala
+++ b/src/calendar/calendar-month-of-year.vala
@@ -10,7 +10,7 @@ namespace California.Calendar {
  * An immutable representation of a { link Month} of a { link Year}.
  */
 
-public class MonthOfYear : DateSpan {
+public class MonthOfYear : Unit<MonthOfYear>, Gee.Comparable<MonthOfYear>, Gee.Hashable<MonthOfYear> {
     /**
      * The { link Month} of the associated { link Year}.
      */
@@ -37,7 +37,7 @@ public class MonthOfYear : DateSpan {
     public string abbrev_name { get; private set; }
     
     public MonthOfYear(Month month, Year year) {
-        base.uninitialized();
+        base.uninitialized(DateUnit.MONTH);
         
         this.month = month;
         this.year = year;
@@ -76,20 +76,16 @@ public class MonthOfYear : DateSpan {
     }
     
     /**
-     * Returns a { link MonthOfYear} adjusted a quantity of months from this one.
-     *
-     * Subtraction (adjusting to a past date) is acheived by using a negative quantity.
+     * @inheritDoc
      */
-    public MonthOfYear adjust(int quantity) {
-        return start_date.adjust(quantity, DateUnit.MONTH).month_of_year();
+    public override MonthOfYear adjust(int quantity) {
+        return start_date.adjust_by(quantity, DateUnit.MONTH).month_of_year();
     }
     
     /**
-     * Returns the number of months between the two { link MonthOfYear}s.
-     *
-     * If the supplied MonthOfYear is earlier than this one, a negative value is returned.
+     * @inheritDoc
      */
-    public int difference(MonthOfYear other) {
+    public override int difference(MonthOfYear other) {
         int compare = compare_to(other);
         if (compare == 0)
             return 0;
@@ -106,18 +102,17 @@ public class MonthOfYear : DateSpan {
         }
     }
     
-    /**
-     * Returns the chronological next { link MonthOfYear}.
-     */
-    public MonthOfYear next() {
-        return adjust(1);
+    public int compare_to(MonthOfYear other) {
+        return (this != other) ? start_date.compare_to(other.start_date) : 0;
     }
     
-    /**
-     * Returns the chronological prior { link MonthOfYear}.
-     */
-    public MonthOfYear previous() {
-        return adjust(-1);
+    public bool equal_to(MonthOfYear other) {
+        return compare_to(other) == 0;
+    }
+    
+    public uint hash() {
+        // 4 bits for month (1 - 12)
+        return (year.value << 4) | month.value;
     }
     
     public override string to_string() {
diff --git a/src/calendar/calendar-month-span.vala b/src/calendar/calendar-month-span.vala
index 506dbc8..1d81db1 100644
--- a/src/calendar/calendar-month-span.vala
+++ b/src/calendar/calendar-month-span.vala
@@ -8,135 +8,47 @@ namespace California.Calendar {
 
 /**
  * An immutable representation of a span of { link MonthOfYear}s.
- *
- * This class provides methods that can turn a { link DateSpan} an iteration of MonthOfYear objects.
- * Partial months are included; the caller needs to do their own clamping if they want to avoid
- * days outside of the DateSpan.
  */
 
-public class MonthSpan : BaseObject, Collection.SimpleIterable<MonthOfYear>, Span<MonthOfYear>,
-    Gee.Comparable<MonthSpan>, Gee.Hashable<MonthSpan> {
-    private class MonthSpanIterator : BaseObject, Collection.SimpleIterator<MonthOfYear> {
-        public MonthOfYear first;
-        public MonthOfYear last;
-        public MonthOfYear? current = null;
-        
-        public MonthSpanIterator(MonthSpan owner) {
-            first = owner.start();
-            last = owner.end();
-        }
-        
-        public new MonthOfYear get() {
-            return current;
-        }
-        
-        public bool next() {
-            if (current == null)
-                current = first;
-            else if (current.start_date.compare_to(last.start_date) < 0)
-                current = current.adjust(1);
-            else
-                return false;
-            
-            return true;
-        }
-        
-        public override string to_string() {
-            return "MonthSpanIterator %s::%s".printf(first.to_string(), last.to_string());
-        }
-    }
-    
-    /**
-     * The { link DateSpan} of the { link MonthOfYear}s.
-     */
-    public DateSpan dates { get; private set; }
-    
-    /**
-     * inheritDoc
-     */
-    public Date start_date { owned get { return dates.start_date; } }
-    
-    /**
-     * inheritDoc
-     */
-    public Date end_date { owned get { return dates.end_date; } }
-    
-    /**
-     * Create a span of { link MonthOfYear}s corresponding to the { link DateSpan}.
-     */
-    public MonthSpan(DateSpan dates) {
-        this.dates = dates;
-    }
-    
+public class MonthSpan : UnitSpan<MonthOfYear> {
     /**
      * Create a span of { link MonthOfYear}s corresponding to the start and end months.
      */
-    public MonthSpan.from_months(MonthOfYear start, MonthOfYear end) {
-        dates = new DateSpan(start.start_date, end.end_date);
-    }
-    
-    /**
-     * Create an arbitrary span of { link MonthOfYear}s starting from the specified starting month.
-     */
-    public MonthSpan.count(MonthOfYear start, int count) {
-        dates = new DateSpan(start.start_date, start.adjust(count).end_date);
-    }
-    
-    /**
-     * inheritDoc
-     */
-    public MonthOfYear start() {
-        return dates.start_date.month_of_year();
+    public MonthSpan(MonthOfYear first, MonthOfYear last) {
+        base (first, last, first.start_date, last.end_date);
     }
     
     /**
-     * inheritDoc
+     * Create a span of { link MonthOfYear}s starting from the specified starting month.
      */
-    public MonthOfYear end() {
-        return dates.end_date.month_of_year();
+    public MonthSpan.count(MonthOfYear first, int count) {
+        MonthOfYear last = first.adjust(count);
+        
+        base (first, last, first.start_date, last.end_date);
     }
     
     /**
-     * @inheritDoc
+     * Create a span of { link MonthOfYear}s from the start and end of a { link Span}.
+     *
+     * The month of the Span's start_date and the month of Span's end_date are used to determine
+     * the MonthSpan.
      */
-    public bool contains(Date date) {
-        return dates.contains(date);
+    public MonthSpan.from_span(Span span) {
+        MonthOfYear first = span.start_date.month_of_year();
+        MonthOfYear last = span.end_date.month_of_year();
+        
+        base (first, last, first.start_date, last.end_date);
     }
     
     /**
      * @inheritDoc
      */
-    public bool has(MonthOfYear month) {
-        return (start().compare_to(month) <= 0) && (end().compare_to(month) >= 0);
-    }
-    
-    /**
-     * Returns an Iterator for each { link MonthOfYear} (full and partial) in the { link MonthSpan}.
-     */
-    public Collection.SimpleIterator<MonthOfYear> iterator() {
-        return new MonthSpanIterator(this);
-    }
-    
-    /**
-     * Compares two { link MonthSpan}s by their { link start_date}.
-     */
-    public int compare_to(MonthSpan other) {
-        return start_date.compare_to(other.start_date);
-    }
-    
-    public bool equal_to(MonthSpan other) {
-        if (this == other)
-            return true;
-        
-        return start_date.equal_to(other.start_date) && end_date.equal_to(other.end_date);
-    }
-    
-    public uint hash() {
-        return start_date.hash() ^ end_date.hash();
+    public override bool contains(MonthOfYear month) {
+        return (first.compare_to(month) <= 0) && (last.compare_to(month) >= 0);
     }
     
     public override string to_string() {
-        return "months of %s".printf(dates.to_string());
+        return "months of %s".printf(to_date_span().to_string());
     }
 }
 
diff --git a/src/calendar/calendar-span.vala b/src/calendar/calendar-span.vala
index 416ecad..4df9f97 100644
--- a/src/calendar/calendar-span.vala
+++ b/src/calendar/calendar-span.vala
@@ -7,45 +7,103 @@
 namespace California.Calendar {
 
 /**
- * An immutable generic span or range of consecutive calendar dates.
+ * An immutable span or range of consecutive calendar { link Date}s.
  *
- * The Span is delineated by specific lengths of time (such as { link Date}, { link Week}, or
- * { link Month} and is required to be Traversable and Iterable by the same.
+ * Span is not currently designed for { link ExactTime} resolution or to be used as the base
+ * class of { link ExactTimeSpan}.  It's possible this will change in the future.
  *
- * Since the start and end Date of a Span may fall within the larger delineated unit of time,
- * the contract is that partial units will always be returned.  If the caller wants to only deal
- * with full units within this Span, they must check all returned values.
- *
- * Although not specified, it's expected that all Spans will also implement Gee.Comparable and
- * Gee.Hashable.
- *
- * Span is not designed for { link ExactTime} resolution.
- *
- * @see DateSpan
- * @see WeekSpan
- * @see MonthSpan
+ * @see DiscreteUnit
+ * @see UnitSpan
  */
 
-public interface Span<G> : BaseObject, Collection.SimpleIterable<G> {
+public abstract class Span : BaseObject {
+    public const string PROP_START_DATE = "start-date";
+    public const string PROP_END_DATE = "end-date";
+    public const string PROP_IS_SAME_DAY = "is-same-day";
+    public const string PROP_DURATION = "duration";
+    
+    private class SpanIterator : BaseObject, Collection.SimpleIterator<Date> {
+        private Date first;
+        private Date last;
+        private Date? current = null;
+        
+        public SpanIterator(Span span) {
+            first = span.start_date;
+            last = span.end_date;
+        }
+        
+        public new Date get() {
+            return current;
+        }
+        
+        public bool next() {
+            if (current == null)
+                current = first;
+            else if (current.compare_to(last) < 0)
+                current = current.next();
+            else
+                return false;
+            
+            return true;
+        }
+        
+        public override string to_string() {
+            return "SpanIterator %s::%s".printf(first.to_string(), last.to_string());
+        }
+    }
+    
     /**
      * Returns the earliest { link Date} within the { link Span}.
      */
-    public abstract Date start_date { owned get; }
+    private Date? _start_date = null;
+    public virtual Date start_date { get { return _start_date; } }
     
     /**
      * Returns the latest { link Date} within the { link Span}.
      */
-    public abstract Date end_date { owned get; }
+    private Date? _end_date = null;
+    public virtual Date end_date { get { return _end_date; } }
     
     /**
-     * The earliest delinated unit of time within the { link Span}.
+     * Convenience property indicating if the { link Span} spans only one day.
      */
-    public abstract G start();
+    public bool is_same_day { get { return start_date.equal_to(end_date); } }
     
     /**
-     * The latest delineated unit of time within the { link Span}.
+     * Returns the { link Duration} this { link Span} represents.
      */
-    public abstract G end();
+    public Duration duration { owned get { return new Duration(end_date.difference(start_date).abs()); } }
+    
+    protected Span(Date start_date, Date end_date) {
+        init_span(start_date, end_date);
+    }
+    
+    /**
+     * Create an unintialized { link Span) on the presumption that { link start_date} and
+     * { link end_date} are overridden or that the child class needs to do more work before
+     * providing a date span.
+     *
+     * Because it's sometimes inconvenient to generate the necessary { link Date}s until the
+     * subclass's constructor completes, Span allows for itself to be created empty assuming
+     * that the subclass will call { link init_span} as soon as it's finished initializing.
+     *
+     * init_span() must be called.  Span will not function properly when uninitialized.
+     */
+    protected Span.uninitialized() {
+    }
+    
+    /**
+     * @see Span.uninitialized
+     */
+    protected void init_span(Date start_date, Date end_date) {
+        if (start_date.compare_to(end_date) <= 0) {
+            _start_date = start_date;
+            _end_date = end_date;
+        } else {
+            _start_date = end_date;
+            _end_date = start_date;
+        }
+    }
     
     /**
      * Returns the earliest { link ExactTime} for this { link Span}.
@@ -53,7 +111,7 @@ public interface Span<G> : BaseObject, Collection.SimpleIterable<G> {
      * @see Date.earliest_exact_time
      */
     public ExactTime earliest_exact_time(Timezone tz) {
-        return start_date.earliest_exact_time(tz);
+        return new ExactTime(tz, start_date, WallTime.earliest);
     }
     
     /**
@@ -62,33 +120,85 @@ public interface Span<G> : BaseObject, Collection.SimpleIterable<G> {
      * @see Date.latest_exact_time
      */
     public ExactTime latest_exact_time(Timezone tz) {
-        return end_date.latest_exact_time(tz);
+        return new ExactTime(tz, end_date, WallTime.latest);
     }
     
     /**
      * Converts the { link Span} into a { link DateSpan}.
      */
     public DateSpan to_date_span() {
-        return new DateSpan(start_date, end_date);
+        return new DateSpan.from_span(this);
+    }
+    
+    /**
+     * Converts the { link Span} into a { link WeekSpan} using the supplied { link FirstOfWeek}.
+     *
+     * Dates covering a partial week are included.
+     */
+    public WeekSpan to_week_span(FirstOfWeek first_of_week) {
+        return new WeekSpan.from_span(this, first_of_week);
     }
     
     /**
-     * true if the { link Span} contains the specified { link Date}.
+     * Converts the { link Span} into a { link MonthSpan}.
      *
-     * This is named to conform to Vala's rule for automatic syntax support.  This allows for the
-     * ''in'' operator to function on Spans, but only for Dates (which is perceived as a common
-     * operation).
+     * Dates covering a partial month are included.
+     */
+    public MonthSpan to_month_span() {
+        return new MonthSpan.from_span(this);
+    }
+    
+    /**
+     * Converts the { link Span} into a { link YearSpan}.
      *
-     * @see has
+     * Dates coverting a partial year are included.
      */
-    public abstract bool contains(Date date);
+    public YearSpan to_year_span() {
+        return new YearSpan.from_span(this);
+    }
+    
+    /**
+     * Returns an { link ExactTimeSpan} for this { link Span}.
+     */
+    public ExactTimeSpan to_exact_time_span(Timezone tz) {
+        return new ExactTimeSpan(earliest_exact_time(tz), latest_exact_time(tz));
+    }
     
     /**
-     * true if the { link Span} contains the specified unit of time.
+     * Returns a { link DateSpan} with starting and ending points within the boundary specified
+     * (inclusive).
      *
-     * @see contains
+     * If this { link Span} is within the clamped dates, this object may be returned.
+     *
+     * This method will not expand a DateSpan to meet the clamp range.
+     */
+    public DateSpan clamp_between(Span span) {
+        Date new_start = (start_date.compare_to(span.start_date) < 0) ? span.start_date : start_date;
+        Date new_end = (end_date.compare_to(span.end_date) > 0) ? span.end_date : end_date;
+        
+        return new DateSpan(new_start, new_end);
+    }
+    
+    /**
+     * True if the { link Span} contains the specified { link Date}.
      */
-    public abstract bool has(G unit);
+    public bool has_date(Date date) {
+        int compare = start_date.compare_to(date);
+        if (compare == 0)
+            return true;
+        else if (compare > 0)
+            return false;
+        
+        return end_date.compare_to(date) >= 0;
+    }
+    
+    /**
+     * Returns a { link Collection.SimpleIterator} of all the { link Date}s in the
+     * { link Span}'s range of time.
+     */
+    public Collection.SimpleIterator<Date> date_iterator() {
+        return new SpanIterator(this);
+    }
 }
 
 }
diff --git a/src/calendar/calendar-unit-span.vala b/src/calendar/calendar-unit-span.vala
new file mode 100644
index 0000000..201db9c
--- /dev/null
+++ b/src/calendar/calendar-unit-span.vala
@@ -0,0 +1,119 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Calendar {
+
+/**
+ * An arbitrary, immutable span of { link Unit}s.
+ *
+ * @see DateSpan
+ * @see MonthSpan
+ * @see WeekSpan
+ * @see YearSpan
+ */
+
+public abstract class UnitSpan<G> : Span, Collection.SimpleIterable<G>, Gee.Comparable<UnitSpan>,
+    Gee.Hashable<UnitSpan> {
+    public const string PROP_FIRST = "first";
+    public const string PROP_LAST = "last";
+    
+    /**
+     * This relies on the fact that Unit<G> is, in fact, G, i.e. Week is a
+     * Unit<Week>.  This will blow-up if that's every not true.
+     */
+    private class UnitSpanIterator<G> : BaseObject, Collection.SimpleIterator<G> {
+        public Unit<G> first;
+        public Unit<G> last;
+        public Unit<G>? current = null;
+        
+        public UnitSpanIterator(G first, G last) {
+            this.first = (Unit<G>) first;
+            this.last = (Unit<G>) last;
+        }
+        
+        public new G get() {
+            return (G) current;
+        }
+        
+        public bool next() {
+            if (current == null)
+                current = first;
+            else if (current.start_date.compare_to(last.start_date) < 0)
+                current = (Unit<G>) current.adjust(1);
+            else
+                return false;
+            
+            return true;
+        }
+        
+        public override string to_string() {
+            return "UnitSpanIterator %s::%s".printf(first.to_string(), last.to_string());
+        }
+    }
+    
+    /**
+     * The earliest delinated unit of time within the { link UnitSpan}.
+     */
+    public G first { get; private set; }
+    
+    /**
+     * The latest delineated unit of time within the { link UnitSpan}.
+     */
+    public G last { get; private set; }
+    
+    protected UnitSpan(G first, G last, Date start_date, Date end_date) {
+        base (start_date, end_date);
+        
+        this.first = first;
+        this.last = last;
+    }
+    
+    /**
+     * True if the { link UnitSpan} contains the specified unit of time.
+     *
+     * This is named to conform to Vala's rule for automatic syntax support.  This allows for the
+     * ''in'' operator to function on UnitSpans.
+     *
+     * To determine if the UnitSpan contains a { link Date}, convert it to a { link DateSpan} with
+     * { link Span.to_date_span} and call its contains() method.
+     */
+    public abstract bool contains(G unit);
+    
+    /**
+     * Returns a { link Collection.SimpleIterator} of the { link UnitSpan}'s unit of time.
+     */
+    public Collection.SimpleIterator<G> iterator() {
+        return new UnitSpanIterator<G>(first, last);
+    }
+    
+    /**
+     * Compares two { link UnitSpan}s by their { link start_date}s.
+     */
+    public int compare_to(UnitSpan other) {
+        return start_date.compare_to(other.start_date);
+    }
+    
+    /**
+     * Returns true if both { link UnitSpan}s are equal.
+     *
+     * "Equal" is defined as having the same { link start_date} and { link end_date}.  That means
+     * a { link MonthSpan} and a { link YearSpan} may be equal if the months span exactly the
+     * same years.
+     */
+    public bool equal_to(UnitSpan other) {
+        if (this == other)
+            return true;
+        
+        return start_date.equal_to(other.start_date) && end_date.equal_to(other.end_date);
+    }
+    
+    public uint hash() {
+        return start_date.hash() ^ end_date.hash();
+    }
+}
+
+}
+
diff --git a/src/calendar/calendar-unit.vala b/src/calendar/calendar-unit.vala
index 7411685..4717e48 100644
--- a/src/calendar/calendar-unit.vala
+++ b/src/calendar/calendar-unit.vala
@@ -7,25 +7,106 @@
 namespace California.Calendar {
 
 /**
- * An enumeration of various calendar units.
+ * An immutable, discrete, well-recognized unit of calendar dates.
+ *
+ * This interface indicates the { link Calendar.Span} represents a discrete unit of time (each of
+ * which may not contain the same number of days, such as { link Year}s, while some might, such as
+ * { link Week}s), in contrast to a { link DateSpan}, which represents an ''arbitrary''
+ * span of dates that may or may not correspond to a well-recognized unit of dates.
+ *
+ * Note that this different than a { link UnitSpan} such as { link MonthSpan} which is designed to
+ * hold an arbitrary consecutive number of their date units (i.e. a span of months).  Unit
+ * represents a single unit of time (a week or a month).
+ *
+ * If Vala supported constrained generics, a UnitSpan would be defined as requiring a generic type
+ * of Unit.
+ *
+ * Unit is not designed to work with discrete { link ExactTime} or { link WallTime} units.
+ *
+ * @see Date
+ * @see Week
+ * @see MonthOfYear
+ * @see Year
  */
 
-public enum DateUnit {
-    DAY,
-    WEEK,
-    MONTH,
-    YEAR
+public abstract class Unit<G> : Span, Collection.SimpleIterable<Date> {
+    public const string PROP_DATE_UNIT = "date-unit";
+    
+    /**
+     * Returns the { link DateUnit} this { link Unit} represents.
+     */
+    public DateUnit date_unit { get; private set; }
+    
+    protected Unit(DateUnit date_unit, Date start_date, Date end_date) {
+        base (start_date, end_date);
+        
+        this.date_unit = date_unit;
+    }
+    
+    /**
+     * This is specifically for { link Date}, which can't pass itself down to { link Span} as that
+     * will create a reference cycle, or for child classes which need to do more work before
+     * providing a date span.
+     *
+     * If the latter, the child class should call { link init_span} to complete initialization.
+     */
+    protected Unit.uninitialized(DateUnit date_unit) {
+        base.uninitialized();
+        
+        this.date_unit = date_unit;
+    }
+    
+    /**
+     * The next chronological discrete unit of time.
+     */
+    public G next() {
+        return adjust(1);
+    }
+    
+    /**
+     * The previous chronological discrete unit of time.
+     */
+    public G previous() {
+        return adjust(-1);
+    }
+    
+    /**
+     * Returns the same type of { link Unit} adjusted a quantity of units from this one.
+     *
+     * Subtraction (adjusting to a past date) is acheived by using a negative quantity.
+     */
+    public abstract G adjust(int quantity);
+    
+    /**
+     * Returns the number of { link Unit}s between the two.
+     *
+     * If the supplied Unit is earlier than this one, a negative value is returned.
+     */
+    public abstract int difference(G other);
+    
+    /**
+     * True if the { link Unit} contains the specified { link Date}.
+     *
+     * This is named to conform to Vala's rule for automatic syntax support.  This allows for the
+     * ''in'' operator to function on DiscreteUnits, but only for Dates (which is a common
+     * operation).
+     */
+    public bool contains(Date date) {
+        return has_date(date);
+    }
+    
+    /**
+     * Returns a { link Collection.SimpleIterator} of all the { link Date}s in the
+     * { link Unit}'s span of time.
+     *
+     * This is named to conform to Vala's rule for automatic iterator support.  This allows for
+     * the ''foreach'' operator to function on DiscreteUnits, but only for Dates (which is a
+     * common operation).
+     */
+    public Collection.SimpleIterator<Date> iterator() {
+        return date_iterator();
+    }
 }
 
-/**
- * An enumeration of various time units.
- *
- * @see WallTime
- */
-public enum TimeUnit {
-    SECOND,
-    MINUTE,
-    HOUR
 }
 
-}
diff --git a/src/calendar/calendar-week-span.vala b/src/calendar/calendar-week-span.vala
index 16a54b8..4fb07ff 100644
--- a/src/calendar/calendar-week-span.vala
+++ b/src/calendar/calendar-week-span.vala
@@ -8,138 +8,61 @@ namespace California.Calendar {
 
 /**
  * An immutable representation of a span of { link Week}s.
- *
- * This class provides methods that can turn a { link DateSpan} an iteration of Week objects.
- * Partial weeks are included; the caller needs to do their own clamping if they want to avoid
- * days outside of the DateSpan.
  */
 
-public class WeekSpan : BaseObject, Collection.SimpleIterable<Week>, Span<Week>, Gee.Comparable<WeekSpan>,
-    Gee.Hashable<WeekSpan> {
-    private class WeekSpanIterator : BaseObject, Collection.SimpleIterator<Week> {
-        public Week first;
-        public Week last;
-        public Week? current = null;
-        
-        public WeekSpanIterator(WeekSpan owner) {
-            first = owner.start();
-            last = owner.end();
-        }
-        
-        public new Week get() {
-            return current;
-        }
-        
-        public bool next() {
-            if (current == null)
-                current = first;
-            else if (current.start_date.compare_to(last.start_date) < 0)
-                current = current.adjust(1);
-            else
-                return false;
-            
-            return true;
-        }
-        
-        public override string to_string() {
-            return "WeekSpanIterator %s::%s".printf(first.to_string(), last.to_string());
-        }
-    }
-    
+public class WeekSpan : UnitSpan<Week> {
     /**
-     * The { link DateSpan} of the { link Week}s.
-     */
-    public DateSpan dates { get; private set; }
-    
-    /**
-     * The defined first day of the week.
+     * The defined first day of the week for the weeks in the span.
      */
     public FirstOfWeek first_of_week { get; private set; }
     
     /**
-     * inheritDoc
-     */
-    public Date start_date { owned get { return dates.start_date; } }
-    
-    /**
-     * inheritDoc
-     */
-    public Date end_date { owned get { return dates.end_date; } }
-    
-    /**
-     * Create a span of { link Week}s corresponding to the { link DateSpan} according to
-     * { link FirstOfWeek}'s definition of a week's starting day.
+     * Create a span of { link Week}s from the start and end week.
+     *
+     * The start week's { link FirstOfWeek} is used.  The end week is converted if it uses a
+     * different first of week value.
      */
-    public WeekSpan(DateSpan dates, FirstOfWeek first_of_week) {
-        this.dates = dates;
-        this.first_of_week = first_of_week;
+    public WeekSpan(Week first, Week last) {
+        base (first, last, first.start_date, last.end_date.week_of(first.first_of_week).end_date);
+        
+        first_of_week = first.first_of_week;
     }
     
     /**
      * Create an arbitrary span of { link Week}s starting from the specified { link Week}.
      *
-     * Week's first-of-week is preserved.
-     */
-    public WeekSpan.count(Week start, int count) {
-        dates = new DateSpan(start.start_date, start.adjust(count).end_date);
-        first_of_week = start.first_of_week;
-    }
-    
-    /**
-     * inheritDoc
+     * start's first-of-week is preserved.
      */
-    public Week start() {
-        return dates.start_date.week_of(first_of_week);
-    }
-    
-    /**
-     * inheritDoc
-     */
-    public Week end() {
-        return dates.end_date.week_of(first_of_week);
+    public WeekSpan.count(Week first, int count) {
+        Week last = first.adjust(count);
+        
+        base (first, last, first.start_date, last.end_date);
+        
+        first_of_week = first.first_of_week;
     }
     
     /**
-     * @inheritDoc
+     * Create a span of { link Week}s corresponding to the { link DateSpan} according to
+     * { link FirstOfWeek}'s definition of a week's starting day.
      */
-    public bool contains(Date date) {
-        return dates.contains(date);
+    public WeekSpan.from_span(Span span, FirstOfWeek first_of_week) {
+        Week first = span.start_date.week_of(first_of_week);
+        Week last = span.end_date.week_of(first_of_week);
+        
+        base (first, last, first.start_date, last.end_date);
+        
+        this.first_of_week = first_of_week;
     }
     
     /**
      * @inheritDoc
      */
-    public bool has(Week week) {
-        return (start().compare_to(week) <= 0) && (end().compare_to(week) >= 0);
-    }
-    
-    /**
-     * Returns an Iterator for each { link Week} (full and partial) in the { link WeekSpan}.
-     */
-    public Collection.SimpleIterator<Week> iterator() {
-        return new WeekSpanIterator(this);
-    }
-    
-    /**
-     * Compares two { link WeekSpan}s by their { link start_date}.
-     */
-    public int compare_to(WeekSpan other) {
-        return start_date.compare_to(other.start_date);
-    }
-    
-    public bool equal_to(WeekSpan other) {
-        if (this == other)
-            return true;
-        
-        return start_date.equal_to(other.start_date) && end_date.equal_to(other.end_date);
-    }
-    
-    public uint hash() {
-        return start_date.hash() ^ end_date.hash();
+    public override bool contains(Week week) {
+        return (first.compare_to(week) <= 0) && (last.compare_to(week) >= 0);
     }
     
     public override string to_string() {
-        return "weeks of %s".printf(dates.to_string());
+        return "weeks of %s".printf(to_date_span().to_string());
     }
 }
 
diff --git a/src/calendar/calendar-week.vala b/src/calendar/calendar-week.vala
index 565d1a3..b52d83d 100644
--- a/src/calendar/calendar-week.vala
+++ b/src/calendar/calendar-week.vala
@@ -18,7 +18,7 @@ namespace California.Calendar {
  * { link Date.week_of} to obtain a Week for a particular calendar day.
  */
 
-public class Week : DateSpan {
+public class Week : Unit<Week>, Gee.Comparable<Week>, Gee.Hashable<Week> {
     /**
      * The one-based week of the month (1 to 6).
      */
@@ -52,7 +52,7 @@ public class Week : DateSpan {
      */
     internal Week(Date start, Date end, int week_of_month, int week_of_year, MonthOfYear month_of_year,
         FirstOfWeek first_of_week) {
-        base (start, end);
+        base (DateUnit.WEEK, start, end);
         
         this.week_of_month = week_of_month;
         this.week_of_year = week_of_year;
@@ -61,14 +61,43 @@ public class Week : DateSpan {
     }
     
     /**
-     * Returns a { link Week} adjusted a quantity of weeks from this one.
-     *
-     * The first day of the week is preserved in the new Week.
-     *
-     * Subtraction (adjusting to a past date) is acheived by using a negative quantity.
+     * @inheritDoc
      */
-    public Week adjust(int quantity) {
-        return start_date.adjust(quantity, DateUnit.WEEK).week_of(first_of_week);
+    public override Week adjust(int quantity) {
+        return start_date.adjust_by(quantity, DateUnit.WEEK).week_of(first_of_week);
+    }
+    
+    /**
+     * @inheritDoc
+     */
+    public override int difference(Week other) {
+        int compare = compare_to(other);
+        if (compare == 0)
+            return 0;
+        
+        // TODO: Iterating sucks, but it will have to suffice for now.
+        int count = 0;
+        Week current = this;
+        for (;;) {
+            current = (compare > 0) ? current.previous() : current.next();
+            count += (compare > 0) ? -1 : 1;
+            
+            if (current.equal_to(other))
+                return count;
+        }
+    }
+    
+    public int compare_to(Week other) {
+        return (this != other) ? start_date.compare_to(other.start_date) : 0;
+    }
+    
+    public bool equal_to(Week other) {
+        return compare_to(other) == 0;
+    }
+    
+    public uint hash() {
+        // 6 bits for week of year (1 - 52)
+        return (month_of_year.hash() << 6) | week_of_year;
     }
     
     public override string to_string() {
diff --git a/src/calendar/calendar-year-span.vala b/src/calendar/calendar-year-span.vala
new file mode 100644
index 0000000..b118475
--- /dev/null
+++ b/src/calendar/calendar-year-span.vala
@@ -0,0 +1,56 @@
+/* Copyright 2014 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+namespace California.Calendar {
+
+/**
+ * An immutable representation of a span of { link Year}s.
+ */
+
+public class YearSpan : UnitSpan<Year> {
+    /**
+     * Create a span of { link Year}s corresponding to the start and end years.
+     */
+    public YearSpan(Year first, Year last) {
+        base (first, last, first.start_date, last.end_date);
+    }
+    
+    /**
+     * Create a span of { link Years}s starting from the specified starting month.
+     */
+    public YearSpan.count(Year first, int count) {
+        Year last = first.adjust(count);
+        
+        base (first, last, first.start_date, last.end_date);
+    }
+    
+    /**
+     * Create a span of { link Year}s from the start and end of a { link Span}.
+     *
+     * The year of the Span's start_date and the month of Span's end_date are used to determine
+     * the YearSpan.
+     */
+    public YearSpan.from_span(Span span) {
+        Year first = span.start_date.year;
+        Year last = span.end_date.year;
+        
+        base (first, last, first.start_date, last.end_date);
+    }
+    
+    /**
+     * @inheritDoc
+     */
+    public override bool contains(Year year) {
+        return (first.compare_to(year) <= 0) && (last.compare_to(year) >= 0);
+    }
+    
+    public override string to_string() {
+        return "months of %s".printf(to_date_span().to_string());
+    }
+}
+
+}
+
diff --git a/src/calendar/calendar-year.vala b/src/calendar/calendar-year.vala
index f1ab980..a04960c 100644
--- a/src/calendar/calendar-year.vala
+++ b/src/calendar/calendar-year.vala
@@ -13,7 +13,7 @@ namespace California.Calendar {
  * value is described as thousands of years from now.
  */
 
-public class Year : DateSpan {
+public class Year : Unit<Year>, Gee.Comparable<Year>, Gee.Hashable<Year> {
     /**
      * The year as an integer.
      */
@@ -25,7 +25,7 @@ public class Year : DateSpan {
      * Negative values and zero are clamped to 1 CE.
      */
     public Year(int value) {
-        base.uninitialized();
+        base.uninitialized(DateUnit.YEAR);
         
         this.value = value.clamp(1, int.MAX);
         
@@ -47,6 +47,32 @@ public class Year : DateSpan {
         return (DateYear) value;
     }
     
+    /**
+     * @inheritDoc
+     */
+    public override Year adjust(int quantity) {
+        return new Year(value + quantity);
+    }
+    
+    /**
+     * @inheritDoc
+     */
+    public override int difference(Year other) {
+        return value - other.value;
+    }
+    
+    public int compare_to(Year other) {
+        return (this != other) ? start_date.compare_to(other.start_date) : 0;
+    }
+    
+    public bool equal_to(Year other) {
+        return compare_to(other) == 0;
+    }
+    
+    public uint hash() {
+        return value;
+    }
+    
     public override string to_string() {
         return value.to_string();
     }
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index 97df645..6c4d5bc 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -17,6 +17,31 @@
 
 namespace California.Calendar {
 
+/**
+ * An enumeration of various calendar units.
+ *
+ * @see Unit
+ */
+
+public enum DateUnit {
+    DAY,
+    WEEK,
+    MONTH,
+    YEAR
+}
+
+/**
+ * An enumeration of various time units.
+ *
+ * @see WallTime
+ */
+
+public enum TimeUnit {
+    SECOND,
+    MINUTE,
+    HOUR
+}
+
 private int init_count = 0;
 
 private static unowned string FMT_MONTH_YEAR_FULL;
diff --git a/src/component/component-date-time.vala b/src/component/component-date-time.vala
index 3795a69..f8121a5 100644
--- a/src/component/component-date-time.vala
+++ b/src/component/component-date-time.vala
@@ -213,7 +213,7 @@ public class DateTime : BaseObject, Gee.Hashable<DateTime>, Gee.Comparable<DateT
         
         // if exclusive, drop back one day
         if (!dtend_inclusive)
-            end_date = end_date.adjust(-1, Calendar.DateUnit.DAY);
+            end_date = end_date.previous();
         
         date_span = new Calendar.DateSpan(start_date, end_date);
         exact_time_span = null;
diff --git a/src/component/component-details-parser.vala b/src/component/component-details-parser.vala
index c396141..ff27110 100644
--- a/src/component/component-details-parser.vala
+++ b/src/component/component-details-parser.vala
@@ -186,7 +186,7 @@ public class DetailsParser : BaseObject {
         // if no end date was describe, assume ends today as well (unless midnight was crossed
         // due to duration)
         if (start_date != null && end_date == null)
-            end_date = midnight_crossed ? start_date.adjust(1, Calendar.DateUnit.DAY) : start_date;
+            end_date = midnight_crossed ? start_date.next() : start_date;
         
         // Event start/end time, if specified
         if (start_time != null && end_time != null) {
@@ -349,7 +349,7 @@ public class DetailsParser : BaseObject {
         // find a Date for day of the week ... starting today, move forward up to one
         // week
         Calendar.Date upcoming = Calendar.System.today;
-        Calendar.Date next_week = upcoming.adjust(1, Calendar.DateUnit.WEEK);
+        Calendar.Date next_week = upcoming.adjust_by(1, Calendar.DateUnit.WEEK);
         do {
             if (upcoming.day_of_week.equal_to(dow))
                 return upcoming;
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 40cb7e8..16a9977 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -325,7 +325,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
     protected static void date_span_to_ical(Calendar.DateSpan date_span, bool dtend_inclusive,
         iCal.icaltimetype *ical_dtstart, iCal.icaltimetype *ical_dtend) {
         date_to_ical(date_span.start_date, ical_dtstart);
-        date_to_ical(date_span.end_date.adjust(dtend_inclusive ? 0 : 1, Calendar.DateUnit.DAY),
+        date_to_ical(date_span.end_date.adjust_by(dtend_inclusive ? 0 : 1, Calendar.DateUnit.DAY),
             ical_dtend);
     }
     
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index aa8459c..4ad6801 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -171,7 +171,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     }
     
     private void on_previous() {
-        current_view.prev();
+        current_view.previous();
     }
     
     private void on_request_create_timed_event(Calendar.ExactTimeSpan initial, Gtk.Widget relative_to,
diff --git a/src/tests/tests-calendar-date.vala b/src/tests/tests-calendar-date.vala
index 2620dd9..fb05d3d 100644
--- a/src/tests/tests-calendar-date.vala
+++ b/src/tests/tests-calendar-date.vala
@@ -23,7 +23,7 @@ private class CalendarDate : UnitTest.Harness {
     }
     
     private Calendar.Date from_today(int days) {
-        return Calendar.System.today.adjust(days, Calendar.DateUnit.DAY);
+        return Calendar.System.today.adjust(days);
     }
     
     private Calendar.DateSpan span_from_today(int start_days, int end_days) {
@@ -33,7 +33,7 @@ private class CalendarDate : UnitTest.Harness {
     private bool clamp_start() throws Error {
         Calendar.DateSpan span = span_from_today(0, 5);
         Calendar.DateSpan clamp = span_from_today(1, 5);
-        Calendar.DateSpan adj = span.clamp(clamp);
+        Calendar.DateSpan adj = span.clamp_between(clamp);
         
         return adj.start_date.equal_to(clamp.start_date) && adj.end_date.equal_to(span.end_date);
     }
@@ -41,7 +41,7 @@ private class CalendarDate : UnitTest.Harness {
     private bool clamp_end() throws Error {
         Calendar.DateSpan span = span_from_today(0, 5);
         Calendar.DateSpan clamp = span_from_today(0, 4);
-        Calendar.DateSpan adj = span.clamp(clamp);
+        Calendar.DateSpan adj = span.clamp_between(clamp);
         
         return adj.start_date.equal_to(span.start_date) && adj.end_date.equal_to(clamp.end_date);
     }
@@ -49,7 +49,7 @@ private class CalendarDate : UnitTest.Harness {
     private bool clamp_both() throws Error {
         Calendar.DateSpan span = span_from_today(0, 5);
         Calendar.DateSpan clamp = span_from_today(1, 4);
-        Calendar.DateSpan adj = span.clamp(clamp);
+        Calendar.DateSpan adj = span.clamp_between(clamp);
         
         return adj.start_date.equal_to(clamp.start_date) && adj.end_date.equal_to(clamp.end_date);
     }
@@ -57,7 +57,7 @@ private class CalendarDate : UnitTest.Harness {
     private bool clamp_neither() throws Error {
         Calendar.DateSpan span = span_from_today(0, 5);
         Calendar.DateSpan clamp = span_from_today(-1, 6);
-        Calendar.DateSpan adj = span.clamp(clamp);
+        Calendar.DateSpan adj = span.clamp_between(clamp);
         
         return adj.start_date.equal_to(span.start_date) && adj.end_date.equal_to(span.end_date);
     }
diff --git a/src/tests/tests-calendar-month-span.vala b/src/tests/tests-calendar-month-span.vala
index 5f200d3..2756987 100644
--- a/src/tests/tests-calendar-month-span.vala
+++ b/src/tests/tests-calendar-month-span.vala
@@ -12,6 +12,7 @@ private class CalendarMonthSpan : UnitTest.Harness {
         add_case("contains-date", contains_date);
         add_case("has-month", has_month);
         add_case("iterator", iterator);
+        add_case("in-operator", in_operator);
     }
     
     protected override void setup() throws Error {
@@ -23,37 +24,37 @@ private class CalendarMonthSpan : UnitTest.Harness {
     }
     
     private Calendar.Date from_today(int days) {
-        return Calendar.System.today.adjust(days, Calendar.DateUnit.DAY);
+        return Calendar.System.today.adjust(days);
     }
     
     private bool todays_month() throws Error {
-        Calendar.MonthSpan span = new Calendar.MonthSpan(new Calendar.DateSpan(
-            from_today(0), from_today(0)));
+        Calendar.MonthSpan span = new Calendar.MonthSpan.from_span(
+            new Calendar.DateSpan(from_today(0), from_today(0)));
         
-        return span.start().equal_to(Calendar.System.today.month_of_year())
-            && span.end().equal_to(Calendar.System.today.month_of_year());
+        return span.first.equal_to(Calendar.System.today.month_of_year())
+            && span.last.equal_to(Calendar.System.today.month_of_year());
     }
     
     private bool contains_date() throws Error {
         Calendar.Date first = new Calendar.Date(Calendar.DayOfMonth.for_checked(1), Calendar.Month.JAN, new 
Calendar.Year(2014));
         Calendar.Date last = new Calendar.Date(Calendar.DayOfMonth.for_checked(30), Calendar.Month.JAN, new 
Calendar.Year(2014));
-        Calendar.MonthSpan span = new Calendar.MonthSpan(new Calendar.DateSpan(first, last));
+        Calendar.MonthSpan span = new Calendar.MonthSpan.from_span(new Calendar.DateSpan(first, last));
         
-        return span.contains(first.adjust(15, Calendar.DateUnit.DAY));
+        return span.has_date(first.adjust(15));
     }
     
     private bool has_month() throws Error {
         Calendar.Date first = new Calendar.Date(Calendar.DayOfMonth.for_checked(1), Calendar.Month.JAN, new 
Calendar.Year(2014));
         Calendar.Date last = new Calendar.Date(Calendar.DayOfMonth.for_checked(30), Calendar.Month.MAR, new 
Calendar.Year(2014));
-        Calendar.MonthSpan span = new Calendar.MonthSpan(new Calendar.DateSpan(first, last));
+        Calendar.MonthSpan span = new Calendar.MonthSpan.from_span(new Calendar.DateSpan(first, last));
         
-        return span.has(new Calendar.MonthOfYear(Calendar.Month.FEB, new Calendar.Year(2014)));
+        return span.contains(new Calendar.MonthOfYear(Calendar.Month.FEB, new Calendar.Year(2014)));
     }
     
     private bool iterator() throws Error {
         Calendar.Date first = new Calendar.Date(Calendar.DayOfMonth.for_checked(1), Calendar.Month.JAN, new 
Calendar.Year(2014));
         Calendar.Date last = new Calendar.Date(Calendar.DayOfMonth.for_checked(30), Calendar.Month.JUN, new 
Calendar.Year(2014));
-        Calendar.MonthSpan span = new Calendar.MonthSpan(new Calendar.DateSpan(first, last));
+        Calendar.MonthSpan span = new Calendar.MonthSpan.from_span(new Calendar.DateSpan(first, last));
         
         Calendar.Month[] months = {
             Calendar.Month.JAN,
@@ -75,6 +76,15 @@ private class CalendarMonthSpan : UnitTest.Harness {
         
         return ctr == 6;
     }
+    
+    private bool in_operator() throws Error {
+        Calendar.Date first = new Calendar.Date(Calendar.DayOfMonth.for_checked(1), Calendar.Month.JAN, new 
Calendar.Year(2014));
+        Calendar.Date last = new Calendar.Date(Calendar.DayOfMonth.for_checked(30), Calendar.Month.MAR, new 
Calendar.Year(2014));
+        Calendar.MonthSpan span = new Calendar.MonthSpan.from_span(new Calendar.DateSpan(first, last));
+        Calendar.MonthOfYear month = new Calendar.MonthOfYear(Calendar.Month.FEB, new Calendar.Year(2014));
+        
+        return month in span;
+    }
 }
 
 }
diff --git a/src/tests/tests-quick-add.vala b/src/tests/tests-quick-add.vala
index 57ff4c2..d4b3314 100644
--- a/src/tests/tests-quick-add.vala
+++ b/src/tests/tests-quick-add.vala
@@ -160,7 +160,7 @@ private class QuickAdd : UnitTest.Harness {
         
         Calendar.ExactTime start = new Calendar.ExactTime(Calendar.Timezone.local, Calendar.System.today,
             new Calendar.WallTime(23, 0, 0));
-        Calendar.ExactTime end = new Calendar.ExactTime(Calendar.Timezone.local, 
Calendar.System.today.adjust(1, Calendar.DateUnit.DAY),
+        Calendar.ExactTime end = new Calendar.ExactTime(Calendar.Timezone.local, 
Calendar.System.today.next(),
             new Calendar.WallTime(0, 0, 0));
         
         return parser.event.summary == "Dinner"
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index 0933d02..4cdf2d2 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -309,7 +309,7 @@ private class Cell : Gtk.EventBox {
         if (reassigned.size > 0) {
             // only need to tell cells following this day's in the current week about the reassignment
             Calendar.Week this_week = date.week_of(owner.first_of_week);
-            Calendar.DateSpan span = new Calendar.DateSpan(date.next(), this_week.end_date).clamp(this_week);
+            Calendar.DateSpan span = new Calendar.DateSpan(date.next(), 
this_week.end_date).clamp_between(this_week);
             
             foreach (Calendar.Date span_date in span) {
                 Cell? cell = owner.get_cell_for_date(span_date);
diff --git a/src/view/month/month-controller.vala b/src/view/month/month-controller.vala
index fb3dec3..2273104 100644
--- a/src/view/month/month-controller.vala
+++ b/src/view/month/month-controller.vala
@@ -120,7 +120,7 @@ public class Controller : BaseObject, View.Controllable {
     // previous month and that Grids outside that range are dropped.  The current chronological
     // month is never discarded.
     private void update_month_grid_cache() {
-        Calendar.MonthSpan cache_span = new Calendar.MonthSpan.from_months(
+        Calendar.MonthSpan cache_span = new Calendar.MonthSpan(
             month_of_year.adjust(0 - (CACHE_NEIGHBORS_COUNT / 2)),
             month_of_year.adjust(CACHE_NEIGHBORS_COUNT / 2));
         
@@ -134,7 +134,7 @@ public class Controller : BaseObject, View.Controllable {
                 continue;
             
             // keep if grid is in cache span
-            if (cache_span.has(grid_moy))
+            if (grid_moy in cache_span)
                 continue;
             
             // drop, remove from GtkStack and local storage
@@ -161,7 +161,7 @@ public class Controller : BaseObject, View.Controllable {
     /**
      * @inheritDoc
      */
-    public void prev() {
+    public void previous() {
         month_of_year = month_of_year.previous();
     }
     
diff --git a/src/view/month/month-grid.vala b/src/view/month/month-grid.vala
index bb21d85..fd76f99 100644
--- a/src/view/month/month-grid.vala
+++ b/src/view/month/month-grid.vala
@@ -168,7 +168,7 @@ private class Grid : Gtk.Grid {
         
         // create a WeekSpan for the first week of the month to the last displayed week (not all
         // months will fill all displayed weeks, but some will)
-        Calendar.WeekSpan span = new 
Calendar.WeekSpan.count(month_of_year.weeks(owner.first_of_week).start(),
+        Calendar.WeekSpan span = new 
Calendar.WeekSpan.count(month_of_year.to_week_span(owner.first_of_week).first,
             ROWS - 1);
         
         // fill in weeks of the displayed month
diff --git a/src/view/view-controllable.vala b/src/view/view-controllable.vala
index db5dc44..d8633c5 100644
--- a/src/view/view-controllable.vala
+++ b/src/view/view-controllable.vala
@@ -78,7 +78,7 @@ public interface Controllable : Object {
     /**
      * Move backward one calendar unit.
      */
-    public abstract void prev();
+    public abstract void previous();
     
     /**
      * Jump to calendar unit representing the current date.


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