[gnome-settings-daemon] datetime: Add new datetime plugin



commit 876590336fdb1292852061509a0dba5385cd1805
Author: Kalev Lember <kalevlember gmail com>
Date:   Sun Aug 25 11:10:33 2013 +0200

    datetime: Add new datetime plugin
    
    For now, this takes care of automatic timezone updating based on
    geolocation. It relies on the new GeoClue2 D-Bus service for getting the
    location information and updates the system TZ using the
    org.freedesktop.timedate1 service.
    
    In the future, it is also going to take care of displaying notifications
    for upcoming time related changes ("Daylight Saving Starts Tonight") and
    notifying the user about timezone changes.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=706979

 .gitignore                                         |    5 +
 configure.ac                                       |    9 +
 data/Makefile.am                                   |    1 +
 ...tings-daemon.plugins.datetime.gschema.xml.in.in |   14 +
 ...gnome.settings-daemon.plugins.gschema.xml.in.in |    1 +
 plugins/Makefile.am                                |    1 +
 plugins/datetime/Makefile.am                       |  104 +++++
 plugins/datetime/backward                          |  118 +++++
 plugins/datetime/datetime.gnome-settings-plugin.in |   10 +
 plugins/datetime/geoclue-interface.xml             |   25 +
 plugins/datetime/gsd-datetime-manager.c            |  152 ++++++
 plugins/datetime/gsd-datetime-manager.h            |   57 +++
 plugins/datetime/gsd-datetime-plugin.c             |   29 ++
 plugins/datetime/gsd-timezone-monitor.c            |  355 ++++++++++++++
 plugins/datetime/gsd-timezone-monitor.h            |   54 +++
 plugins/datetime/test-datetime.c                   |    7 +
 plugins/datetime/timedated1-interface.xml          |   28 ++
 plugins/datetime/tz.c                              |  482 ++++++++++++++++++++
 plugins/datetime/tz.h                              |   89 ++++
 po/POTFILES.in                                     |    1 +
 po/POTFILES.skip                                   |    1 +
 21 files changed, 1543 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 13ba569..c0d804e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,6 +56,11 @@ plugins/background/test-background
 plugins/common/gsd-test-input-helper
 plugins/common/test-egg-key-parsing
 plugins/cursor/gsd-test-cursor
+plugins/datetime/gsd-test-datetime
+plugins/datetime/geoclue.c
+plugins/datetime/geoclue.h
+plugins/datetime/timedated.c
+plugins/datetime/timedated.h
 plugins/housekeeping/gsd-disk-space-test
 plugins/housekeeping/gsd-empty-trash-test
 plugins/housekeeping/gsd-test-housekeeping
diff --git a/configure.ac b/configure.ac
index 41b748b..47710ef 100644
--- a/configure.ac
+++ b/configure.ac
@@ -245,6 +245,13 @@ dnl ---------------------------------------------------------------------------
 PKG_CHECK_MODULES(COLOR, [colord >= 1.0.2 gnome-desktop-3.0 >= $GNOME_DESKTOP_REQUIRED_VERSION 
libcanberra-gtk3])
 
 dnl ---------------------------------------------------------------------------
+dnl - datetime
+dnl ---------------------------------------------------------------------------
+
+AC_CHECK_LIBM
+AC_SUBST(LIBM)
+
+dnl ---------------------------------------------------------------------------
 dnl - wacom (disabled for s390/s390x and non Linux platforms)
 dnl ---------------------------------------------------------------------------
 
@@ -508,6 +515,7 @@ plugins/clipboard/Makefile
 plugins/color/Makefile
 plugins/common/Makefile
 plugins/cursor/Makefile
+plugins/datetime/Makefile
 plugins/dummy/Makefile
 plugins/power/Makefile
 plugins/housekeeping/Makefile
@@ -534,6 +542,7 @@ data/org.gnome.settings-daemon.plugins.xsettings.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.keyboard.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.power.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.color.gschema.xml.in
+data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.media-keys.gschema.xml.in
 data/org.gnome.settings-daemon.peripherals.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.housekeeping.gschema.xml.in
diff --git a/data/Makefile.am b/data/Makefile.am
index ecfe00a..c1522db 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -12,6 +12,7 @@ gsettings_SCHEMAS =                                                   \
        org.gnome.settings-daemon.plugins.keyboard.gschema.xml          \
        org.gnome.settings-daemon.plugins.power.gschema.xml             \
        org.gnome.settings-daemon.plugins.color.gschema.xml             \
+       org.gnome.settings-daemon.plugins.datetime.gschema.xml          \
        org.gnome.settings-daemon.plugins.media-keys.gschema.xml        \
        org.gnome.settings-daemon.plugins.xsettings.gschema.xml         \
        org.gnome.settings-daemon.plugins.housekeeping.gschema.xml      \
diff --git a/data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in.in 
b/data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in.in
new file mode 100644
index 0000000..eacc4a0
--- /dev/null
+++ b/data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in.in
@@ -0,0 +1,14 @@
+<schemalist>
+  <schema gettext-domain="@GETTEXT_PACKAGE@" id="org.gnome.settings-daemon.plugins.datetime" 
path="/org/gnome/settings-daemon/plugins/datetime/">
+    <key name="active" type="b">
+      <default>true</default>
+      <_summary>Activation of this plugin</_summary>
+      <_description>Whether this plugin would be activated by gnome-settings-daemon or not</_description>
+    </key>
+    <key name="priority" type="i">
+      <default>0</default>
+      <_summary>Priority to use for this plugin</_summary>
+      <_description>Priority to use for this plugin in gnome-settings-daemon startup queue</_description>
+    </key>
+  </schema>
+</schemalist>
diff --git a/data/org.gnome.settings-daemon.plugins.gschema.xml.in.in 
b/data/org.gnome.settings-daemon.plugins.gschema.xml.in.in
index bea64f3..57a058d 100644
--- a/data/org.gnome.settings-daemon.plugins.gschema.xml.in.in
+++ b/data/org.gnome.settings-daemon.plugins.gschema.xml.in.in
@@ -14,6 +14,7 @@
     <child name="clipboard" schema="org.gnome.settings-daemon.plugins.clipboard"/>
     <child name="color" schema="org.gnome.settings-daemon.plugins.color"/>
     <child name="cursor" schema="org.gnome.settings-daemon.plugins.cursor"/>
+    <child name="datetime" schema="org.gnome.settings-daemon.plugins.datetime"/>
     <child name="gsdwacom" schema="org.gnome.settings-daemon.plugins.gsdwacom"/>
     <child name="housekeeping" schema="org.gnome.settings-daemon.plugins.housekeeping"/>
     <child name="keyboard" schema="org.gnome.settings-daemon.plugins.keyboard"/>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 787fd14..2f4840d 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -6,6 +6,7 @@ enabled_plugins =       \
        clipboard       \
        color           \
        cursor          \
+       datetime        \
        dummy           \
        power           \
        housekeeping    \
diff --git a/plugins/datetime/Makefile.am b/plugins/datetime/Makefile.am
new file mode 100644
index 0000000..f8cdbe2
--- /dev/null
+++ b/plugins/datetime/Makefile.am
@@ -0,0 +1,104 @@
+plugin_name = datetime
+
+geoclue.c: geoclue.h
+geoclue.h: Makefile.am geoclue-interface.xml
+       gdbus-codegen                                           \
+               --interface-prefix org.freedesktop.GeoClue2     \
+               --generate-c-code geoclue                       \
+               --c-namespace Geoclue                           \
+               $(srcdir)/geoclue-interface.xml
+
+timedated.c: timedated.h
+timedated.h: Makefile.am timedated1-interface.xml
+       gdbus-codegen                                           \
+               --interface-prefix org.freedesktop.             \
+               --generate-c-code timedated                     \
+               $(srcdir)/timedated1-interface.xml
+
+BUILT_SOURCES =                        \
+       geoclue.c               \
+       geoclue.h               \
+       timedated.c             \
+       timedated.h
+
+tzdatadir = $(pkgdatadir)/datetime
+dist_tzdata_DATA = backward
+
+libexec_PROGRAMS = gsd-test-datetime
+
+gsd_test_datetime_SOURCES =    \
+       $(BUILT_SOURCES)        \
+       gsd-datetime-manager.c  \
+       gsd-datetime-manager.h  \
+       gsd-timezone-monitor.c  \
+       gsd-timezone-monitor.h  \
+       test-datetime.c         \
+       tz.c                    \
+       tz.h
+
+gsd_test_datetime_CFLAGS =                                     \
+       -I$(top_srcdir)/gnome-settings-daemon                   \
+       -I$(top_builddir)/gnome-settings-daemon                 \
+       -I$(top_srcdir)/plugins/common                          \
+       -DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"      \
+       -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\""                  \
+       $(DATETIME_CFLAGS)                                      \
+       $(PLUGIN_CFLAGS)                                        \
+       $(SETTINGS_PLUGIN_CFLAGS)                               \
+       $(AM_CFLAGS)
+
+gsd_test_datetime_LDADD =                                      \
+       $(top_builddir)/gnome-settings-daemon/libgsd.la         \
+       $(top_builddir)/plugins/common/libcommon.la             \
+       $(LIBM)                                                 \
+       $(SETTINGS_PLUGIN_LIBS)
+
+plugin_LTLIBRARIES = \
+       libdatetime.la
+
+libdatetime_la_SOURCES =       \
+       $(BUILT_SOURCES)        \
+       gsd-datetime-plugin.c   \
+       gsd-datetime-manager.h  \
+       gsd-datetime-manager.c  \
+       gsd-timezone-monitor.h  \
+       gsd-timezone-monitor.c  \
+       tz.h                    \
+       tz.c
+
+libdatetime_la_CPPFLAGS =                                      \
+       -I$(top_srcdir)/gnome-settings-daemon                   \
+       -I$(top_builddir)/gnome-settings-daemon                 \
+       -DGNOME_SETTINGS_LOCALEDIR=\""$(datadir)/locale"\"      \
+       -DGNOMECC_DATA_DIR="\"$(pkgdatadir)\""                  \
+       $(AM_CPPFLAGS)
+
+libdatetime_la_CFLAGS =                        \
+       $(PLUGIN_CFLAGS)                \
+       $(SETTINGS_PLUGIN_CFLAGS)       \
+       $(AM_CFLAGS)
+
+libdatetime_la_LDFLAGS = \
+       $(GSD_PLUGIN_LDFLAGS)
+
+libdatetime_la_LIBADD = \
+       $(LIBM) \
+       $(SETTINGS_PLUGIN_LIBS)
+
+plugin_in_files = \
+       datetime.gnome-settings-plugin.in
+
+plugin_DATA = $(plugin_in_files:.gnome-settings-plugin.in=.gnome-settings-plugin)
+
+EXTRA_DIST =                           \
+       geoclue-interface.xml           \
+       timedated1-interface.xml        \
+       $(plugin_in_files)
+
+CLEANFILES = \
+       $(plugin_DATA)
+
+DISTCLEANFILES = \
+       $(plugin_DATA)
+
+ GSD_INTLTOOL_PLUGIN_RULE@
diff --git a/plugins/datetime/backward b/plugins/datetime/backward
new file mode 100644
index 0000000..f1f95a8
--- /dev/null
+++ b/plugins/datetime/backward
@@ -0,0 +1,118 @@
+# <pre>
+# @(#)backward 8.9
+# This file is in the public domain, so clarified as of
+# 2009-05-17 by Arthur David Olson.
+
+# This file provides links between current names for time zones
+# and their old names.  Many names changed in late 1993.
+
+Link   Africa/Asmara           Africa/Asmera
+Link   Africa/Bamako           Africa/Timbuktu
+Link   America/Argentina/Catamarca     America/Argentina/ComodRivadavia
+Link   America/Adak            America/Atka
+Link   America/Argentina/Buenos_Aires  America/Buenos_Aires
+Link   America/Argentina/Catamarca     America/Catamarca
+Link   America/Atikokan        America/Coral_Harbour
+Link   America/Argentina/Cordoba       America/Cordoba
+Link   America/Tijuana         America/Ensenada
+Link   America/Indiana/Indianapolis    America/Fort_Wayne
+Link   America/Indiana/Indianapolis    America/Indianapolis
+Link   America/Argentina/Jujuy America/Jujuy
+Link   America/Indiana/Knox    America/Knox_IN
+Link   America/Kentucky/Louisville     America/Louisville
+Link   America/Argentina/Mendoza       America/Mendoza
+Link   America/Rio_Branco      America/Porto_Acre
+Link   America/Argentina/Cordoba       America/Rosario
+Link   America/St_Thomas       America/Virgin
+Link   Asia/Ashgabat           Asia/Ashkhabad
+Link   Asia/Chongqing          Asia/Chungking
+Link   Asia/Dhaka              Asia/Dacca
+Link   Asia/Kathmandu          Asia/Katmandu
+Link   Asia/Kolkata            Asia/Calcutta
+Link   Asia/Macau              Asia/Macao
+Link   Asia/Jerusalem          Asia/Tel_Aviv
+Link   Asia/Ho_Chi_Minh        Asia/Saigon
+Link   Asia/Thimphu            Asia/Thimbu
+Link   Asia/Makassar           Asia/Ujung_Pandang
+Link   Asia/Ulaanbaatar        Asia/Ulan_Bator
+Link   Atlantic/Faroe          Atlantic/Faeroe
+Link   Europe/Oslo             Atlantic/Jan_Mayen
+Link   Australia/Sydney        Australia/ACT
+Link   Australia/Sydney        Australia/Canberra
+Link   Australia/Lord_Howe     Australia/LHI
+Link   Australia/Sydney        Australia/NSW
+Link   Australia/Darwin        Australia/North
+Link   Australia/Brisbane      Australia/Queensland
+Link   Australia/Adelaide      Australia/South
+Link   Australia/Hobart        Australia/Tasmania
+Link   Australia/Melbourne     Australia/Victoria
+Link   Australia/Perth         Australia/West
+Link   Australia/Broken_Hill   Australia/Yancowinna
+Link   America/Rio_Branco      Brazil/Acre
+Link   America/Noronha         Brazil/DeNoronha
+Link   America/Sao_Paulo       Brazil/East
+Link   America/Manaus          Brazil/West
+Link   America/Halifax         Canada/Atlantic
+Link   America/Winnipeg        Canada/Central
+Link   America/Regina          Canada/East-Saskatchewan
+Link   America/Toronto         Canada/Eastern
+Link   America/Edmonton        Canada/Mountain
+Link   America/St_Johns        Canada/Newfoundland
+Link   America/Vancouver       Canada/Pacific
+Link   America/Regina          Canada/Saskatchewan
+Link   America/Whitehorse      Canada/Yukon
+Link   America/Santiago        Chile/Continental
+Link   Pacific/Easter          Chile/EasterIsland
+Link   America/Havana          Cuba
+Link   Africa/Cairo            Egypt
+Link   Europe/Dublin           Eire
+Link   Europe/London           Europe/Belfast
+Link   Europe/Chisinau         Europe/Tiraspol
+Link   Europe/London           GB
+Link   Europe/London           GB-Eire
+Link   Etc/GMT                 GMT+0
+Link   Etc/GMT                 GMT-0
+Link   Etc/GMT                 GMT0
+Link   Etc/GMT                 Greenwich
+Link   Asia/Hong_Kong          Hongkong
+Link   Atlantic/Reykjavik      Iceland
+Link   Asia/Tehran             Iran
+Link   Asia/Jerusalem          Israel
+Link   America/Jamaica         Jamaica
+Link   Asia/Tokyo              Japan
+Link   Pacific/Kwajalein       Kwajalein
+Link   Africa/Tripoli          Libya
+Link   America/Tijuana         Mexico/BajaNorte
+Link   America/Mazatlan        Mexico/BajaSur
+Link   America/Mexico_City     Mexico/General
+Link   Pacific/Auckland        NZ
+Link   Pacific/Chatham         NZ-CHAT
+Link   America/Denver          Navajo
+Link   Asia/Shanghai           PRC
+Link   Pacific/Pago_Pago       Pacific/Samoa
+Link   Pacific/Chuuk           Pacific/Yap
+Link   Pacific/Chuuk           Pacific/Truk
+Link   Pacific/Pohnpei         Pacific/Ponape
+Link   Europe/Warsaw           Poland
+Link   Europe/Lisbon           Portugal
+Link   Asia/Taipei             ROC
+Link   Asia/Seoul              ROK
+Link   Asia/Singapore          Singapore
+Link   Europe/Istanbul         Turkey
+Link   Etc/UCT                 UCT
+Link   America/Anchorage       US/Alaska
+Link   America/Adak            US/Aleutian
+Link   America/Phoenix         US/Arizona
+Link   America/Chicago         US/Central
+Link   America/Indiana/Indianapolis    US/East-Indiana
+Link   America/New_York        US/Eastern
+Link   Pacific/Honolulu        US/Hawaii
+Link   America/Indiana/Knox    US/Indiana-Starke
+Link   America/Detroit         US/Michigan
+Link   America/Denver          US/Mountain
+Link   America/Los_Angeles     US/Pacific
+Link   Pacific/Pago_Pago       US/Samoa
+Link   Etc/UTC                 UTC
+Link   Etc/UTC                 Universal
+Link   Europe/Moscow           W-SU
+Link   Etc/UTC                 Zulu
diff --git a/plugins/datetime/datetime.gnome-settings-plugin.in 
b/plugins/datetime/datetime.gnome-settings-plugin.in
new file mode 100644
index 0000000..f7b8b5a
--- /dev/null
+++ b/plugins/datetime/datetime.gnome-settings-plugin.in
@@ -0,0 +1,10 @@
+[GNOME Settings Plugin]
+Module=datetime
+IAge=0
+# Default Priority
+# Priority=100
+_Name=Date and Time
+_Description=Automatically update timezone and display notifications
+Authors=Kalev Lember
+Copyright=Copyright © 2013
+Website=
diff --git a/plugins/datetime/geoclue-interface.xml b/plugins/datetime/geoclue-interface.xml
new file mode 100644
index 0000000..3bf956b
--- /dev/null
+++ b/plugins/datetime/geoclue-interface.xml
@@ -0,0 +1,25 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node>
+  <interface name="org.freedesktop.GeoClue2.Manager">
+    <method name="GetClient">
+      <arg name="client" type="o" direction="out"/>
+    </method>
+  </interface>
+  <interface name="org.freedesktop.GeoClue2.Client">
+    <property name="Location" type="o" access="read"/>
+    <property name="DistanceThreshold" type="u" access="readwrite"/>
+    <method name="Start"/>
+    <method name="Stop"/>
+    <signal name="LocationUpdated">
+      <arg name="old" type="o"/>
+      <arg name="new" type="o"/>
+    </signal>
+  </interface>
+  <interface name="org.freedesktop.GeoClue2.Location">
+    <property name="latitude" type="d" access="read"/>
+    <property name="longitude" type="d" access="read"/>
+    <property name="accuracy" type="d" access="read"/>
+    <property name="description" type="s" access="read"/>
+  </interface>
+</node>
diff --git a/plugins/datetime/gsd-datetime-manager.c b/plugins/datetime/gsd-datetime-manager.c
new file mode 100644
index 0000000..e08203b
--- /dev/null
+++ b/plugins/datetime/gsd-datetime-manager.c
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember gmail com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <gio/gio.h>
+
+#include "gsd-datetime-manager.h"
+#include "gsd-timezone-monitor.h"
+#include "gnome-settings-profile.h"
+
+#define GSD_DATETIME_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSD_TYPE_DATETIME_MANAGER, 
GsdDatetimeManagerPrivate))
+
+#define DATETIME_SCHEMA "org.gnome.desktop.datetime"
+#define AUTO_TIMEZONE_KEY "automatic-timezone"
+
+struct GsdDatetimeManagerPrivate
+{
+        GSettings *settings;
+        GsdTimezoneMonitor *timezone_monitor;
+};
+
+static void gsd_datetime_manager_class_init (GsdDatetimeManagerClass *klass);
+static void gsd_datetime_manager_init (GsdDatetimeManager *manager);
+static void gsd_datetime_manager_finalize (GObject *object);
+
+G_DEFINE_TYPE (GsdDatetimeManager, gsd_datetime_manager, G_TYPE_OBJECT)
+
+static gpointer manager_object = NULL;
+
+static void
+auto_timezone_settings_changed_cb (GSettings          *settings,
+                                   const char         *key,
+                                   GsdDatetimeManager *self)
+{
+        gboolean enabled;
+
+        enabled = g_settings_get_boolean (settings, key);
+        if (enabled && self->priv->timezone_monitor == NULL) {
+                g_debug ("Automatic timezone enabled");
+                self->priv->timezone_monitor = gsd_timezone_monitor_new ();
+        } else {
+                g_debug ("Automatic timezone disabled");
+                g_clear_object (&self->priv->timezone_monitor);
+        }
+}
+
+gboolean
+gsd_datetime_manager_start (GsdDatetimeManager *self,
+                            GError            **error)
+{
+        g_debug ("Starting datetime manager");
+        gnome_settings_profile_start (NULL);
+
+        self->priv->settings = g_settings_new (DATETIME_SCHEMA);
+
+        auto_timezone_settings_changed_cb (self->priv->settings, AUTO_TIMEZONE_KEY, self);
+        g_signal_connect (self->priv->settings, "changed::" AUTO_TIMEZONE_KEY,
+                          G_CALLBACK (auto_timezone_settings_changed_cb), self);
+
+        gnome_settings_profile_end (NULL);
+
+        return TRUE;
+}
+
+void
+gsd_datetime_manager_stop (GsdDatetimeManager *self)
+{
+        g_debug ("Stopping datetime manager");
+
+        g_clear_object (&self->priv->settings);
+        g_clear_object (&self->priv->timezone_monitor);
+}
+
+static GObject *
+gsd_datetime_manager_constructor (GType type,
+                                  guint n_construct_properties,
+                                  GObjectConstructParam *construct_properties)
+{
+        GsdDatetimeManager *m;
+
+        m = GSD_DATETIME_MANAGER (G_OBJECT_CLASS (gsd_datetime_manager_parent_class)->constructor (type,
+                                                                                                   
n_construct_properties,
+                                                                                                   
construct_properties));
+
+        return G_OBJECT (m);
+}
+
+static void
+gsd_datetime_manager_class_init (GsdDatetimeManagerClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->constructor = gsd_datetime_manager_constructor;
+        object_class->finalize = gsd_datetime_manager_finalize;
+
+        g_type_class_add_private (klass, sizeof (GsdDatetimeManagerPrivate));
+}
+
+static void
+gsd_datetime_manager_init (GsdDatetimeManager *manager)
+{
+        manager->priv = GSD_DATETIME_MANAGER_GET_PRIVATE (manager);
+}
+
+static void
+gsd_datetime_manager_finalize (GObject *object)
+{
+        GsdDatetimeManager *manager;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (GSD_IS_DATETIME_MANAGER (object));
+
+        manager = GSD_DATETIME_MANAGER (object);
+
+        g_return_if_fail (manager->priv != NULL);
+
+        gsd_datetime_manager_stop (manager);
+
+        G_OBJECT_CLASS (gsd_datetime_manager_parent_class)->finalize (object);
+}
+
+GsdDatetimeManager *
+gsd_datetime_manager_new (void)
+{
+        if (manager_object != NULL) {
+                g_object_ref (manager_object);
+        } else {
+                manager_object = g_object_new (GSD_TYPE_DATETIME_MANAGER, NULL);
+                g_object_add_weak_pointer (manager_object,
+                                           (gpointer *) &manager_object);
+        }
+
+        return GSD_DATETIME_MANAGER (manager_object);
+}
diff --git a/plugins/datetime/gsd-datetime-manager.h b/plugins/datetime/gsd-datetime-manager.h
new file mode 100644
index 0000000..2125d3c
--- /dev/null
+++ b/plugins/datetime/gsd-datetime-manager.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember gmail com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSD_DATETIME_MANAGER_H
+#define __GSD_DATETIME_MANAGER_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_DATETIME_MANAGER         (gsd_datetime_manager_get_type ())
+#define GSD_DATETIME_MANAGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), GSD_TYPE_DATETIME_MANAGER, 
GsdDatetimeManager))
+#define GSD_DATETIME_MANAGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), GSD_TYPE_DATETIME_MANAGER, 
GsdDatetimeManagerClass))
+#define GSD_IS_DATETIME_MANAGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GSD_TYPE_DATETIME_MANAGER))
+#define GSD_IS_DATETIME_MANAGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), GSD_TYPE_DATETIME_MANAGER))
+#define GSD_DATETIME_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), GSD_TYPE_DATETIME_MANAGER, 
GsdDatetimeManagerClass))
+
+typedef struct GsdDatetimeManagerPrivate GsdDatetimeManagerPrivate;
+
+typedef struct
+{
+        GObject parent;
+        GsdDatetimeManagerPrivate *priv;
+} GsdDatetimeManager;
+
+typedef struct
+{
+        GObjectClass parent_class;
+} GsdDatetimeManagerClass;
+
+GType gsd_datetime_manager_get_type (void) G_GNUC_CONST;
+
+GsdDatetimeManager *gsd_datetime_manager_new (void);
+gboolean gsd_datetime_manager_start (GsdDatetimeManager *manager, GError **error);
+void gsd_datetime_manager_stop (GsdDatetimeManager *manager);
+
+G_END_DECLS
+
+#endif /* __GSD_DATETIME_MANAGER_H */
diff --git a/plugins/datetime/gsd-datetime-plugin.c b/plugins/datetime/gsd-datetime-plugin.c
new file mode 100644
index 0000000..af5564c
--- /dev/null
+++ b/plugins/datetime/gsd-datetime-plugin.c
@@ -0,0 +1,29 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember gmail com>
+ *
+ * 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, 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+
+#include "gnome-settings-plugin.h"
+#include "gsd-datetime-manager.h"
+
+GNOME_SETTINGS_PLUGIN_REGISTER (GsdDatetime, gsd_datetime)
diff --git a/plugins/datetime/gsd-timezone-monitor.c b/plugins/datetime/gsd-timezone-monitor.c
new file mode 100644
index 0000000..f283d69
--- /dev/null
+++ b/plugins/datetime/gsd-timezone-monitor.c
@@ -0,0 +1,355 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember gmail com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include "gsd-timezone-monitor.h"
+
+#include "geoclue.h"
+#include "timedated.h"
+#include "tz.h"
+
+#include <math.h>
+
+typedef struct
+{
+        GCancellable *cancellable;
+        GeoclueClient *geoclue_client;
+        GeoclueManager *geoclue_manager;
+        Timedate1 *dtm;
+
+        TzDB *tzdb;
+        gchar *current_timezone;
+} GsdTimezoneMonitorPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (GsdTimezoneMonitor, gsd_timezone_monitor, G_TYPE_OBJECT)
+
+static void
+set_timezone_cb (GObject      *source,
+                 GAsyncResult *res,
+                 gpointer      user_data)
+{
+        GsdTimezoneMonitor *self = user_data;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+        GError *error = NULL;
+
+        if (!timedate1_call_set_timezone_finish (priv->dtm,
+                                                 res,
+                                                 &error)) {
+                g_warning ("Could not set system timezone: %s", error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+queue_set_timezone (GsdTimezoneMonitor *self,
+                    const char *timezone)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        timedate1_call_set_timezone (priv->dtm,
+                                     timezone,
+                                     TRUE,
+                                     priv->cancellable,
+                                     set_timezone_cb,
+                                     self);
+}
+
+
+#define EARTH_RADIUS_KM 6372.795
+
+/* Copy of geocode_location_get_distance_from() from geocode-glib */
+static double
+distance_between (gdouble loca_latitude, gdouble loca_longitude,
+                  gdouble locb_latitude, gdouble locb_longitude)
+{
+        gdouble dlat, dlon, lat1, lat2;
+        gdouble a, c;
+
+        /* Algorithm from:
+         * http://www.movable-type.co.uk/scripts/latlong.html */
+
+        dlat = (locb_latitude - loca_latitude) * M_PI / 180.0;
+        dlon = (locb_longitude - loca_longitude) * M_PI / 180.0;
+        lat1 = loca_latitude * M_PI / 180.0;
+        lat2 = locb_latitude * M_PI / 180.0;
+
+        a = sin (dlat / 2) * sin (dlat / 2) +
+            sin (dlon / 2) * sin (dlon / 2) * cos (lat1) * cos (lat2);
+        c = 2 * atan2 (sqrt (a), sqrt (1-a));
+        return EARTH_RADIUS_KM * c;
+}
+
+static gint
+compare_locations (TzLocation *a,
+                   TzLocation *b)
+{
+        if (a->dist > b->dist)
+                return 1;
+
+        if (a->dist < b->dist)
+                return -1;
+
+        return 0;
+}
+
+static void
+process_location (GsdTimezoneMonitor *self,
+                  gdouble             latitude,
+                  gdouble             longitude)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+        GPtrArray *array;
+        GList *distances = NULL;
+        TzLocation *closest_tz_location;
+        gint i;
+
+        array = tz_get_locations (priv->tzdb);
+
+        for (i = 0; i < array->len; i++) {
+                TzLocation *loc = array->pdata[i];
+
+                loc->dist = distance_between (latitude, longitude,
+                                              loc->latitude, loc->longitude);
+                distances = g_list_prepend (distances, loc);
+
+        }
+        distances = g_list_sort (distances, (GCompareFunc) compare_locations);
+
+        closest_tz_location = (TzLocation*) distances->data;
+        if (g_strcmp0 (priv->current_timezone, closest_tz_location->zone) != 0) {
+                g_debug ("Changing timezone to %s", closest_tz_location->zone);
+                queue_set_timezone (self, closest_tz_location->zone);
+        }
+
+        g_list_free (distances);
+}
+
+static void
+on_location_proxy_ready (GObject      *source_object,
+                         GAsyncResult *res,
+                         gpointer      user_data)
+{
+        GeoclueLocation *location;
+        gdouble latitude, longitude;
+        GError *error = NULL;
+        GsdTimezoneMonitor *self = user_data;
+
+        location = geoclue_location_proxy_new_for_bus_finish (res, &error);
+        if (error != NULL) {
+                g_critical ("Failed to connect to GeoClue2 service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        latitude = geoclue_location_get_latitude (location);
+        longitude = geoclue_location_get_longitude (location);
+
+        process_location (self, latitude, longitude);
+
+        g_object_unref (location);
+}
+
+static void
+on_location_updated (GDBusProxy *client,
+                     gchar      *location_path_old,
+                     gchar      *location_path_new,
+                     gpointer    user_data)
+{
+        GsdTimezoneMonitor *self = user_data;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        geoclue_location_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                            G_DBUS_PROXY_FLAGS_NONE,
+                                            "org.freedesktop.GeoClue2",
+                                            location_path_new,
+                                            priv->cancellable,
+                                            on_location_proxy_ready,
+                                            self);
+}
+
+static void
+on_start_ready (GObject      *source_object,
+                GAsyncResult *res,
+                gpointer      user_data)
+{
+        GError *error = NULL;
+
+        if (!geoclue_client_call_start_finish (GEOCLUE_CLIENT (source_object),
+                                               res,
+                                               &error)) {
+                g_critical ("Failed to start GeoClue2 client: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+}
+
+static void
+on_client_proxy_ready (GObject      *source_object,
+                       GAsyncResult *res,
+                       gpointer      user_data)
+{
+        GError *error = NULL;
+        GsdTimezoneMonitor *self = user_data;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        priv->geoclue_client = geoclue_client_proxy_new_for_bus_finish (res, &error);
+        if (error != NULL) {
+                g_critical ("Failed to connect to GeoClue2 service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        g_signal_connect (priv->geoclue_client, "location-updated",
+                          G_CALLBACK (on_location_updated), self);
+
+        geoclue_client_call_start (priv->geoclue_client,
+                                   priv->cancellable,
+                                   on_start_ready,
+                                   self);
+}
+
+static void
+on_get_client_ready (GObject      *source_object,
+                     GAsyncResult *res,
+                     gpointer      user_data)
+{
+        gchar *client_path;
+        GError *error = NULL;
+        GsdTimezoneMonitor *self = user_data;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        if (!geoclue_manager_call_get_client_finish (GEOCLUE_MANAGER (source_object),
+                                                     &client_path,
+                                                     res,
+                                                     &error)) {
+                g_critical ("Failed to connect to GeoClue2 service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        geoclue_client_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                          G_DBUS_PROXY_FLAGS_NONE,
+                                          "org.freedesktop.GeoClue2",
+                                          client_path,
+                                          priv->cancellable,
+                                          on_client_proxy_ready,
+                                          self);
+}
+
+static void
+on_manager_proxy_ready (GObject      *source_object,
+                        GAsyncResult *res,
+                        gpointer      user_data)
+{
+
+        GError *error = NULL;
+        GsdTimezoneMonitor *self = user_data;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        priv->geoclue_manager = geoclue_manager_proxy_new_for_bus_finish (res, &error);
+        if (error != NULL) {
+                g_critical ("Failed to connect to GeoClue2 service: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        geoclue_manager_call_get_client (priv->geoclue_manager,
+                                         priv->cancellable,
+                                         on_get_client_ready,
+                                         self);
+}
+
+static void
+register_geoclue (GsdTimezoneMonitor *self)
+{
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        geoclue_manager_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                                           G_DBUS_PROXY_FLAGS_NONE,
+                                           "org.freedesktop.GeoClue2",
+                                           "/org/freedesktop/GeoClue2/Manager",
+                                           priv->cancellable,
+                                           on_manager_proxy_ready,
+                                           self);
+}
+
+GsdTimezoneMonitor *
+gsd_timezone_monitor_new (void)
+{
+        return g_object_new (GSD_TYPE_TIMEZONE_MONITOR, NULL);
+}
+
+static void
+gsd_timezone_monitor_finalize (GObject *obj)
+{
+        GsdTimezoneMonitor *monitor = GSD_TIMEZONE_MONITOR (obj);
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (monitor);
+
+        g_debug ("Stopping timezone monitor");
+
+        if (priv->cancellable) {
+                g_cancellable_cancel (priv->cancellable);
+                g_clear_object (&priv->cancellable);
+        }
+
+        g_clear_object (&priv->dtm);
+        g_clear_object (&priv->geoclue_client);
+        g_clear_object (&priv->geoclue_manager);
+        g_clear_pointer (&priv->current_timezone, g_free);
+        g_clear_pointer (&priv->tzdb, tz_db_free);
+
+        G_OBJECT_CLASS (gsd_timezone_monitor_parent_class)->finalize (obj);
+}
+
+static void
+gsd_timezone_monitor_class_init (GsdTimezoneMonitorClass *klass)
+{
+        GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->finalize = gsd_timezone_monitor_finalize;
+}
+
+static void
+gsd_timezone_monitor_init (GsdTimezoneMonitor *self)
+{
+        GError *error = NULL;
+        GsdTimezoneMonitorPrivate *priv = gsd_timezone_monitor_get_instance_private (self);
+
+        g_debug ("Starting timezone monitor");
+
+        priv->cancellable = g_cancellable_new ();
+        priv->dtm = timedate1_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                      G_DBUS_PROXY_FLAGS_NONE,
+                                                      "org.freedesktop.timedate1",
+                                                      "/org/freedesktop/timedate1",
+                                                      priv->cancellable,
+                                                      &error);
+        if (priv->dtm == NULL) {
+                g_warning ("Could not get proxy for DateTimeMechanism: %s", error->message);
+                g_error_free (error);
+                return;
+        }
+
+        priv->current_timezone = timedate1_dup_timezone (priv->dtm);
+        priv->tzdb = tz_load_db ();
+
+        register_geoclue (self);
+}
diff --git a/plugins/datetime/gsd-timezone-monitor.h b/plugins/datetime/gsd-timezone-monitor.h
new file mode 100644
index 0000000..d3aa3b3
--- /dev/null
+++ b/plugins/datetime/gsd-timezone-monitor.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2013 Kalev Lember <kalevlember gmail com>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __GSD_TIMEZONE_MONITOR_H
+#define __GSD_TIMEZONE_MONITOR_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GSD_TYPE_TIMEZONE_MONITOR                  (gsd_timezone_monitor_get_type ())
+#define GSD_TIMEZONE_MONITOR(obj)                  (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitor))
+#define GSD_IS_TIMEZONE_MONITOR(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GSD_TYPE_TIMEZONE_MONITOR))
+#define GSD_TIMEZONE_MONITOR_CLASS(klass)          (G_TYPE_CHECK_CLASS_CAST ((klass), 
GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitorClass))
+#define GSD_IS_TIMEZONE_MONITOR_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GSD_TYPE_TIMEZONE_MONITOR))
+#define GSD_TIMEZONE_MONITOR_GET_CLASS(obj)        (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GSD_TYPE_TIMEZONE_MONITOR, GsdTimezoneMonitorClass))
+
+typedef struct _GsdTimezoneMonitor        GsdTimezoneMonitor;
+typedef struct _GsdTimezoneMonitorClass   GsdTimezoneMonitorClass;
+
+struct _GsdTimezoneMonitor
+{
+       GObject parent_instance;
+};
+
+struct _GsdTimezoneMonitorClass
+{
+       GObjectClass parent_class;
+};
+
+GType gsd_timezone_monitor_get_type (void) G_GNUC_CONST;
+
+GsdTimezoneMonitor *gsd_timezone_monitor_new (void);
+
+G_END_DECLS
+
+#endif /* __GSD_TIMEZONE_MONITOR_H */
diff --git a/plugins/datetime/test-datetime.c b/plugins/datetime/test-datetime.c
new file mode 100644
index 0000000..0a55ba2
--- /dev/null
+++ b/plugins/datetime/test-datetime.c
@@ -0,0 +1,7 @@
+#define NEW gsd_datetime_manager_new
+#define START gsd_datetime_manager_start
+#define STOP gsd_datetime_manager_stop
+#define MANAGER GsdDatetimeManager
+#include "gsd-datetime-manager.h"
+
+#include "test-plugin.h"
diff --git a/plugins/datetime/timedated1-interface.xml b/plugins/datetime/timedated1-interface.xml
new file mode 100644
index 0000000..3370e0e
--- /dev/null
+++ b/plugins/datetime/timedated1-interface.xml
@@ -0,0 +1,28 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
+"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd";>
+<node>
+ <interface name="org.freedesktop.timedate1">
+  <property name="Timezone" type="s" access="read"/>
+  <property name="LocalRTC" type="b" access="read"/>
+  <property name="CanNTP" type="b" access="read"/>
+  <property name="NTP" type="b" access="read"/>
+  <method name="SetTime">
+   <arg name="usec_utc" type="x" direction="in"/>
+   <arg name="relative" type="b" direction="in"/>
+   <arg name="user_interaction" type="b" direction="in"/>
+  </method>
+  <method name="SetTimezone">
+   <arg name="timezone" type="s" direction="in"/>
+   <arg name="user_interaction" type="b" direction="in"/>
+  </method>
+  <method name="SetLocalRTC">
+   <arg name="local_rtc" type="b" direction="in"/>
+   <arg name="fix_system" type="b" direction="in"/>
+   <arg name="user_interaction" type="b" direction="in"/>
+  </method>
+  <method name="SetNTP">
+   <arg name="use_ntp" type="b" direction="in"/>
+   <arg name="user_interaction" type="b" direction="in"/>
+  </method>
+ </interface>
+</node>
diff --git a/plugins/datetime/tz.c b/plugins/datetime/tz.c
new file mode 100644
index 0000000..c539d59
--- /dev/null
+++ b/plugins/datetime/tz.c
@@ -0,0 +1,482 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <glib.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <math.h>
+#include <string.h>
+#include "tz.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)
+{
+       gchar *tz_data_file;
+       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);
+               g_free (tz_data_file);
+               return NULL;
+       }
+
+       tz_db = g_new0 (TzDB, 1);
+       tz_db->locations = g_ptr_array_new ();
+
+       while (fgets (buf, sizeof(buf), tzfile))
+       {
+               gchar **tmpstrarr;
+               gchar *latstr, *lngstr, *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);
+
+               g_free (latstr);
+               g_free (lngstr);
+               g_strfreev (tmpstrarr);
+       }
+       
+       fclose (tzfile);
+       
+       /* now sort by country */
+       sort_locations_by_country (tz_db->locations);
+       
+       g_free (tz_data_file);
+
+       /* 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;
+}
+
+glong
+tz_location_get_utc_offset (TzLocation *loc)
+{
+       TzInfo *tz_info;
+       glong offset;
+
+       tz_info = tz_info_from_location (loc);
+       offset = tz_info->utc_offset;
+       tz_info_free (tz_info);
+       return offset;
+}
+
+TzInfo *
+tz_info_from_location (TzLocation *loc)
+{
+       TzInfo *tzinfo;
+       time_t curtime;
+       struct tm *curzone;
+       gchar *tz_env_value;
+       
+       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
+       /* Currently this solution doesnt seem to work - I get that */
+       /* America/Phoenix uses daylight savings, which is wrong    */
+       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");
+
+       g_free (tz_env_value);
+       
+       return tzinfo;
+}
+
+
+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) {
+               char *prefixed;
+
+               prefixed = g_strdup_printf ("/%s", b);
+               if (g_str_has_suffix (a, prefixed)) {
+                       g_free (prefixed);
+                       return TRUE;
+               }
+               g_free (prefixed);
+       }
+
+       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)
+{
+  GError *error = NULL;
+  char **lines, *contents;
+  guint i;
+
+  tz_db->backward = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+  if (g_file_get_contents (GNOMECC_DATA_DIR "/datetime/backward", &contents, NULL, &error) == FALSE)
+    {
+      g_warning ("Failed to load 'backward' file: %s", error->message);
+      return;
+    }
+  lines = g_strsplit (contents, "\n", -1);
+  g_free (contents);
+  for (i = 0; lines[i] != NULL; i++)
+    {
+      char **items;
+      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));
+      g_strfreev (items);
+    }
+  g_strfreev (lines);
+}
+
diff --git a/plugins/datetime/tz.h b/plugins/datetime/tz.h
new file mode 100644
index 0000000..71c1c23
--- /dev/null
+++ b/plugins/datetime/tz.h
@@ -0,0 +1,89 @@
+/* -*- 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef _E_TZ_H
+#define _E_TZ_H
+
+#include <glib.h>
+
+#ifndef __sun
+#  define TZ_DATA_FILE "/usr/share/zoneinfo/zone.tab"
+#else
+#  define TZ_DATA_FILE "/usr/share/lib/zoneinfo/tab/zone_sun.tab"
+#endif
+
+typedef struct _TzDB TzDB;
+typedef struct _TzLocation TzLocation;
+typedef struct _TzInfo TzInfo;
+
+
+struct _TzDB
+{
+       GPtrArray  *locations;
+       GHashTable *backward;
+};
+
+struct _TzLocation
+{
+       gchar *country;
+       gdouble latitude;
+       gdouble longitude;
+       gchar *zone;
+       gchar *comment;
+
+       gdouble dist; /* distance to clicked point for comparison */
+};
+
+/* see the glibc info page information on time zone information */
+/*  tzname_normal    is the default name for the timezone */
+/*  tzname_daylight  is the name of the zone when in daylight savings */
+/*  utc_offset       is offset in seconds from utc */
+/*  daylight         if non-zero then location obeys daylight savings */
+
+struct _TzInfo
+{
+       gchar *tzname_normal;
+       gchar *tzname_daylight;
+       glong utc_offset;
+       gint daylight;
+};
+
+
+TzDB      *tz_load_db                 (void);
+void       tz_db_free                 (TzDB *db);
+char *     tz_info_get_clean_name     (TzDB *tz_db,
+                                      const char *tz);
+GPtrArray *tz_get_locations           (TzDB *db);
+void       tz_location_get_position   (TzLocation *loc,
+                                      double *longitude, double *latitude);
+char      *tz_location_get_country    (TzLocation *loc);
+gchar     *tz_location_get_zone       (TzLocation *loc);
+gchar     *tz_location_get_comment    (TzLocation *loc);
+glong      tz_location_get_utc_offset (TzLocation *loc);
+gint       tz_location_set_locally    (TzLocation *loc);
+TzInfo    *tz_info_from_location      (TzLocation *loc);
+void       tz_info_free               (TzInfo *tz_info);
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 93d0075..9f05c02 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@ data/gnome-settings-daemon.desktop.in.in
 data/org.gnome.settings-daemon.peripherals.gschema.xml.in.in
 data/org.gnome.settings-daemon.peripherals.wacom.gschema.xml.in.in
 data/org.gnome.settings-daemon.plugins.color.gschema.xml.in.in
+data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in.in
 data/org.gnome.settings-daemon.plugins.gschema.xml.in.in
 data/org.gnome.settings-daemon.plugins.housekeeping.gschema.xml.in.in
 data/org.gnome.settings-daemon.plugins.keyboard.gschema.xml.in.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index 6941193..132d3a5 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -10,6 +10,7 @@ data/gnome-settings-daemon.desktop.in
 data/org.gnome.settings-daemon.peripherals.gschema.xml.in
 data/org.gnome.settings-daemon.peripherals.wacom.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.color.gschema.xml.in
+data/org.gnome.settings-daemon.plugins.datetime.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.housekeeping.gschema.xml.in
 data/org.gnome.settings-daemon.plugins.keyboard.gschema.xml.in


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]