summaryrefslogtreecommitdiff
path: root/capplets/time-admin/src/time-zone.c
diff options
context:
space:
mode:
authorzhuyaliang <[email protected]>2019-06-10 15:08:04 +0800
committerraveit65 <[email protected]>2019-06-14 12:05:04 +0200
commit6cb1deca1340a8b47d7b041bb3510fbe92da9580 (patch)
treee735c2f782843d334be64bbab602d560cb9e04e4 /capplets/time-admin/src/time-zone.c
parent72dabd15f2495ff92310b265c10f6b30f58589c7 (diff)
downloadmate-control-center-6cb1deca1340a8b47d7b041bb3510fbe92da9580.tar.bz2
mate-control-center-6cb1deca1340a8b47d7b041bb3510fbe92da9580.tar.xz
add time-admin source code
Diffstat (limited to 'capplets/time-admin/src/time-zone.c')
-rw-r--r--capplets/time-admin/src/time-zone.c661
1 files changed, 661 insertions, 0 deletions
diff --git a/capplets/time-admin/src/time-zone.c b/capplets/time-admin/src/time-zone.c
new file mode 100644
index 00000000..dfb86f3b
--- /dev/null
+++ b/capplets/time-admin/src/time-zone.c
@@ -0,0 +1,661 @@
+/* time-admin
+* Copyright (C) 2018 zhuyaliang https://github.com/zhuyaliang/
+*
+* 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 3 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 <https://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "time-zone.h"
+#include "time-map.h"
+#include "time-tool.h"
+#include <math.h>
+#define MATE_DESKTOP_USE_UNSTABLE_API
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <libintl.h>
+
+#include <libmate-desktop/mate-languages.h>
+
+
+#define DEFAULT_TZ "Europe/London"
+#define BACKFILE "/usr/share/mate-time-admin/map/backward"
+static void LocationChanged(TimezoneMap *map,
+ TzLocation *location,TimeAdmin *ta);
+enum {
+ CITY_COL_CITY_HUMAN_READABLE,
+ CITY_COL_ZONE,
+ CITY_NUM_COLS
+};
+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));
+}
+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)
+{
+ FILE *fp;
+ char buf[128] = { 0 };
+
+ tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ fp = fopen(BACKFILE,"r");
+ if(fp == NULL)
+ {
+ g_error("%s does not exist\r\n",BACKFILE);
+
+ }
+ while(fgets(buf,128,fp))
+ {
+ g_auto(GStrv) items = NULL;
+ guint j;
+ char *real, *alias;
+
+ if (g_ascii_strncasecmp (buf, "Link\t", 5) != 0)
+ continue;
+
+ items = g_strsplit (buf, "\t", -1);
+ real = NULL;
+ alias = NULL;
+ 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", buf);
+
+ /* 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));
+
+ }
+ fclose(fp);
+}
+
+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;
+ 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 GtkWidget *GetTimeZoneMap(TimeAdmin *ta)
+{
+ GtkWidget *map;
+ g_autoptr(GtkEntryCompletion) completion = NULL;
+
+ map = (GtkWidget *) timezone_map_new ();
+ /*
+ g_signal_connect_object (map,
+ "location-changed",
+ G_CALLBACK (LocationChanged),
+ map,
+ G_CONNECT_SWAPPED);
+ */
+ g_signal_connect (map,
+ "location-changed",
+ G_CALLBACK (LocationChanged),
+ ta);
+
+ completion = gtk_entry_completion_new ();
+ gtk_entry_set_completion (GTK_ENTRY (ta->TimezoneEntry), completion);
+ gtk_entry_completion_set_model (completion, GTK_TREE_MODEL (ta->CityListStore));
+ gtk_entry_completion_set_text_column (completion, CITY_COL_CITY_HUMAN_READABLE);
+
+
+ return map;
+}
+static char *
+translated_city_name (TzLocation *loc)
+{
+ g_autofree gchar *zone_translated = NULL;
+ g_auto(GStrv) split_translated = NULL;
+ g_autofree gchar *country = NULL;
+ gchar *name;
+ gint length;
+
+ zone_translated = g_strdup (dgettext (GETTEXT_PACKAGE_TIMEZONES, loc->zone));
+ g_strdelimit (zone_translated, "_", ' ');
+ split_translated = g_regex_split_simple ("[\\x{2044}\\x{2215}\\x{29f8}\\x{ff0f}/]",
+ zone_translated,
+ 0, 0);
+
+ length = g_strv_length (split_translated);
+
+ country = mate_get_country_from_code (loc->country, NULL);
+ name = g_strdup_printf (C_("timezone loc", "%s, %s"),
+ split_translated[length-1],
+ country);
+
+ return name;
+}
+
+static void
+update_timezone (TimezoneMap *map)
+{
+ g_autofree gchar *bubble_text = NULL;
+ g_autofree gchar *city_country = NULL;
+ g_autofree gchar *utc_label = NULL;
+ g_autofree gchar *time_label = NULL;
+ g_autofree gchar *tz_desc = NULL;
+ TzLocation *current_location;
+ GDateTime *date;
+
+ date = g_date_time_new_now_local ();
+ current_location = timezone_map_get_location (TIMEZONEMAP (map));
+ city_country = translated_city_name (current_location);
+
+ utc_label = g_date_time_format (date, _("UTC%:::z"));
+
+ tz_desc = g_strdup_printf ( "%s (%s)",
+ g_date_time_get_timezone_abbreviation (date),
+ utc_label);
+ time_label = g_date_time_format (date, _("%R"));
+
+ bubble_text = g_strdup_printf ("<b>%s</b>\n"
+ "<small>%s</small>\n"
+ "<b>%s</b>",
+ tz_desc,
+ city_country,
+ time_label);
+ timezone_map_set_bubble_text (TIMEZONEMAP (map), bubble_text);
+}
+
+static void LocationChanged(TimezoneMap *map,
+ TzLocation *location,
+ TimeAdmin *ta)
+{
+
+ update_timezone (map);
+}
+static void
+get_initial_timezone (TimeAdmin *ta)
+{
+ const gchar *timezone;
+
+ timezone = GetTimeZone(ta);
+
+ if (timezone == NULL ||
+ !timezone_map_set_timezone (TIMEZONEMAP (ta->map), timezone))
+ {
+ g_warning ("Timezone '%s' is unhandled,setting %s as default", timezone ? timezone : "(null)", DEFAULT_TZ);
+ timezone_map_set_timezone (TIMEZONEMAP (ta->map), DEFAULT_TZ);
+ }
+ update_timezone (TIMEZONEMAP(ta->map));
+}
+static void LoadCities (TzLocation *loc,
+ GtkListStore *CityStore)
+{
+ g_autofree gchar *human_readable = NULL;
+
+ human_readable = translated_city_name (loc);
+ gtk_list_store_insert_with_values (CityStore,
+ NULL,
+ 0,
+ CITY_COL_CITY_HUMAN_READABLE,
+ human_readable,
+ CITY_COL_ZONE,
+ loc->zone,
+ -1);
+}
+
+static void CreateCityList(TimeAdmin *ta)
+{
+ g_autoptr(TzDB) db = NULL;
+
+ ta->CityListStore = gtk_list_store_new (CITY_NUM_COLS,G_TYPE_STRING,G_TYPE_STRING);
+
+ db = tz_load_db ();
+ g_ptr_array_foreach (db->locations, (GFunc) LoadCities, ta->CityListStore);
+
+}
+static GtkWidget *CreateZoneFrame(TimeAdmin *ta)
+{
+ GtkWidget *TimeZoneFrame;
+
+ TimeZoneFrame = gtk_frame_new (_("Time Zone"));
+ gtk_widget_set_size_request(TimeZoneFrame,300,200);
+ gtk_frame_set_shadow_type(GTK_FRAME(TimeZoneFrame),GTK_SHADOW_NONE);
+
+ return TimeZoneFrame;
+}
+static GtkWidget *CreateZoneScrolled(TimeAdmin *ta)
+{
+ GtkWidget *Scrolled;
+ Scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (Scrolled),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (Scrolled),
+ GTK_SHADOW_IN);
+
+ return Scrolled;
+}
+static void CreateZoneEntry(TimeAdmin *ta)
+{
+ GtkWidget *hbox;
+
+ ta->TimezoneEntry = gtk_search_entry_new ();
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
+ gtk_widget_set_halign (hbox, GTK_ALIGN_CENTER);
+ gtk_box_pack_start (GTK_BOX (hbox), ta->TimezoneEntry, FALSE, FALSE, 0);
+
+ ta->SearchBar = gtk_search_bar_new ();
+ gtk_search_bar_connect_entry (GTK_SEARCH_BAR (ta->SearchBar),
+ GTK_ENTRY (ta->TimezoneEntry));
+ gtk_search_bar_set_show_close_button (GTK_SEARCH_BAR (ta->SearchBar),FALSE);
+ gtk_container_add (GTK_CONTAINER (ta->SearchBar), hbox);
+ gtk_search_bar_set_search_mode(GTK_SEARCH_BAR(ta->SearchBar),TRUE);
+
+}
+
+static gboolean CityChanged(GtkEntryCompletion *completion,
+ GtkTreeModel *model,
+ GtkTreeIter *iter,
+ TimeAdmin *self)
+{
+ GtkWidget *entry;
+ g_autofree gchar *zone = NULL;
+
+ gtk_tree_model_get (model,
+ iter,
+ CITY_COL_ZONE,
+ &zone,
+ -1);
+ timezone_map_set_timezone (TIMEZONEMAP (self->map), zone);
+
+ entry = gtk_entry_completion_get_entry (completion);
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+
+ return TRUE;
+}
+static void ChoooseTimezoneDone (GtkWidget *widget,
+ TimeAdmin *ta)
+{
+ TimezoneMap *map;
+ g_autofree gchar *ZoneCity = NULL;
+
+ map = TIMEZONEMAP(ta->map);
+ SetTimeZone(ta->proxy,map->location->zone);
+
+ ZoneCity = translated_city_name(map->location);
+ gtk_button_set_label((GTK_BUTTON(ta->TimeZoneButton)),ZoneCity);
+ gtk_widget_hide_on_delete(GTK_WIDGET(ta->dialog));
+}
+
+static void ChoooseTimezoneClose(GtkWidget *widget,
+ TimeAdmin *ta)
+{
+ gtk_widget_hide_on_delete(GTK_WIDGET(ta->dialog));
+}
+
+void SetupTimezoneDialog(TimeAdmin *ta)
+{
+ GtkWidget *Vbox;
+ GtkWidget *TimeZoneFrame;
+ GtkWidget *Scrolled;
+
+ ta->dialog = gtk_dialog_new_with_buttons (_("Time Zone Selection"),
+ NULL,
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ NULL,
+ NULL);
+ gtk_window_set_default_size (GTK_WINDOW (ta->dialog), 730, 520);
+
+ ta->TZclose = DialogAddButtonWithIconName(GTK_DIALOG(ta->dialog),
+ _("Close"),
+ "window-close",
+ GTK_RESPONSE_CANCEL);
+
+ ta->TZconfire = DialogAddButtonWithIconName(GTK_DIALOG(ta->dialog),
+ _("Confirm"),
+ "emblem-default",
+ GTK_RESPONSE_OK);
+ g_signal_connect (ta->TZconfire,
+ "clicked",
+ G_CALLBACK (ChoooseTimezoneDone),
+ ta);
+
+ g_signal_connect (ta->TZclose,
+ "clicked",
+ G_CALLBACK (ChoooseTimezoneClose),
+ ta);
+
+ Vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_style_context_add_class (gtk_widget_get_style_context (Vbox), "linked");
+
+
+ TimeZoneFrame = CreateZoneFrame(ta);
+ Scrolled = CreateZoneScrolled(ta);
+ gtk_container_add (GTK_CONTAINER (TimeZoneFrame), Scrolled);
+ CreateCityList(ta);
+ CreateZoneEntry(ta);
+ gtk_box_pack_start (GTK_BOX (Vbox), ta->SearchBar,FALSE,FALSE, 0);
+ ta->map = GetTimeZoneMap(ta);
+ gtk_widget_show (ta->map);
+ gtk_container_add (GTK_CONTAINER (Scrolled),ta->map);
+ gtk_box_pack_start(GTK_BOX(Vbox),TimeZoneFrame,TRUE,TRUE,10);
+ get_initial_timezone(ta);
+
+ g_signal_connect(gtk_entry_get_completion (GTK_ENTRY (ta->TimezoneEntry)),
+ "match-selected",
+ G_CALLBACK (CityChanged),
+ ta);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (ta->dialog))),
+ Vbox,
+ TRUE,
+ TRUE, 8);
+}
+void tz_info_free (TzInfo *tzinfo)
+{
+ g_return_if_fail (tzinfo != NULL);
+
+ if (tzinfo->tzname_normal) g_free (tzinfo->tzname_normal);
+ if (tzinfo->tzname_daylight) g_free (tzinfo->tzname_daylight);
+ 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);
+}
+
+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_normal = g_strdup (curzone->tm_zone);
+ if (curzone->tm_isdst)
+ tzinfo->tzname_daylight =
+ g_strdup (&curzone->tm_zone[curzone->tm_isdst]);
+ else
+ tzinfo->tzname_daylight = NULL;
+
+ tzinfo->utc_offset = curzone->tm_gmtoff;
+#else
+ tzinfo->tzname_normal = NULL;
+ tzinfo->tzname_daylight = 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;
+}
+glong tz_location_get_utc_offset (TzLocation *loc)
+{
+ g_autoptr(TzInfo) tz_info = NULL;
+ glong offset;
+
+ tz_info = tz_info_from_location (loc);
+ offset = tz_info->utc_offset;
+ return offset;
+}
+void RunTimeZoneDialog (GtkButton *button,
+ gpointer data)
+{
+ TimeAdmin *ta = (TimeAdmin *)data;
+
+ gtk_widget_show_all(GTK_WIDGET(ta->dialog));
+}
+static void
+tz_location_free (TzLocation *loc, gpointer data)
+{
+ g_free (loc->country);
+ g_free (loc->zone);
+ g_free (loc->comment);
+
+ g_free (loc);
+}
+void TimeZoneDateBaseFree (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;
+}