summaryrefslogtreecommitdiffstats
path: root/panels/datetime/tz.c
diff options
context:
space:
mode:
Diffstat (limited to 'panels/datetime/tz.c')
-rw-r--r--panels/datetime/tz.c492
1 files changed, 492 insertions, 0 deletions
diff --git a/panels/datetime/tz.c b/panels/datetime/tz.c
new file mode 100644
index 0000000..4fe2305
--- /dev/null
+++ b/panels/datetime/tz.c
@@ -0,0 +1,492 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Generic timezone utilities.
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Authors: Hans Petter Jansson <hpj@ximian.com>
+ *
+ * Largely based on Michael Fulbright's work on Anaconda.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY 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/>.
+ */
+
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include <ctype.h>
+#include "tz.h"
+#include "cc-datetime-resources.h"
+
+
+/* Forward declarations for private functions */
+
+static float convert_pos (gchar *pos, int digits);
+static int compare_country_names (const void *a, const void *b);
+static void sort_locations_by_country (GPtrArray *locations);
+static gchar * tz_data_file_get (void);
+static void load_backward_tz (TzDB *tz_db);
+
+/* ---------------- *
+ * Public interface *
+ * ---------------- */
+TzDB *
+tz_load_db (void)
+{
+ g_autofree gchar *tz_data_file = NULL;
+ TzDB *tz_db;
+ FILE *tzfile;
+ char buf[4096];
+
+ tz_data_file = tz_data_file_get ();
+ if (!tz_data_file) {
+ g_warning ("Could not get the TimeZone data file name");
+ return NULL;
+ }
+ tzfile = fopen (tz_data_file, "r");
+ if (!tzfile) {
+ g_warning ("Could not open *%s*\n", tz_data_file);
+ return NULL;
+ }
+
+ tz_db = g_new0 (TzDB, 1);
+ tz_db->locations = g_ptr_array_new ();
+
+ while (fgets (buf, sizeof(buf), tzfile))
+ {
+ g_auto(GStrv) tmpstrarr = NULL;
+ g_autofree gchar *latstr = NULL;
+ g_autofree gchar *lngstr = NULL;
+ gchar *p;
+ TzLocation *loc;
+
+ if (*buf == '#') continue;
+
+ g_strchomp(buf);
+ tmpstrarr = g_strsplit(buf,"\t", 6);
+
+ latstr = g_strdup (tmpstrarr[1]);
+ p = latstr + 1;
+ while (*p != '-' && *p != '+') p++;
+ lngstr = g_strdup (p);
+ *p = '\0';
+
+ loc = g_new0 (TzLocation, 1);
+ loc->country = g_strdup (tmpstrarr[0]);
+ loc->zone = g_strdup (tmpstrarr[2]);
+ loc->latitude = convert_pos (latstr, 2);
+ loc->longitude = convert_pos (lngstr, 3);
+
+#ifdef __sun
+ if (tmpstrarr[3] && *tmpstrarr[3] == '-' && tmpstrarr[4])
+ loc->comment = g_strdup (tmpstrarr[4]);
+
+ if (tmpstrarr[3] && *tmpstrarr[3] != '-' && !islower(loc->zone)) {
+ TzLocation *locgrp;
+
+ /* duplicate entry */
+ locgrp = g_new0 (TzLocation, 1);
+ locgrp->country = g_strdup (tmpstrarr[0]);
+ locgrp->zone = g_strdup (tmpstrarr[3]);
+ locgrp->latitude = convert_pos (latstr, 2);
+ locgrp->longitude = convert_pos (lngstr, 3);
+ locgrp->comment = (tmpstrarr[4]) ? g_strdup (tmpstrarr[4]) : NULL;
+
+ g_ptr_array_add (tz_db->locations, (gpointer) locgrp);
+ }
+#else
+ loc->comment = (tmpstrarr[3]) ? g_strdup(tmpstrarr[3]) : NULL;
+#endif
+
+ g_ptr_array_add (tz_db->locations, (gpointer) loc);
+ }
+
+ fclose (tzfile);
+
+ /* now sort by country */
+ sort_locations_by_country (tz_db->locations);
+
+ /* Load up the hashtable of backward links */
+ load_backward_tz (tz_db);
+
+ return tz_db;
+}
+
+static void
+tz_location_free (TzLocation *loc)
+{
+ g_free (loc->country);
+ g_free (loc->zone);
+ g_free (loc->comment);
+
+ g_free (loc);
+}
+
+void
+tz_db_free (TzDB *db)
+{
+ g_ptr_array_foreach (db->locations, (GFunc) tz_location_free, NULL);
+ g_ptr_array_free (db->locations, TRUE);
+ g_hash_table_destroy (db->backward);
+ g_free (db);
+}
+
+GPtrArray *
+tz_get_locations (TzDB *db)
+{
+ return db->locations;
+}
+
+
+gchar *
+tz_location_get_country (TzLocation *loc)
+{
+ return loc->country;
+}
+
+
+gchar *
+tz_location_get_zone (TzLocation *loc)
+{
+ return loc->zone;
+}
+
+
+gchar *
+tz_location_get_comment (TzLocation *loc)
+{
+ return loc->comment;
+}
+
+
+void
+tz_location_get_position (TzLocation *loc, double *longitude, double *latitude)
+{
+ *longitude = loc->longitude;
+ *latitude = loc->latitude;
+}
+
+/* For timezone map display purposes, we try to highlight regions of the
+ * world that keep the same time. There is no reasonable API to discover
+ * this; at the moment we just group timezones by their non-daylight-savings
+ * UTC offset and hope that's good enough. However, in some cases that
+ * produces confusing results. For example, Irish Standard Time is legally
+ * defined as the country's summer time, with a negative DST offset in
+ * winter; but this results in the same observed clock times as countries
+ * that observe Western European (Summer) Time, not those that observe
+ * Central European (Summer) Time, so we should group Ireland with the
+ * former, matching the grouping implied by data/timezone_*.png.
+ *
+ * This is something of a hack, and there remain other problems with
+ * timezone grouping: for example, grouping timezones north and south of the
+ * equator together where DST is observed at different times of the year is
+ * dubious.
+ */
+struct {
+ const char *zone;
+ gint offset;
+} base_offset_overrides[] = {
+ { "Europe/Dublin", 0 },
+};
+
+glong
+tz_location_get_base_utc_offset (TzLocation *loc)
+{
+ g_autoptr(TzInfo) tz_info = NULL;
+ glong offset;
+ guint i;
+
+ tz_info = tz_info_from_location (loc);
+ offset = tz_info->utc_offset + (tz_info->daylight ? -3600 : 0);
+
+ for (i = 0; i < G_N_ELEMENTS (base_offset_overrides); i++) {
+ if (g_str_equal (loc->zone, base_offset_overrides[i].zone)) {
+ offset = base_offset_overrides[i].offset;
+ break;
+ }
+ }
+
+ return offset;
+}
+
+TzInfo *
+tz_info_from_location (TzLocation *loc)
+{
+ TzInfo *tzinfo;
+ time_t curtime;
+ struct tm *curzone;
+ g_autofree gchar *tz_env_value = NULL;
+
+ g_return_val_if_fail (loc != NULL, NULL);
+ g_return_val_if_fail (loc->zone != NULL, NULL);
+
+ tz_env_value = g_strdup (getenv ("TZ"));
+ setenv ("TZ", loc->zone, 1);
+
+#if 0
+ tzset ();
+#endif
+ tzinfo = g_new0 (TzInfo, 1);
+
+ curtime = time (NULL);
+ curzone = localtime (&curtime);
+
+#ifndef __sun
+ tzinfo->tzname = g_strdup (curzone->tm_zone);
+ tzinfo->utc_offset = curzone->tm_gmtoff;
+#else
+ tzinfo->tzname = NULL;
+ tzinfo->utc_offset = 0;
+#endif
+
+ tzinfo->daylight = curzone->tm_isdst;
+
+ if (tz_env_value)
+ setenv ("TZ", tz_env_value, 1);
+ else
+ unsetenv ("TZ");
+
+ return tzinfo;
+}
+
+
+void
+tz_info_free (TzInfo *tzinfo)
+{
+ g_return_if_fail (tzinfo != NULL);
+
+ if (tzinfo->tzname) g_free (tzinfo->tzname);
+ g_free (tzinfo);
+}
+
+struct {
+ const char *orig;
+ const char *dest;
+} aliases[] = {
+ { "Asia/Istanbul", "Europe/Istanbul" }, /* Istanbul is in both Europe and Asia */
+ { "Europe/Nicosia", "Asia/Nicosia" }, /* Ditto */
+ { "EET", "Europe/Istanbul" }, /* Same tz as the 2 above */
+ { "HST", "Pacific/Honolulu" },
+ { "WET", "Europe/Brussels" }, /* Other name for the mainland Europe tz */
+ { "CET", "Europe/Brussels" }, /* ditto */
+ { "MET", "Europe/Brussels" },
+ { "Etc/Zulu", "Etc/GMT" },
+ { "Etc/UTC", "Etc/GMT" },
+ { "GMT", "Etc/GMT" },
+ { "Greenwich", "Etc/GMT" },
+ { "Etc/UCT", "Etc/GMT" },
+ { "Etc/GMT0", "Etc/GMT" },
+ { "Etc/GMT+0", "Etc/GMT" },
+ { "Etc/GMT-0", "Etc/GMT" },
+ { "Etc/Universal", "Etc/GMT" },
+ { "PST8PDT", "America/Los_Angeles" }, /* Other name for the Atlantic tz */
+ { "EST", "America/New_York" }, /* Other name for the Eastern tz */
+ { "EST5EDT", "America/New_York" }, /* ditto */
+ { "CST6CDT", "America/Chicago" }, /* Other name for the Central tz */
+ { "MST", "America/Denver" }, /* Other name for the mountain tz */
+ { "MST7MDT", "America/Denver" }, /* ditto */
+};
+
+static gboolean
+compare_timezones (const char *a,
+ const char *b)
+{
+ if (g_str_equal (a, b))
+ return TRUE;
+ if (strchr (b, '/') == NULL) {
+ g_autofree gchar *prefixed = NULL;
+
+ prefixed = g_strdup_printf ("/%s", b);
+ if (g_str_has_suffix (a, prefixed))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+char *
+tz_info_get_clean_name (TzDB *tz_db,
+ const char *tz)
+{
+ char *ret;
+ const char *timezone;
+ guint i;
+ gboolean replaced;
+
+ /* Remove useless prefixes */
+ if (g_str_has_prefix (tz, "right/"))
+ tz = tz + strlen ("right/");
+ else if (g_str_has_prefix (tz, "posix/"))
+ tz = tz + strlen ("posix/");
+
+ /* Here start the crazies */
+ replaced = FALSE;
+
+ for (i = 0; i < G_N_ELEMENTS (aliases); i++) {
+ if (compare_timezones (tz, aliases[i].orig)) {
+ replaced = TRUE;
+ timezone = aliases[i].dest;
+ break;
+ }
+ }
+
+ /* Try again! */
+ if (!replaced) {
+ /* Ignore crazy solar times from the '80s */
+ if (g_str_has_prefix (tz, "Asia/Riyadh") ||
+ g_str_has_prefix (tz, "Mideast/Riyadh")) {
+ timezone = "Asia/Riyadh";
+ replaced = TRUE;
+ }
+ }
+
+ if (!replaced)
+ timezone = tz;
+
+ ret = g_hash_table_lookup (tz_db->backward, timezone);
+ if (ret == NULL)
+ return g_strdup (timezone);
+ return g_strdup (ret);
+}
+
+/* ----------------- *
+ * Private functions *
+ * ----------------- */
+
+static gchar *
+tz_data_file_get (void)
+{
+ gchar *file;
+
+ file = g_strdup (TZ_DATA_FILE);
+
+ return file;
+}
+
+static float
+convert_pos (gchar *pos, int digits)
+{
+ gchar whole[10];
+ gchar *fraction;
+ gint i;
+ float t1, t2;
+
+ if (!pos || strlen(pos) < 4 || digits > 9) return 0.0;
+
+ for (i = 0; i < digits + 1; i++) whole[i] = pos[i];
+ whole[i] = '\0';
+ fraction = pos + digits + 1;
+
+ t1 = g_strtod (whole, NULL);
+ t2 = g_strtod (fraction, NULL);
+
+ if (t1 >= 0.0) return t1 + t2/pow (10.0, strlen(fraction));
+ else return t1 - t2/pow (10.0, strlen(fraction));
+}
+
+
+#if 0
+
+/* Currently not working */
+static void
+free_tzdata (TzLocation *tz)
+{
+
+ if (tz->country)
+ g_free(tz->country);
+ if (tz->zone)
+ g_free(tz->zone);
+ if (tz->comment)
+ g_free(tz->comment);
+
+ g_free(tz);
+}
+#endif
+
+
+static int
+compare_country_names (const void *a, const void *b)
+{
+ const TzLocation *tza = * (TzLocation **) a;
+ const TzLocation *tzb = * (TzLocation **) b;
+
+ return strcmp (tza->zone, tzb->zone);
+}
+
+
+static void
+sort_locations_by_country (GPtrArray *locations)
+{
+ qsort (locations->pdata, locations->len, sizeof (gpointer),
+ compare_country_names);
+}
+
+static void
+load_backward_tz (TzDB *tz_db)
+{
+ g_auto(GStrv) lines = NULL;
+ g_autoptr(GBytes) bytes = NULL;
+ const char *contents;
+ guint i;
+
+ tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ bytes = g_resources_lookup_data ("/org/gnome/control-center/datetime/backward",
+ G_RESOURCE_LOOKUP_FLAGS_NONE, NULL);
+ contents = (const char *) g_bytes_get_data (bytes, NULL);
+
+ lines = g_strsplit (contents, "\n", -1);
+
+ for (i = 0; lines[i] != NULL; i++)
+ {
+ g_auto(GStrv) items = NULL;
+ guint j;
+ char *real, *alias;
+
+ if (g_ascii_strncasecmp (lines[i], "Link\t", 5) != 0)
+ continue;
+
+ items = g_strsplit (lines[i], "\t", -1);
+ real = NULL;
+ alias = NULL;
+ /* Skip the "Link<tab>" part */
+ for (j = 1; items[j] != NULL; j++)
+ {
+ if (items[j][0] == '\0')
+ continue;
+ if (real == NULL)
+ {
+ real = items[j];
+ continue;
+ }
+ alias = items[j];
+ break;
+ }
+
+ if (real == NULL || alias == NULL)
+ g_warning ("Could not parse line: %s", lines[i]);
+
+ /* We don't need more than one name for it */
+ if (g_str_equal (real, "Etc/UTC") ||
+ g_str_equal (real, "Etc/UCT"))
+ real = "Etc/GMT";
+
+ g_hash_table_insert (tz_db->backward, g_strdup (alias), g_strdup (real));
+ }
+}
+