aboutsummaryrefslogtreecommitdiff
path: root/src/utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils.c')
-rw-r--r--src/utils.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..f4eb53f
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,307 @@
+/*
+ * 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>
+ */
+
+
+#include <datetime/utils.h>
+#include <datetime/settings-shared.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <locale.h>
+#include <langinfo.h>
+#include <string.h>
+
+/* Check the system locale setting to see if the format is 24-hour
+ time or 12-hour time */
+gboolean
+is_locale_12h(void)
+{
+ 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]!=NULL; i++)
+ if (strstr(t_fmt, formats_24h[i]) != NULL)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+split_settings_location(const gchar* location, gchar** zone, gchar** name)
+{
+ gchar* location_dup = g_strdup(location);
+ if(location_dup != NULL)
+ g_strstrip(location_dup);
+
+ gchar* first;
+ if(location_dup && (first = strchr(location_dup, ' ')))
+ *first = '\0';
+
+ if(zone)
+ *zone = location_dup;
+
+ if(name != NULL)
+ {
+ gchar* after = first ? g_strstrip(first + 1) : NULL;
+
+ if(after && *after)
+ {
+ *name = g_strdup(after);
+ }
+ else if (location_dup) // make the name from zone
+ {
+ 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 = ' ';
+
+ *name = after;
+ }
+ else
+ {
+ *name = NULL;
+ }
+ }
+}
+
+/**
+ * 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)
+{
+ gchar* zone;
+ gchar* name;
+ split_settings_location(timezone_, &zone, &name);
+
+ gchar* saved_zone;
+ gchar* saved_name;
+ split_settings_location(saved_location, &saved_zone, &saved_name);
+
+ gchar* rv;
+ if (g_strcmp0(zone, saved_zone) == 0)
+ {
+ rv = saved_name;
+ saved_name = NULL;
+ }
+ else
+ {
+ rv = name;
+ name = NULL;
+ }
+
+ g_free(zone);
+ g_free(name);
+ g_free(saved_zone);
+ g_free(saved_name);
+ return rv;
+}
+
+gchar*
+get_timezone_name(const gchar* timezone_, GSettings* settings)
+{
+ 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;
+}
+
+/***
+****
+***/
+
+typedef enum
+{
+ DATE_PROXIMITY_TODAY,
+ DATE_PROXIMITY_TOMORROW,
+ DATE_PROXIMITY_WEEK,
+ DATE_PROXIMITY_FAR
+}
+date_proximity_t;
+
+static date_proximity_t
+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 = g_date_time_add_days(now, 1);
+
+ 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(tomorrow);
+ }
+
+ // does it happen this week?
+ if (prox == DATE_PROXIMITY_FAR)
+ {
+ 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);
+
+ if (g_date_time_compare(time, week_bound) <= 0)
+ prox = DATE_PROXIMITY_WEEK;
+
+ g_date_time_unref(week_bound);
+ g_date_time_unref(week);
+ }
+
+ return prox;
+}
+
+const char*
+T_(const char *msg)
+{
+ /* 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;
+}
+
+
+/**
+ * _ 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”.
+ */
+char* generate_full_format_string_at_time (GDateTime* now,
+ GDateTime* then,
+ GDateTime* then_end)
+{
+ GString* ret = g_string_new (NULL);
+
+ if (then != NULL)
+ {
+ 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);
+
+ 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;
+ }
+ }
+
+ /* 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 g_string_free (ret, FALSE);
+}