[california/wip/timezone] Flesh out Calendar.System and Calendar.Timezone



commit 68bd18818ed4925c4fa62170bf416893f7b27398
Author: Jim Nelson <jim yorba org>
Date:   Thu Mar 6 17:17:28 2014 -0800

    Flesh out Calendar.System and Calendar.Timezone

 src/Makefile.am                        |    4 +
 src/calendar/calendar-dbus.vala        |   58 +++++++++++++++++++
 src/calendar/calendar-olson-zone.vala  |   66 +++++++++++++++++++++
 src/calendar/calendar-system.vala      |   98 +++++++++++++++++++++++++++----
 src/calendar/calendar-timezone.vala    |   63 ++++++++++----------
 src/calendar/calendar.vala             |   22 ++-----
 src/component/component-instance.vala  |    2 +-
 src/component/component-uid.vala       |    2 +-
 src/host/host-create-update-event.vala |    4 +-
 src/host/host-main-window.vala         |    2 +-
 src/host/host-show-event.vala          |    4 +-
 src/view/month/month-cell.vala         |    2 +-
 src/view/month/month-controllable.vala |   14 ++--
 13 files changed, 266 insertions(+), 75 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index a74ca36..e8942d2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -32,13 +32,17 @@ california_VALASOURCES = \
        calendar/calendar-day-of-month.vala \
        calendar/calendar-day-of-week.vala \
        calendar/calendar-date.vala \
+       calendar/calendar-dbus.vala \
        calendar/calendar-error.vala \
        calendar/calendar-exact-time.vala \
        calendar/calendar-exact-time-span.vala \
        calendar/calendar-first-of-week.vala \
        calendar/calendar-month.vala \
        calendar/calendar-month-of-year.vala \
+       calendar/calendar-olson-zone.vala \
        calendar/calendar-span.vala \
+       calendar/calendar-system.vala \
+       calendar/calendar-timezone.vala \
        calendar/calendar-unit.vala \
        calendar/calendar-wall-time.vala \
        calendar/calendar-week.vala \
diff --git a/src/calendar/calendar-dbus.vala b/src/calendar/calendar-dbus.vala
new file mode 100644
index 0000000..aa1de83
--- /dev/null
+++ b/src/calendar/calendar-dbus.vala
@@ -0,0 +1,58 @@
+/* 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.
+ */
+
+/**
+ * D-Bus interfaces needed for system time/date information.
+ */
+
+namespace California.Calendar.DBus {
+
+/**
+ * D-Bus interface to systemd-timedated which holds various useful time/date-related information.
+ *
+ * See [[http://www.freedesktop.org/wiki/Software/systemd/timedated/]]
+ */
+
+[DBus (name = "org.freedesktop.timedate1")]
+public interface timedated : Object {
+    public const string NAME = "org.freedesktop.timedate1";
+    public const string OBJECT_PATH = "/org/freedesktop/timedate1";
+    
+    public const string PROP_TIMEZONE = "Timezone";
+    public const string PROP_LOCAL_RTC = "LocalRTC";
+    public const string PROP_NTP = "NTP";
+    
+    public abstract string timezone { owned get; }
+    public abstract bool local_rtc { get; }
+    public abstract bool ntp { get; }
+    
+    public abstract void set_time(int64 usec_etc, bool relative, bool user_interaction) throws IOError;
+    public abstract void set_timezone(string timezone, bool user_interaction) throws IOError;
+    public abstract void set_local_rtc(bool local_rtc, bool fix_system, bool user_interaction)
+        throws IOError;
+    public abstract void set_ntp(bool use_ntp, bool user_interaction) throws IOError;
+}
+
+/**
+ * D-Bus interface for querying and monitoring properties.
+ *
+ * See [[https://pythonhosted.org/txdbus/dbus_overview.html]], "org.freedesktop.DBus.Properties"
+ */
+
+[DBus (name = "org.freedesktop.DBus.Properties")]
+public interface Properties : Object {
+    public const string NAME = "org.freedesktop.DBus.Properties";
+    
+    public signal void properties_changed(string interf, HashTable<string, Variant> 
changed_properties_values,
+        string[] changed_properties);
+    
+    public abstract Variant get(string interf, string property) throws IOError;
+    public abstract void get_all(string interf, HashTable<string, Variant> properties) throws IOError;
+    public abstract void set(string interf, string property, Variant value) throws IOError;
+}
+
+}
+
diff --git a/src/calendar/calendar-olson-zone.vala b/src/calendar/calendar-olson-zone.vala
new file mode 100644
index 0000000..2d21ddb
--- /dev/null
+++ b/src/calendar/calendar-olson-zone.vala
@@ -0,0 +1,66 @@
+/* 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 {
+
+/**
+ * The Olson name of a time zone in the tz (or zoneinfo) database.
+ *
+ * An Olson name is in the form of "Area/Location".  This class merely encapsulates this string
+ * and gives it some type-ness; actual time zone calculations is left to { link Timezone}.
+ * In particular, little to no error-checking is performed by this class.  At the moment, the
+ * class looks for perverse input and defaults to UTC.  It also does no processing or consideration
+ * for zone aliases ("links"), which is why it does not implement Gee.Hashable or Gee.Comparable.
+ *
+ * This class is immutable.
+ *
+ * Future expansion may include some processing or parsing of the name itself, but that's not
+ * planned at the moment.
+ *
+ * The IANA database of Olson zones and related information is located at
+ * [[https://www.iana.org/time-zones]]
+ */
+
+public class OlsonZone : BaseObject {
+    /**
+     * The string value this class uses if an empty string is passed to the constructor.
+     *
+     * Note that this is not the only definition of UTC in the zoneinfo database.  That is,
+     * a simple comparison of { link value} to this constant is no guarantee that an
+     * { link OlsonZone} is or is not UTC.
+     */
+    public const string UTC = "UTC";
+    
+    /**
+     * An { link OlsonZone} representation of UTC.
+     *
+     * @see UTC
+     */
+    public static OlsonZone utc { get; private set; }
+    
+    /**
+     * The raw Olson zoneinfo name.
+     */
+    public string value { get; private set; }
+    
+    public OlsonZone(string area_location) {
+        value = !String.is_empty(area_location) ? area_location : UTC;
+    }
+    
+    internal static void init() {
+        utc = new OlsonZone(UTC);
+    }
+    
+    internal static void terminate() {
+        utc = null;
+    }
+    
+    public override string to_string() {
+        return value;
+    }
+}
+
+}
diff --git a/src/calendar/calendar-system.vala b/src/calendar/calendar-system.vala
index fbf37d0..a75d02e 100644
--- a/src/calendar/calendar-system.vala
+++ b/src/calendar/calendar-system.vala
@@ -8,28 +8,86 @@ namespace California.Calendar {
 
 /**
  * A singleton offering system-based calendar and time information and updates of important changes.
+ *
+ * Most of System's properties are static as a convenience for callers (to avoid having to always
+ * reference { link instance}).  Since static properties have no notification mechanism, callers
+ * interested in being notified of changes must subscribe to the instance's particular signals,
+ * i.e. { link zone_changed}.
  */
 
-public class System {
-    public const string PROP_LOCAL_TZ = "local-tz";
-    
+public class System : BaseObject {
     public static System instance { get; private set; }
     
     /**
-     * The current local { link Timezone}.
+     * The current date according to the local timezone.
      *
-     * Since this can change at runtime, the property should ne monitored for changes.
+     * TODO: This currently does not update as the program executes.
+     */
+    public static Date today { get; private set; }
+    
+    /**
+     * Returns the current { link ExactTime} of the local TimeZone.
      */
-    public Timezone local_tz { get; private set; }
+    public static ExactTime now {
+        owned get {
+            return new ExactTime.now(new TimeZone.local());
+        }
+    }
     
     /**
      * If the current user is configured to use 12 or 24-hour time.
      */
-    public bool is_24hr { get; private set; }
+    public static bool is_24hr { get; private set; }
+    
+    /**
+     * Returns the system's configured zone as an { link OlsonZone}.
+     */
+    public static OlsonZone zone { get; private set; }
+    
+    /**
+     * The current system { link Timezone}.
+     *
+     * @see local_timezone_changed
+     * @see Timezone.utc
+     */
+    public static Timezone local_timezone { get; private set; }
+    
+    private static DBus.timedated timedated_service;
+    private static DBus.Properties timedated_properties;
+    
+    /**
+     * Fired when { link zone} changes.
+     *
+     * This generally indicates that the user has changed system time zone manually or that the
+     * system detected the change through geolocation services.
+     */
+    public signal void zone_changed(OlsonZone new_zone);
+    
+    /**
+     * Fired when { link local_timezone} changes due to system configuration changes.
+     */
+    public signal void local_timezone_changed(Timezone new_local_timezone);
     
     private System() {
-        // TODO: Fetch from GNOME settings
+        zone = new OlsonZone(timedated_service.timezone);
+        local_timezone = new Timezone(zone);
+        debug("Local zone: %s", zone.to_string());
+        
+        // to be notified of changes as they occur
+        timedated_properties.properties_changed.connect(on_timedated_properties_changed);
+        
+        // TODO: Fetch and update from GNOME settings
         is_24hr = false;
+        
+        // TODO: Tie this into the event loop so it's properly updated
+        today = new Date.now(new TimeZone.local());
+    }
+    
+    internal static void preinit() throws IOError {
+        timedated_service = Bus.get_proxy_sync(BusType.SYSTEM, DBus.timedated.NAME,
+            DBus.timedated.OBJECT_PATH);
+        timedated_properties = Bus.get_proxy_sync(BusType.SYSTEM, DBus.Properties.NAME,
+            DBus.timedated.OBJECT_PATH);
     }
     
     internal static void init() {
@@ -38,13 +96,27 @@ public class System {
     
     internal static void terminate() {
         instance = null;
+        timedated_service = null;
+        timedated_properties = null;
     }
     
-    /**
-     * Returns the current { link ExactTime} of the local TimeZone.
-     */
-    public ExactTime now() {
-        return new ExactTime.now(new TimeZone.local());
+    private void on_timedated_properties_changed(string interf,
+        HashTable<string, Variant> changed_properties_values, string[] changed_properties) {
+        if (changed_properties_values.contains(DBus.timedated.PROP_TIMEZONE)
+            || DBus.timedated.PROP_TIMEZONE in changed_properties) {
+            zone = new OlsonZone(timedated_service.timezone);
+            local_timezone = new Timezone(zone);
+            debug("New local zone: %s", zone.to_string());
+            
+            // fire signals last in case a subscriber monitoring zone thinks that local_timezone
+            // has also changed
+            zone_changed(zone);
+            local_timezone_changed(local_timezone);
+        }
+    }
+    
+    public override string to_string() {
+        return get_class().get_type().name();
     }
 }
 
diff --git a/src/calendar/calendar-timezone.vala b/src/calendar/calendar-timezone.vala
index 22d38bf..773997a 100644
--- a/src/calendar/calendar-timezone.vala
+++ b/src/calendar/calendar-timezone.vala
@@ -6,60 +6,59 @@
 
 namespace California.Calendar {
 
-public Timezone : BaseObject, Gee.Hashable<Timezone> {
-    private enum Special {
-        NONE,
-        LOCAL,
-        UTC
-    }
-    
+public class Timezone : BaseObject {
     /**
      * The { link Timezone} for UTC.
      */
     public static Timezone utc { get; private set; }
     
     /**
+     * The system's configured { link Timezone}.
+     *
+     * This is merely a convenience method for { link System.local_timezone}.
+     *
+     * @see System.local_timezone_changed
+     */
+    public static Timezone local { get { return System.local_timezone; } }
+    
+    /**
+     * The { link OlsonZone} for this { link Timezone}.
+     */
+    public OlsonZone zone { get; private set; }
+    
+    /**
      * Returns true if this { link Timezone} represents UTC.
+     *
+     * This merely tests that this object is the same as the current { link local} object; no deep
+     * equality is tested.
      */
-    public bool is_utc { get { return special == Special.UTC; } }
+    public bool is_utc { get { return this == utc; } }
     
     /**
-     * Returns true if this { link Timezone} represents the local timezone.
+     * Returns true if this { link Timezone} represents the system's configured time zone.
      *
-     * Since timezones may change for the computer, this returns true if the timezone was local
-     * at the time the object was created.
+     * This merely tests that this object is the same as the current { link local} object; no deep
+     * equality is tested.
      */
-    public bool is_local { get { return special == Special.LOCAL; } }
+    public bool is_local { get { return this == local; } }
     
     private TimeZone tz;
-    private string? identifier;
-    private Special special;
     
-    public Timezone(string identifier) {
-        tz = new TimeZone(identifier);
-        this.identifier = identifier;
-        special = Special.NONE;
+    public Timezone(OlsonZone zone) {
+        tz = new TimeZone(zone.value);
+        this.zone = zone;
     }
     
-    private Timezone(TimeZone tz, Special special) {
-        this.tz = tz;
-        identifier = null;
-        this.special = special;
+    internal static void init() {
+        utc = new Timezone(OlsonZone.utc);
     }
     
-    internal void init() {
-        utc = new Timezone(new TimeZone.utc(), Special.UTC);
-    }
-    
-    internal void terminate() {
+    internal static void terminate() {
         utc = null;
     }
     
-    /**
-     * Returns the local { link Timezone}.
-     */
-    public static Timezone local() {
-        return new Timezone(new TimeZone.local(), Special.LOCAL);
+    public override string to_string() {
+        return zone.to_string();
     }
 }
 
diff --git a/src/calendar/calendar.vala b/src/calendar/calendar.vala
index acbbf7d..6ab23da 100644
--- a/src/calendar/calendar.vala
+++ b/src/calendar/calendar.vala
@@ -17,13 +17,6 @@
 
 namespace California.Calendar {
 
-/**
- * The current date according to the local timezone.
- *
- * This currently does not update as the program executes.
- */
-public Date today;
-
 private int init_count = 0;
 
 private static unowned string FMT_MONTH_YEAR_FULL;
@@ -57,31 +50,30 @@ public void init() throws Error {
     FMT_PRETTY_DATE_ABBREV = _("%a, %b %e, %Y");
     FMT_PRETTY_DATE_ABBREV_NO_YEAR = _("%a, %b %e");
     
+    // This init() throws an IOError, so perform before others to prevent unnecessary unwinding
+    System.preinit();
+    
     // internal initialization
-    Timezone.init();
+    OlsonZone.init();
     DayOfWeek.init();
     DayOfMonth.init();
     Month.init();
     WallTime.init();
     System.init();
-    
-    // TODO: Tie this into the event loop so it's properly updated; also make it a property of
-    // an instance so it can be monitored
-    today = new Date.now(new TimeZone.local());
+    Timezone.init();
 }
 
 public void terminate() {
     if (!California.Unit.do_terminate(ref init_count))
         return;
     
-    today = null;
-    
+    Timezone.terminate();
     System.terminate();
     WallTime.terminate();
     Month.terminate();
     DayOfMonth.terminate();
     DayOfWeek.terminate();
-    Timezone.terminate();
+    OlsonZone.terminate();
 }
 
 }
diff --git a/src/component/component-instance.vala b/src/component/component-instance.vala
index 2218aa4..d2e4fcd 100644
--- a/src/component/component-instance.vala
+++ b/src/component/component-instance.vala
@@ -134,7 +134,7 @@ public abstract class Instance : BaseObject, Gee.Hashable<Instance> {
         if (from_full_update)
             return;
         
-        dtstamp = Calendar.now();
+        dtstamp = Calendar.System.now;
         
         iCal.icaltimetype ical_dtstamp = {};
         exact_time_to_ical(dtstamp, &ical_dtstamp);
diff --git a/src/component/component-uid.vala b/src/component/component-uid.vala
index 63b684d..758f961 100644
--- a/src/component/component-uid.vala
+++ b/src/component/component-uid.vala
@@ -24,7 +24,7 @@ public class UID : BaseObject, Gee.Hashable<UID>, Gee.Comparable<UID> {
     public static UID generate() {
         // Borrowed liberally from EDS' e_cal_component_gen_uid
         return new UID("%s-%d-%d-%d-%08X %s".printf(
-            Calendar.now().format("%FT%H:%M:%S%z"),
+            Calendar.System.now.format("%FT%H:%M:%S%z"),
             Posix.getpid(),
             (int) Posix.getgid(),
             (int) Posix.getppid(),
diff --git a/src/host/host-create-update-event.vala b/src/host/host-create-update-event.vala
index 0fb6511..503d91a 100644
--- a/src/host/host-create-update-event.vala
+++ b/src/host/host-create-update-event.vala
@@ -108,9 +108,9 @@ public class CreateUpdateEvent : Gtk.Grid {
             
             all_day_toggle.active = true;
             selected_date_span = event.date_span;
-            initial_start_time = new Calendar.WallTime.from_exact_time(Calendar.now());
+            initial_start_time = new Calendar.WallTime.from_exact_time(Calendar.System.now);
             initial_end_time = new Calendar.WallTime.from_exact_time(
-                Calendar.now().adjust_time(1, Calendar.TimeUnit.HOUR));
+                Calendar.System.now.adjust_time(1, Calendar.TimeUnit.HOUR));
         }
         
         // initialize start and end time (as in, wall clock time)
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index 73619db..6673f70 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -98,7 +98,7 @@ public class MainWindow : Gtk.ApplicationWindow {
     
     private void on_new_event() {
         // create all-day event for today
-        Calendar.DateSpan initial = new Calendar.DateSpan(Calendar.today, Calendar.today);
+        Calendar.DateSpan initial = new Calendar.DateSpan(Calendar.System.today, Calendar.System.today);
         
         // revert to today's date and use the widget for the popover
         create_event(null, initial, null, current_view.today(), null);
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index 151f47f..f6bf6d0 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -35,8 +35,8 @@ public class ShowEvent : Gtk.Grid {
         // if any dates are not in current year, display year in all dates
         Calendar.Date.PrettyFlag date_flags = Calendar.Date.PrettyFlag.NONE;
         Calendar.DateSpan date_span = event.get_event_date_span();
-        if (!date_span.start_date.year.equal_to(Calendar.today.year)
-            || !date_span.end_date.year.equal_to(Calendar.today.year)) {
+        if (!date_span.start_date.year.equal_to(Calendar.System.today.year)
+            || !date_span.end_date.year.equal_to(Calendar.System.today.year)) {
             date_flags |= Calendar.Date.PrettyFlag.INCLUDE_YEAR;
         }
         
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index fce628d..61ad64c 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -165,7 +165,7 @@ public class Cell : Gtk.EventBox {
         if (selected) {
             Gdk.cairo_set_source_rgba(ctx, RGBA_SELECTED);
             ctx.paint();
-        } else if (date != null && date.equal_to(Calendar.today)) {
+        } else if (date != null && date.equal_to(Calendar.System.today)) {
             Gdk.cairo_set_source_rgba(ctx, RGBA_CURRENT_DAY);
             ctx.paint();
         }
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index c6d5133..0572de8 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -108,7 +108,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
         notify[PROP_SHOW_OUTSIDE_MONTH].connect(update_cells);
         
         // update now that signal handlers are in place
-        month_of_year = Calendar.today.month_of_year();
+        month_of_year = Calendar.System.today.month_of_year();
         first_of_week = Calendar.FirstOfWeek.SUNDAY;
     }
     
@@ -132,13 +132,13 @@ public class Controllable : Gtk.Grid, View.Controllable {
     public Gtk.Widget today() {
         // since changing the date is expensive in terms of adding/removing subscriptions, only
         // update the property if it's actually different
-        Calendar.MonthOfYear now = Calendar.today.month_of_year();
+        Calendar.MonthOfYear now = Calendar.System.today.month_of_year();
         if (!now.equal_to(month_of_year))
             month_of_year = now;
         
-        assert(date_to_cell.has_key(Calendar.today));
+        assert(date_to_cell.has_key(Calendar.System.today));
         
-        return date_to_cell.get(Calendar.today);
+        return date_to_cell.get(Calendar.System.today);
     }
     
     /**
@@ -231,12 +231,12 @@ public class Controllable : Gtk.Grid, View.Controllable {
     
     private void on_month_of_year_changed() {
         current_label = month_of_year.full_name;
-        is_viewing_today = month_of_year.equal_to(Calendar.today.month_of_year());
+        is_viewing_today = month_of_year.equal_to(Calendar.System.today.month_of_year());
         
         // default date is first of month unless displaying current month, in which case it's
         // current date
         try {
-            default_date = is_viewing_today ? Calendar.today
+            default_date = is_viewing_today ? Calendar.System.today
                 : month_of_year.date_for(month_of_year.first_day_of_month());
         } catch (CalendarError calerr) {
             // this should always work
@@ -405,7 +405,7 @@ public class Controllable : Gtk.Grid, View.Controllable {
         
         // TODO: Define default time better
         Calendar.ExactTime start;
-        if(release_cell.date.equal_to(Calendar.today)) {
+        if(release_cell.date.equal_to(Calendar.System.today)) {
             start = new Calendar.ExactTime.now(new TimeZone.local());
         } else {
             start = new Calendar.ExactTime(new TimeZone.local(), release_cell.date,


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