[california] Update current date when date changes: Closes bgo#726139



commit abf84e744e1d7252ddd13c225427c9fbc97bdf43
Author: Jim Nelson <jim yorba org>
Date:   Thu Mar 20 16:47:30 2014 -0700

    Update current date when date changes: Closes bgo#726139
    
    Detecting if the current date has changed is done through two
    mechanisms: (a) if the timezone changes and that change results in
    a date change, and (b) polling the system clock occassionally for
    a date change.  The second feels wasteful but there's no system
    event I can locate that will inform an app when the date has changed
    and don't want to poll too infrequently in case the user/NTP updates
    the clock manually.

 src/calendar/calendar-date.vala        |   14 +++++-
 src/calendar/calendar-exact-time.vala  |    9 ++++
 src/calendar/calendar-system.vala      |   82 +++++++++++++++++++++++++++++---
 src/host/host-main-window.vala         |    3 +-
 src/host/host-show-event.vala          |    2 +
 src/view/month/month-cell.vala         |    8 +++
 src/view/month/month-controllable.vala |   16 ++++++-
 src/view/view-controllable.vala        |    3 +
 8 files changed, 127 insertions(+), 10 deletions(-)
---
diff --git a/src/calendar/calendar-date.vala b/src/calendar/calendar-date.vala
index a401342..65b7b2c 100644
--- a/src/calendar/calendar-date.vala
+++ b/src/calendar/calendar-date.vala
@@ -34,7 +34,12 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
         /**
          * Indicates that the year should be included in the return date string.
          */
-        INCLUDE_YEAR
+        INCLUDE_YEAR,
+        /**
+         * Indicates that the localized string for "Today" should not be used if the date matches
+         * { link System.today}.
+         */
+        NO_TODAY
     }
     
     public DayOfWeek day_of_week { get; private set; }
@@ -276,10 +281,17 @@ public class Date : BaseObject, Gee.Comparable<Date>, Gee.Hashable<Date> {
     /**
      * Returns the { link Date} in a prettified, localized format according to supplied
      * { link PrettyFlag}s.
+     *
+     * Returns "Today" (localized) if this matches { link System.today} unless the NO_TODAY flag
+     * or INCLUDE_YEAR flag is specified.
      */
     public string to_pretty_string(PrettyFlag flags) {
         bool abbrev = (flags & PrettyFlag.ABBREV) != 0;
         bool with_year = (flags & PrettyFlag.INCLUDE_YEAR) != 0;
+        bool no_today = (flags & PrettyFlag.NO_TODAY) != 0;
+        
+        if (!no_today && !with_year && equal_to(System.today))
+            return _("Today");
         
         unowned string fmt;
         if (abbrev)
diff --git a/src/calendar/calendar-exact-time.vala b/src/calendar/calendar-exact-time.vala
index 74078cd..518a2dc 100644
--- a/src/calendar/calendar-exact-time.vala
+++ b/src/calendar/calendar-exact-time.vala
@@ -153,6 +153,15 @@ public class ExactTime : BaseObject, Gee.Comparable<ExactTime>, Gee.Hashable<Exa
     }
     
     /**
+     * Returns the difference (in seconds) between this { link ExactTime} and another ExactTime.
+     *
+     * If the supplied ExactTime is earlier than this one, a negative value will be returned.
+     */
+    public int64 difference(ExactTime other) {
+        return date_time.difference(other.date_time) / TimeSpan.SECOND;
+    }
+    
+    /**
      * See DateTime.to_unix_time.
      */
     public time_t to_time_t() {
diff --git a/src/calendar/calendar-system.vala b/src/calendar/calendar-system.vala
index be1558b..8bd745e 100644
--- a/src/calendar/calendar-system.vala
+++ b/src/calendar/calendar-system.vala
@@ -20,12 +20,16 @@ public class System : BaseObject {
     private const string CLOCK_FORMAT_KEY = "clock-format";
     private const string CLOCK_FORMAT_24H = "24h";
     
+    private const int CHECK_DATE_PRIORITY = Priority.LOW;
+    private const int MIN_CHECK_DATE_INTERVAL_SEC = 1;
+    private const int MAX_CHECK_DATE_INTERVAL_SEC = 30;
+    
     public static System instance { get; private set; }
     
     /**
      * The current date according to the local timezone.
      *
-     * TODO: This currently does not update as the program executes.
+     * @see today_changed
      */
     public static Date today { get; private set; }
     
@@ -60,6 +64,14 @@ public class System : BaseObject {
     private static DBus.Properties timedated_properties;
     
     /**
+     * Fired when { link today} changes.
+     *
+     * This indicates that the system time has crossed midnight (potentially either direction since
+     * clock adjustments can happen for a variety of reasons).
+     */
+    public signal void today_changed(Calendar.Date old_today, Calendar.Date new_today);
+    
+    /**
      * Fired when { link is_24hr} changes.
      *
      * This means the user has changed the their clock format configuration.
@@ -72,14 +84,15 @@ public class System : BaseObject {
      * 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);
+    public signal void zone_changed(OlsonZone old_zone, OlsonZone new_zone);
     
     /**
      * Fired when { link local_timezone} changes due to system configuration changes.
      */
-    public signal void timezone_changed(Timezone new_timezone);
+    public signal void timezone_changed(Timezone old_timezone, Timezone new_timezone);
     
     private Settings system_clock_format_schema = new Settings(CLOCK_FORMAT_SCHEMA);
+    private uint date_timer_id = 0;
     
     private System() {
         zone = new OlsonZone(timedated_service.timezone);
@@ -89,6 +102,7 @@ public class System : BaseObject {
         // to be notified of changes as they occur
         timedated_properties.properties_changed.connect(on_timedated_properties_changed);
         
+        // monitor 12/24-hr setting for changes and update
         is_24hr = system_clock_format_schema.get_string(CLOCK_FORMAT_KEY) == CLOCK_FORMAT_24H;
         system_clock_format_schema.changed[CLOCK_FORMAT_KEY].connect(() => {
             bool new_is_24hr = system_clock_format_schema.get_string(CLOCK_FORMAT_KEY) == CLOCK_FORMAT_24H;
@@ -98,8 +112,19 @@ public class System : BaseObject {
             }
         });
         
-        // TODO: Tie this into the event loop so it's properly updated
+        // timezone change can potentially update "today"; unless there's a system event we can
+        // trap to indicate when the date has changed, use the event loop to check once every so
+        // often, but also attempt to calculate time until midnight to change along with it ...
+        // this may seem wasteful, but since the date can change for a lot of reasons (user
+        // intervention, clock drift, NTP, etc.) this is a simple way to stay on top of things
         today = new Date.now(Timezone.local);
+        date_timer_id = Timeout.add_seconds_full(CHECK_DATE_PRIORITY, next_check_today_interval_sec(),
+            check_today_changed);
+    }
+    
+    ~System() {
+        if (date_timer_id != 0)
+            Source.remove(date_timer_id);
     }
     
     internal static void preinit() throws IOError {
@@ -123,17 +148,60 @@ public class System : BaseObject {
         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) {
+            OlsonZone old_zone = zone;
             zone = new OlsonZone(timedated_service.timezone);
-            timezone = new Timezone(zone);
             debug("New local zone: %s", zone.to_string());
             
+            Timezone old_timezone = timezone;
+            timezone = new Timezone(zone);
+            
             // fire signals last in case a subscriber monitoring zone thinks that local_timezone
             // has also changed
-            zone_changed(zone);
-            timezone_changed(timezone);
+            zone_changed(old_zone, zone);
+            timezone_changed(old_timezone, timezone);
+            
+            // update current date, if necessary
+            update_today();
         }
     }
     
+    private int next_check_today_interval_sec() {
+        // get the amount of time until midnight, adding one back to account for the second needed
+        // to cross into the next day, and another one because this isn't rocket science but a
+        // best-effort kind of thing
+        ExactTime last_sec_of_day = new ExactTime(Timezone.local, today, WallTime.latest);
+        int64 sec_to_midnight = last_sec_of_day.difference(new ExactTime.now(Timezone.local)) + 2;
+        
+        // clamp values to (a) ensure zero is never passed to Timeout.add_seconds(), which could
+        // cause brief racing around midnight if close (but not close enough) to the appointed hour,
+        // and (b) not too far out in case the user changes the date and would like to see the
+        // change in California at some reasonable interval
+        return (int) sec_to_midnight.clamp(MIN_CHECK_DATE_INTERVAL_SEC, MAX_CHECK_DATE_INTERVAL_SEC);
+    }
+    
+    // See note in constructor for logic behind this SourceFunc
+    private bool check_today_changed() {
+        update_today();
+        
+        // reschedule w/ the next interval
+        date_timer_id = Timeout.add_seconds_full(CHECK_DATE_PRIORITY, next_check_today_interval_sec(),
+            check_today_changed);
+        
+        return false;
+    }
+    
+    private void update_today() {
+        Date new_today = new Date.now(Timezone.local);
+        if (new_today.equal_to(today))
+            return;
+        
+        Date old_today = today;
+        today = new_today;
+        debug("Date changed: %s", new_today.to_string());
+        
+        today_changed(old_today, new_today);
+    }
+    
     public override string to_string() {
         return get_class().get_type().name();
     }
diff --git a/src/host/host-main-window.vala b/src/host/host-main-window.vala
index c7465f6..50934e4 100644
--- a/src/host/host-main-window.vala
+++ b/src/host/host-main-window.vala
@@ -41,7 +41,8 @@ public class MainWindow : Gtk.ApplicationWindow {
         
         bool rtl = get_direction () == Gtk.TextDirection.RTL;
         
-        Gtk.Button today = new Gtk.Button.with_label(_("Today"));
+        Gtk.Button today = new Gtk.Button.with_label(_("_Today"));
+        today.use_underline = true;
         today.clicked.connect(() => { current_view.today(); });
         
         Gtk.Button prev = new Gtk.Button.from_icon_name(rtl ? "go-previous-rtl-symbolic" : 
"go-previous-symbolic", Gtk.IconSize.MENU);
diff --git a/src/host/host-show-event.vala b/src/host/host-show-event.vala
index dd87d92..1c8af92 100644
--- a/src/host/host-show-event.vala
+++ b/src/host/host-show-event.vala
@@ -33,10 +33,12 @@ public class ShowEvent : Gtk.Grid, Interaction {
         
         build_display();
         Calendar.System.instance.is_24hr_changed.connect(build_display);
+        Calendar.System.instance.today_changed.connect(build_display);
     }
     
     ~ShowEvent() {
         Calendar.System.instance.is_24hr_changed.disconnect(build_display);
+        Calendar.System.instance.today_changed.disconnect(build_display);
     }
     
     private void build_display() {
diff --git a/src/view/month/month-cell.vala b/src/view/month/month-cell.vala
index 2aa450f..ba732a4 100644
--- a/src/view/month/month-cell.vala
+++ b/src/view/month/month-cell.vala
@@ -91,12 +91,14 @@ public class Cell : Gtk.EventBox {
         notify["date"].connect(queue_draw);
         notify["selected"].connect(queue_draw);
         Calendar.System.instance.is_24hr_changed.connect(on_24hr_changed);
+        Calendar.System.instance.today_changed.connect(on_today_changed);
         
         canvas.draw.connect(on_draw);
     }
     
     ~Cell() {
         Calendar.System.instance.is_24hr_changed.disconnect(on_24hr_changed);
+        Calendar.System.instance.today_changed.disconnect(on_today_changed);
     }
     
     internal static void init() {
@@ -159,6 +161,12 @@ public class Cell : Gtk.EventBox {
             queue_draw();
     }
     
+    private void on_today_changed(Calendar.Date old_today, Calendar.Date new_today) {
+        // need to know re: redrawing background color to indicate current day
+        if (date != null && (date.equal_to(old_today) || date.equal_to(new_today)))
+            queue_draw();
+    }
+    
     private void on_span_updated(Object object, ParamSpec param) {
         if (date == null)
             return;
diff --git a/src/view/month/month-controllable.vala b/src/view/month/month-controllable.vala
index bff8f18..de49d3d 100644
--- a/src/view/month/month-controllable.vala
+++ b/src/view/month/month-controllable.vala
@@ -111,11 +111,16 @@ public class Controllable : Gtk.Grid, View.Controllable {
         notify[PROP_MONTH_OF_YEAR].connect(on_month_of_year_changed);
         notify[PROP_FIRST_OF_WEEK].connect(update_first_of_week);
         notify[PROP_SHOW_OUTSIDE_MONTH].connect(update_cells);
+        Calendar.System.instance.today_changed.connect(on_today_changed);
         
         // update now that signal handlers are in place
         month_of_year = Calendar.System.today.month_of_year();
     }
     
+    ~Controllable() {
+        Calendar.System.instance.today_changed.disconnect(on_today_changed);
+    }
+    
     /**
      * @inheritDoc
      */
@@ -237,9 +242,18 @@ public class Controllable : Gtk.Grid, View.Controllable {
         update_subscription();
     }
     
+    private void update_is_viewing_today() {
+        is_viewing_today = month_of_year.equal_to(Calendar.System.today.month_of_year());
+    }
+    
+    private void on_today_changed() {
+        // don't update view but indicate if it's still in view
+        update_is_viewing_today();
+    }
+    
     private void on_month_of_year_changed() {
         current_label = month_of_year.full_name;
-        is_viewing_today = month_of_year.equal_to(Calendar.System.today.month_of_year());
+        update_is_viewing_today();
         
         // default date is first of month unless displaying current month, in which case it's
         // current date
diff --git a/src/view/view-controllable.vala b/src/view/view-controllable.vala
index 9f83936..0346bf9 100644
--- a/src/view/view-controllable.vala
+++ b/src/view/view-controllable.vala
@@ -28,6 +28,9 @@ public interface Controllable : Object {
     /**
      * Flag indicating if the current calendar unit matches the unit the { link today} method
      * could jump to.
+     *
+     * This value should update dynamically as { link Calendar.System.today} changes.  There's no
+     * requirement for the { link Controllable} to change its view as the day changes, however.
      */
     public abstract bool is_viewing_today { get; protected set; }
     


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