diff options
Diffstat (limited to 'panels/datetime/tz.c')
-rw-r--r-- | panels/datetime/tz.c | 492 |
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)); + } +} + |