/* * Copyright 2013 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as published * by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . * * Authors: * Charles Kerr */ #include #include #include // T_() #include #include #include // setlocale() #include // nl_langinfo() #include // strstr() namespace ayatana { namespace indicator { namespace datetime { /*** **** ***/ namespace { void clear_timer(guint& tag) { if (tag) { g_source_remove(tag); tag = 0; } } gint calculate_milliseconds_until_next_second(const DateTime& now) { gint interval_usec; guint interval_msec; interval_usec = G_USEC_PER_SEC - g_date_time_get_microsecond(now.get()); interval_msec = (interval_usec + 999) / 1000; return interval_msec; } /* * We periodically rebuild the sections that have time format strings * that are dependent on the current time: * * 1. appointment menuitems' time format strings depend on the * current time; for example, they don't show the day of week * if the appointment is today. * * 2. location menuitems' time format strings depend on the * current time; for example, they don't show the day of the week * if the local date and location date are the same. * * 3. the "local date" menuitem in the calendar section is, * obviously, dependent on the local time. * * In short, we want to update whenever the number of days between two zone * might have changed. We do that by updating when either zone's day changes. * * Since not all UTC offsets are evenly divisible by hours * (examples: Newfoundland UTC-03:30, Nepal UTC+05:45), refreshing on the hour * is not enough. We need to refresh at HH:00, HH:15, HH:30, and HH:45. */ guint calculate_seconds_until_next_fifteen_minutes(GDateTime * now) { char * str; gint minute; guint seconds; GTimeSpan diff; GDateTime * next; GDateTime * start_of_next; minute = g_date_time_get_minute(now); minute = 15 - (minute % 15); next = g_date_time_add_minutes(now, minute); start_of_next = g_date_time_new_local(g_date_time_get_year(next), g_date_time_get_month(next), g_date_time_get_day_of_month(next), g_date_time_get_hour(next), g_date_time_get_minute(next), 0.1); str = g_date_time_format(start_of_next, "%F %T"); g_debug("%s %s the next timestamp rebuild will be at %s", G_STRLOC, G_STRFUNC, str); g_free(str); diff = g_date_time_difference(start_of_next, now); seconds = (diff + (G_TIME_SPAN_SECOND-1)) / G_TIME_SPAN_SECOND; g_date_time_unref(start_of_next); g_date_time_unref(next); return seconds; } } // unnamed namespace class Formatter::Impl { public: Impl(Formatter* owner, const std::shared_ptr& clock): m_owner(owner), m_clock(clock) { m_owner->header_format.changed().connect([this](const std::string& /*fmt*/){update_header();}); m_clock->minute_changed.connect([this](){update_header();}); update_header(); restartRelativeTimer(); } ~Impl() { clear_timer(m_header_seconds_timer); clear_timer(m_relative_timer); } private: static bool format_shows_seconds(const std::string& fmt) { return (fmt.find("%s") != std::string::npos) || (fmt.find("%S") != std::string::npos) || (fmt.find("%T") != std::string::npos) || (fmt.find("%X") != std::string::npos) || (fmt.find("%c") != std::string::npos); } void update_header() { // update the header property const auto fmt = m_owner->header_format.get(); const auto str = m_clock->localtime().format(fmt); m_owner->header.set(str); // if the header needs to show seconds, set a timer. if (format_shows_seconds(fmt)) start_header_timer(); else clear_timer(m_header_seconds_timer); } // we've got a header format that shows seconds, // so we need to update it every second void start_header_timer() { clear_timer(m_header_seconds_timer); const auto now = m_clock->localtime(); auto interval_msec = calculate_milliseconds_until_next_second(now); interval_msec += 50; // add a small margin to ensure the callback // fires /after/ next is reached m_header_seconds_timer = g_timeout_add_full(G_PRIORITY_HIGH, interval_msec, on_header_timer, this, nullptr); } static gboolean on_header_timer(gpointer gself) { static_cast(gself)->update_header(); return G_SOURCE_REMOVE; } private: void restartRelativeTimer() { clear_timer(m_relative_timer); const auto now = m_clock->localtime(); const auto seconds = calculate_seconds_until_next_fifteen_minutes(now.get()); m_relative_timer = g_timeout_add_seconds(seconds, onRelativeTimer, this); } static gboolean onRelativeTimer(gpointer gself) { auto self = static_cast(gself); self->m_owner->relative_format_changed(); self->restartRelativeTimer(); return G_SOURCE_REMOVE; } private: Formatter* const m_owner; guint m_header_seconds_timer = 0; guint m_relative_timer = 0; public: std::shared_ptr m_clock; }; /*** **** ***/ Formatter::Formatter(const std::shared_ptr& clock): p(new Formatter::Impl(this, clock)) { } Formatter::~Formatter() { } const char* Formatter::default_header_time_format(bool twelvehour, bool show_seconds) { const char* fmt; if (twelvehour && show_seconds) /* TRANSLATORS: a strftime(3) format for 12hr time w/seconds */ fmt = T_("%l:%M:%S %p"); else if (twelvehour) /* TRANSLATORS: a strftime(3) format for 12hr time */ fmt = T_("%l:%M %p"); else if (show_seconds) /* TRANSLATORS: a strftime(3) format for 24hr time w/seconds */ fmt = T_("%H:%M:%S"); else /* TRANSLATORS: a strftime(3) format for 24hr time */ fmt = T_("%H:%M"); return fmt; } /*** **** ***/ std::string Formatter::relative_format(GDateTime* then_begin, GDateTime* then_end) const { auto cstr = generate_full_format_string_at_time (p->m_clock->localtime().get(), then_begin, then_end); const std::string ret = cstr; g_free (cstr); return ret; } /*** **** ***/ } // namespace datetime } // namespace indicator } // namespace ayatana