aboutsummaryrefslogtreecommitdiff
path: root/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils.c')
-rw-r--r--src/utils.c611
1 files changed, 226 insertions, 385 deletions
diff --git a/src/utils.c b/src/utils.c
index c90f2e7..f4eb53f 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -1,212 +1,142 @@
-/* -*- Mode: C; coding: utf-8; indent-tabs-mode: nil; tab-width: 2 -*-
-
-A dialog for setting time and date preferences.
-
-Copyright 2010 Canonical Ltd.
-
-Authors:
- Michael Terry <michael.terry@canonical.com>
+/*
+ * Copyright 2010, 2014 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 <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ * Michael Terry <michael.terry@canonical.com>
+ * Charles Kerr <charles.kerr@canonical.com>
+ */
-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.
+#include <datetime/utils.h>
+#include <datetime/settings-shared.h>
-You should have received a copy of the GNU General Public License along
-with this program. If not, see <http://www.gnu.org/licenses/>.
-*/
+#include <glib.h>
+#include <glib/gi18n.h>
-#include <glib/gi18n-lib.h>
-#include <gio/gio.h>
#include <locale.h>
#include <langinfo.h>
#include <string.h>
-#include "utils.h"
-#include "settings-shared.h"
/* Check the system locale setting to see if the format is 24-hour
time or 12-hour time */
gboolean
-is_locale_12h (void)
+is_locale_12h(void)
{
- static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k", NULL};
- const char *t_fmt = nl_langinfo (T_FMT);
- int i;
+ int i;
+ static const char *formats_24h[] = {"%H", "%R", "%T", "%OH", "%k", NULL};
+ const char* t_fmt = nl_langinfo(T_FMT);
- for (i = 0; formats_24h[i]; ++i) {
- if (strstr (t_fmt, formats_24h[i])) {
- return FALSE;
- }
- }
+ for (i=0; formats_24h[i]!=NULL; i++)
+ if (strstr(t_fmt, formats_24h[i]) != NULL)
+ return FALSE;
- return TRUE;
+ return TRUE;
}
void
-split_settings_location (const gchar * location, gchar ** zone, gchar ** name)
+split_settings_location(const gchar* location, gchar** zone, gchar** name)
{
- gchar * location_dup;
- gchar * first;
+ gchar* location_dup = g_strdup(location);
+ if(location_dup != NULL)
+ g_strstrip(location_dup);
- location_dup = g_strdup (location);
- g_strstrip (location_dup);
+ gchar* first;
+ if(location_dup && (first = strchr(location_dup, ' ')))
+ *first = '\0';
- if ((first = strchr (location_dup, ' ')))
- *first = '\0';
-
- if (zone != NULL)
- {
- *zone = location_dup;
- }
+ if(zone)
+ *zone = location_dup;
- if (name != NULL)
+ if(name != NULL)
{
- gchar * after = first ? g_strstrip (first + 1) : NULL;
+ gchar* after = first ? g_strstrip(first + 1) : NULL;
- if (after && *after)
+ if(after && *after)
{
- *name = g_strdup (after);
+ *name = g_strdup(after);
}
- else /* make the name from zone */
+ else if (location_dup) // make the name from zone
{
- gchar * chr = strrchr (location_dup, '/');
- after = g_strdup (chr ? chr + 1 : location_dup);
+ gchar * chr = strrchr(location_dup, '/');
+ after = g_strdup(chr ? chr + 1 : location_dup);
- /* replace underscores with spaces */
- for (chr=after; chr && *chr; chr++)
- if (*chr == '_')
- *chr = ' ';
+ // replace underscores with spaces
+ for(chr=after; chr && *chr; chr++)
+ if(*chr == '_')
+ *chr = ' ';
- *name = after;
+ *name = after;
+ }
+ else
+ {
+ *name = NULL;
}
}
}
-gchar *
-get_current_zone_name (const gchar * location, GSettings * settings)
-{
- gchar * new_zone, * new_name;
- gchar * tz_name;
- gchar * old_zone, * old_name;
- gchar * rv;
-
- split_settings_location (location, &new_zone, &new_name);
-
- tz_name = g_settings_get_string (settings, SETTINGS_TIMEZONE_NAME_S);
- split_settings_location (tz_name, &old_zone, &old_name);
- g_free (tz_name);
-
- /* new_name is always just a sanitized version of a timezone.
- old_name is potentially a saved "pretty" version of a timezone name from
- geonames. So we prefer to use it if available and the zones match. */
-
- if (g_strcmp0 (old_zone, new_zone) == 0) {
- rv = old_name;
- old_name = NULL;
- }
- else {
- rv = new_name;
- new_name = NULL;
- }
-
- g_free (new_zone);
- g_free (old_zone);
- g_free (new_name);
- g_free (old_name);
-
- return rv;
-}
-
-/* Translate msg according to the locale specified by LC_TIME */
-static const char *
-T_(const char *msg)
+/**
+ * Our Locations come from two places: (1) direct user input and (2) ones
+ * guessed by the system, such as from geoclue or timedate1.
+ *
+ * Since the latter only have a timezone (eg, "America/Chicago") and the
+ * former have a descriptive name provided by the end user (eg,
+ * "America/Chicago Oklahoma City"), this function tries to make a
+ * more human-readable name by using the user-provided name if the guessed
+ * timezone matches the last one the user manually clicked on.
+ *
+ * In the example above, this allows the menuitem for the system-guessed
+ * timezone ("America/Chicago") to read "Oklahoma City" after the user clicks
+ * on the "Oklahoma City" menuitem.
+ */
+gchar*
+get_beautified_timezone_name(const char* timezone_, const char* saved_location)
{
- /* General strategy here is to make sure LANGUAGE is empty (since that
- trumps all LC_* vars) and then to temporarily swap LC_TIME and
- LC_MESSAGES. Then have gettext translate msg.
-
- We strdup the strings because the setlocale & *env functions do not
- guarantee anything about the storage used for the string, and thus
- the string may not be portably safe after multiple calls.
-
- Note that while you might think g_dcgettext would do the trick here,
- that actually looks in /usr/share/locale/XX/LC_TIME, not the
- LC_MESSAGES directory, so we won't find any translation there.
- */
- char *message_locale = g_strdup(setlocale(LC_MESSAGES, NULL));
- const char *time_locale = setlocale (LC_TIME, NULL);
- char *language = g_strdup(g_getenv("LANGUAGE"));
- const char *rv;
- if (language)
- g_unsetenv("LANGUAGE");
- setlocale(LC_MESSAGES, time_locale);
-
- /* Get the LC_TIME version */
- rv = _(msg);
-
- /* Put everything back the way it was */
- setlocale(LC_MESSAGES, message_locale);
- if (language)
- g_setenv("LANGUAGE", language, TRUE);
- g_free(message_locale);
- g_free(language);
- return rv;
-}
+ gchar* zone;
+ gchar* name;
+ split_settings_location(timezone_, &zone, &name);
-gchar *
-join_date_and_time_format_strings (const char * date_string,
- const char * time_string)
-{
- gchar * str;
+ gchar* saved_zone;
+ gchar* saved_name;
+ split_settings_location(saved_location, &saved_zone, &saved_name);
- if (date_string && time_string)
- {
- /* TRANSLATORS: This is a format string passed to strftime to combine the
- * date and the time. The value of "%s\xE2\x80\x82%s" will result in a
- * string like this in US English 12-hour time: 'Fri Jul 16 11:50 AM'.
- * The space in between date and time is a Unicode en space
- * (E28082 in UTF-8 hex). */
- str = g_strdup_printf (T_("%s\xE2\x80\x82%s"), date_string, time_string);
- }
- else if (date_string)
+ gchar* rv;
+ if (g_strcmp0(zone, saved_zone) == 0)
{
- str = g_strdup_printf (T_("%s"), date_string);
+ rv = saved_name;
+ saved_name = NULL;
}
- else /* time_string */
+ else
{
- str = g_strdup_printf (T_("%s"), time_string);
+ rv = name;
+ name = NULL;
}
- return str;
+ g_free(zone);
+ g_free(name);
+ g_free(saved_zone);
+ g_free(saved_name);
+ return rv;
}
-/***
-****
-***/
-
-static const gchar *
-get_default_header_time_format (gboolean twelvehour, gboolean show_seconds)
+gchar*
+get_timezone_name(const gchar* timezone_, GSettings* settings)
{
- const gchar * 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;
+ gchar* saved_location = g_settings_get_string(settings, SETTINGS_TIMEZONE_NAME_S);
+ gchar* rv = get_beautified_timezone_name(timezone_, saved_location);
+ g_free(saved_location);
+ return rv;
}
/***
@@ -215,252 +145,163 @@ get_default_header_time_format (gboolean twelvehour, gboolean show_seconds)
typedef enum
{
- DATE_PROXIMITY_TODAY,
- DATE_PROXIMITY_TOMORROW,
- DATE_PROXIMITY_WEEK,
- DATE_PROXIMITY_FAR
+ DATE_PROXIMITY_TODAY,
+ DATE_PROXIMITY_TOMORROW,
+ DATE_PROXIMITY_WEEK,
+ DATE_PROXIMITY_FAR
}
date_proximity_t;
static date_proximity_t
-get_date_proximity (GDateTime * now, GDateTime * time)
+getDateProximity(GDateTime* now, GDateTime* time)
{
- date_proximity_t prox = DATE_PROXIMITY_FAR;
- gint now_year, now_month, now_day;
- gint time_year, time_month, time_day;
-
- /* does it happen today? */
- g_date_time_get_ymd (now, &now_year, &now_month, &now_day);
- g_date_time_get_ymd (time, &time_year, &time_month, &time_day);
- if ((now_year == time_year) && (now_month == time_month) && (now_day == time_day))
- prox = DATE_PROXIMITY_TODAY;
-
- /* does it happen tomorrow? */
- if (prox == DATE_PROXIMITY_FAR)
- {
- GDateTime * tomorrow;
- gint tom_year, tom_month, tom_day;
-
- tomorrow = g_date_time_add_days (now, 1);
- g_date_time_get_ymd (tomorrow, &tom_year, &tom_month, &tom_day);
- if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day))
- prox = DATE_PROXIMITY_TOMORROW;
-
- g_date_time_unref (tomorrow);
- }
-
- /* does it happen this week? */
- if (prox == DATE_PROXIMITY_FAR)
+ date_proximity_t prox = DATE_PROXIMITY_FAR;
+ gint now_year, now_month, now_day;
+ gint time_year, time_month, time_day;
+
+ // does it happen today?
+ g_date_time_get_ymd(now, &now_year, &now_month, &now_day);
+ g_date_time_get_ymd(time, &time_year, &time_month, &time_day);
+ if ((now_year == time_year) && (now_month == time_month) && (now_day == time_day))
+ prox = DATE_PROXIMITY_TODAY;
+
+ // does it happen tomorrow?
+ if (prox == DATE_PROXIMITY_FAR)
{
- GDateTime * week;
- GDateTime * week_bound;
-
- week = g_date_time_add_days (now, 6);
- week_bound = g_date_time_new_local (g_date_time_get_year(week),
- g_date_time_get_month (week),
- g_date_time_get_day_of_month(week),
- 23, 59, 59.9);
+ GDateTime* tomorrow = g_date_time_add_days(now, 1);
- if (g_date_time_compare (time, week_bound) <= 0)
- prox = DATE_PROXIMITY_WEEK;
+ gint tom_year, tom_month, tom_day;
+ g_date_time_get_ymd(tomorrow, &tom_year, &tom_month, &tom_day);
+ if ((tom_year == time_year) && (tom_month == time_month) && (tom_day == time_day))
+ prox = DATE_PROXIMITY_TOMORROW;
- g_date_time_unref (week_bound);
- g_date_time_unref (week);
+ g_date_time_unref(tomorrow);
}
- return prox;
-}
-
-
-/*
- * "Terse" time & date format strings
- *
- * Used on the phone menu where space is at a premium, these strings
- * express the time and date in as brief a form as possible.
- *
- * Examples from spec:
- * 1. "Daily 6:30 AM"
- * 2. "5:07 PM" (note date is omitted; today's date is implicit)
- * 3. "Daily 12 PM" (note minutes are omitted for on-the-hour times)
- * 4. "Tomorrow 7 AM" (note "Tomorrow" is used instead of a day of week)
- */
-
-static const gchar *
-get_terse_date_format_string (date_proximity_t proximity)
-{
- const gchar * fmt;
-
- switch (proximity)
+ // does it happen this week?
+ if (prox == DATE_PROXIMITY_FAR)
{
- case DATE_PROXIMITY_TODAY:
- /* 'Today' is implicit in the terse case, so no string needed */
- fmt = NULL;
- break;
-
- case DATE_PROXIMITY_TOMORROW:
- fmt = T_("Tomorrow");
- break;
-
- case DATE_PROXIMITY_WEEK:
- /* a strftime(3) fmt string for abbreviated day of week */
- fmt = T_("%a");
- break;
-
- default:
- /* a strftime(3) fmt string for day-of-month and abbreviated month */
- fmt = T_("%d %b");
- break;
- }
-
- return fmt;
-}
+ GDateTime* week = g_date_time_add_days(now, 6);
+ GDateTime* week_bound = g_date_time_new_local(g_date_time_get_year(week),
+ g_date_time_get_month(week),
+ g_date_time_get_day_of_month(week),
+ 23, 59, 59.9);
-const gchar*
-get_terse_header_time_format_string (void)
-{
- const gboolean twelvehour = is_locale_12h ();
- const gboolean show_seconds = FALSE;
+ if (g_date_time_compare(time, week_bound) <= 0)
+ prox = DATE_PROXIMITY_WEEK;
- return get_default_header_time_format (twelvehour, show_seconds);
-}
-
-const gchar *
-get_terse_time_format_string (GDateTime * time)
-{
- const gchar * fmt;
-
- if (g_date_time_get_minute (time) != 0)
- {
- fmt = get_terse_header_time_format_string ();
- }
- else
- {
- /* a strftime(3) fmt string for a 12 hour on-the-hour time, eg "7 PM" */
- fmt = T_("%l %p");
+ g_date_time_unref(week_bound);
+ g_date_time_unref(week);
}
- return fmt;
-}
-
-gchar *
-generate_terse_format_string_at_time (GDateTime * now, GDateTime * time)
-{
- const date_proximity_t prox = get_date_proximity (now, time);
- const gchar * date_fmt = get_terse_date_format_string (prox);
- const gchar * time_fmt = get_terse_time_format_string (time);
- return join_date_and_time_format_strings (date_fmt, time_fmt);
+ return prox;
}
-/***
-**** FULL
-***/
-
-static const gchar *
-get_full_date_format_string (gboolean show_day, gboolean show_date, gboolean show_year)
+const char*
+T_(const char *msg)
{
- const char * fmt;
-
- if (show_day && show_date && show_year)
- /* TRANSLATORS: a strftime(3) format showing the weekday, date, and year */
- fmt = T_("%a %b %e %Y");
- else if (show_day && show_date)
- /* TRANSLATORS: a strftime(3) format showing the weekday and date */
- fmt = T_("%a %b %e");
- else if (show_day && show_year)
- /* TRANSLATORS: a strftime(3) format showing the weekday and year. */
- fmt = T_("%a %Y");
- else if (show_day)
- /* TRANSLATORS: a strftime(3) format showing the weekday. */
- fmt = T_("%a");
- else if (show_date && show_year)
- /* TRANSLATORS: a strftime(3) format showing the date and year */
- fmt = T_("%b %e %Y");
- else if (show_date)
- /* TRANSLATORS: a strftime(3) format showing the date */
- fmt = T_("%b %e");
- else if (show_year)
- /* TRANSLATORS: a strftime(3) format showing the year */
- fmt = T_("%Y");
- else
- fmt = NULL;
-
- return fmt;
+ /* General strategy here is to make sure LANGUAGE is empty (since that
+ trumps all LC_* vars) and then to temporarily swap LC_TIME and
+ LC_MESSAGES. Then have gettext translate msg.
+
+ We strdup the strings because the setlocale & *env functions do not
+ guarantee anything about the storage used for the string, and thus
+ the string may not be portably safe after multiple calls.
+
+ Note that while you might think g_dcgettext would do the trick here,
+ that actually looks in /usr/share/locale/XX/LC_TIME, not the
+ LC_MESSAGES directory, so we won't find any translation there.
+ */
+
+ gchar* message_locale = g_strdup(setlocale(LC_MESSAGES, NULL));
+ const char* time_locale = setlocale(LC_TIME, NULL);
+ gchar* language = g_strdup(g_getenv("LANGUAGE"));
+
+ if (language)
+ g_unsetenv("LANGUAGE");
+ setlocale(LC_MESSAGES, time_locale);
+
+ /* Get the LC_TIME version */
+ const char* rv = _(msg);
+
+ /* Put everything back the way it was */
+ setlocale(LC_MESSAGES, message_locale);
+ if (language)
+ g_setenv("LANGUAGE", language, TRUE);
+
+ g_free(message_locale);
+ g_free(language);
+ return rv;
}
-/*
- * "Full" time & date format strings
- *
- * These are used on the desktop menu & header and honors the
- * GSettings entries for 12/24hr mode and whether or not to show seconds.
- *
+/**
+ * _ a time today should be shown as just the time (e.g. “3:55 PM”)
+ * _ a full-day event today should be shown as “Today”
+ * _ a time any other day this week should be shown as the short version of the
+ * day and time (e.g. “Wed 3:55 PM”)
+ * _ a full-day event tomorrow should be shown as “Tomorrow”
+ * _ a full-day event another day this week should be shown as the
+ * weekday (e.g. “Friday”)
+ * _ a time after this week should be shown as the short version of the day,
+ * date, and time (e.g. “Wed 21 Apr 3:55 PM”)
+ * _ a full-day event after this week should be shown as the short version of
+ * the day and date (e.g. “Wed 21 Apr”).
+ * _ in addition, when presenting the times of upcoming events, the time should
+ * be followed by the timezone if it is different from the one the computer
+ * is currently set to. For example, “Wed 3:55 PM UTC−5”.
*/
-
-const gchar *
-get_full_time_format_string (GSettings * settings)
+char* generate_full_format_string_at_time (GDateTime* now,
+ GDateTime* then,
+ GDateTime* then_end)
{
- gboolean twelvehour;
- gboolean show_seconds;
-
- g_return_val_if_fail (settings != NULL, NULL);
-
- show_seconds = g_settings_get_boolean (settings, SETTINGS_SHOW_SECONDS_S);
+ GString* ret = g_string_new (NULL);
- switch (g_settings_get_enum (settings, SETTINGS_TIME_FORMAT_S))
+ if (then != NULL)
{
- case TIME_FORMAT_MODE_LOCALE_DEFAULT:
- twelvehour = is_locale_12h();
- break;
-
- case TIME_FORMAT_MODE_24_HOUR:
- twelvehour = FALSE;
- break;
-
- default:
- twelvehour = TRUE;
- break;
- }
-
- return get_default_header_time_format (twelvehour, show_seconds);
-}
-
-gchar *
-generate_full_format_string (gboolean show_day, gboolean show_date, gboolean show_year, GSettings * settings)
-{
- const gchar * date_fmt = get_full_date_format_string (show_day, show_date, show_year);
- const gchar * time_fmt = get_full_time_format_string (settings);
- return join_date_and_time_format_strings (date_fmt, time_fmt);
-}
-
-gchar *
-generate_full_format_string_at_time (GDateTime * now, GDateTime * time, GSettings * settings)
-{
- gboolean show_day;
- gboolean show_date;
+ const gboolean full_day = then_end && (g_date_time_difference(then_end, then) >= G_TIME_SPAN_DAY);
+ const date_proximity_t prox = getDateProximity(now, then);
- g_return_val_if_fail (now != NULL, NULL);
- g_return_val_if_fail (time != NULL, NULL);
- g_return_val_if_fail (settings != NULL, NULL);
+ if (full_day)
+ {
+ switch (prox)
+ {
+ case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("Today")); break;
+ case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow")); break;
+ case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%A")); break;
+ case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b")); break;
+ }
+ }
+ else if (is_locale_12h())
+ {
+ switch (prox)
+ {
+ case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("%l:%M %p")); break;
+ case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow\u2003%l:%M %p")); break;
+ case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%a\u2003%l:%M %p")); break;
+ case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b\u2003%l:%M %p")); break;
+ }
+ }
+ else
+ {
+ switch (prox)
+ {
+ case DATE_PROXIMITY_TODAY: g_string_assign (ret, T_("%H:%M")); break;
+ case DATE_PROXIMITY_TOMORROW: g_string_assign (ret, T_("Tomorrow\u2003%H:%M")); break;
+ case DATE_PROXIMITY_WEEK: g_string_assign (ret, T_("%a\u2003%H:%M")); break;
+ case DATE_PROXIMITY_FAR: g_string_assign (ret, T_("%a %d %b\u2003%H:%M")); break;
+ }
+ }
- switch (get_date_proximity (now, time))
- {
- case DATE_PROXIMITY_TODAY:
- show_day = FALSE;
- show_date = FALSE;
- break;
-
- case DATE_PROXIMITY_TOMORROW:
- case DATE_PROXIMITY_WEEK:
- show_day = FALSE;
- show_date = TRUE;
- break;
-
- default:
- show_day = TRUE;
- show_date = TRUE;
- break;
+ /* if it's an appointment in a different timezone (and doesn't run for a full day)
+ then the time should be followed by its timezone. */
+ if ((then_end != NULL) &&
+ (!full_day) &&
+ ((g_date_time_get_utc_offset(now) != g_date_time_get_utc_offset(then))))
+ {
+ g_string_append_printf (ret, " %s", g_date_time_get_timezone_abbreviation(then));
+ }
}
- return generate_full_format_string (show_day, show_date, FALSE, settings);
+ return g_string_free (ret, FALSE);
}
-