pidgin/purple-plugin-pack
merge of '1e18671feafd8f1a223c5f7d2264f23846304991'
and '232732c662117f3311e7e111493f92ceeddbfd85'
--- a/.mtn-ignore Mon Oct 22 04:40:46 2007 -0400
+++ b/.mtn-ignore Mon Oct 22 05:00:47 2007 -0400
@@ -4,7 +4,9 @@
-config.(guess|log|status|sub)
+buddytime/gtktimezonetest +config.(cache|guess|log|status|sub) @@ -14,6 +16,8 @@
--- a/AUTHORS Mon Oct 22 04:40:46 2007 -0400
+++ b/AUTHORS Mon Oct 22 05:00:47 2007 -0400
@@ -9,6 +9,7 @@
Daniel Atallah <datallah@users.sourceforge.net>
Sadrul H Chowdhury <sadrul@users.sourceforge.net>
Richard Laager <rlaager@guifications.org>
+Martijn van Oosterhout <kleptog@svana.org> --- a/ChangeLog Mon Oct 22 04:40:46 2007 -0400
+++ b/ChangeLog Mon Oct 22 05:00:47 2007 -0400
@@ -26,6 +26,8 @@
builds. Pidgin will have persistent conversations soon.
* Added dewysiwygification plugin
* Added timelog plugin, from Jon Oberheide's gaim-timelog
+ * Merged buddytimezone from the buddytools package into the existing + (incomplete) buddytime plugin * Fixed lack of .build, .pidgin-plugin, and Makefile.mingw for convbadger
--- a/buddytime/Makefile.am Mon Oct 22 04:40:46 2007 -0400
+++ b/buddytime/Makefile.am Mon Oct 22 05:00:47 2007 -0400
@@ -1,23 +1,45 @@
-EXTRA_DIST = .incomplete .pidgin-plugin
+EXTRA_DIST = .incomplete .purple-plugin .pidgin-plugin +noinst_PROGRAMS = recursetest -buddytimedir = $(PIDGIN_LIBDIR)
-buddytime_la_LDFLAGS = -module -avoid-version
-buddytime_LTLIBRARIES = buddytime.la
+noinst_PROGRAMS += gtktimezonetest +gtktimezonetest_SOURCES = \ -buddytime_la_SOURCES = \
+gtktimezonetest_LDADD = \
+gtkbuddytimedir = $(PIDGIN_LIBDIR) +gtkbuddytime_la_LDFLAGS = -module -avoid-version +gtkbuddytime_LTLIBRARIES = gtkbuddytime.la +gtkbuddytime_la_SOURCES = \ +gtkbuddytime_la_LIBADD = \ +buddytimedir = $(PURPLE_LIBDIR) +buddytime_la_LDFLAGS = -module -avoid-version +buddytime_LTLIBRARIES = buddytime.la +buddytime_la_SOURCES = \ -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
-DDATADIR=\"$(PIDGIN_DATADIR)\" \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddyedit.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,429 @@
+/************************************************************************* + * A Gaim plugin that adds an edit to to buddies allowing you to change + * various details you can't normally change. It also provides a mechanism + * for subsequent plugins to add themselves to that dialog. + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Some code copyright (C) 2006, Richard Laager <rlaager@users.sf.net> + * 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 + *************************************************************************/ +#define PLUGIN "core-kleptog-buddyedit" +#include "gaim-compat.h" +#include "debug.h" /* Debug output functions */ +#include "request.h" /* Requests stuff */ +static GaimPlugin *plugin_self; +buddyedit_editcomplete_cb(GaimBlistNode * data, GaimRequestFields * fields) + gboolean blist_destroy = FALSE; + GaimBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */ + /* Account detail stuff */ + case GAIM_BLIST_BUDDY_NODE: + GaimBuddy *buddy = (GaimBuddy *) data; + GaimAccount *account = gaim_request_fields_get_account(fields, "account"); + const char *name = gaim_request_fields_get_string(fields, "name"); + const char *alias = gaim_request_fields_get_string(fields, "alias"); + /* If any details changes, create the buddy */ + if((account != buddy->account) || strcmp(name, buddy->name)) + GaimBuddy *newbuddy = gaim_buddy_new(account, name, alias); + gaim_blist_add_buddy(newbuddy, NULL, NULL, data); /* Copy it to correct location */ + /* Now this is ugly, but we want to copy the settings and avoid issues with memory management */ + tmp = ((GaimBlistNode *) buddy)->settings; + ((GaimBlistNode *) buddy)->settings = ((GaimBlistNode *) newbuddy)->settings; + ((GaimBlistNode *) newbuddy)->settings = tmp; + data = (GaimBlistNode *) newbuddy; + gaim_blist_alias_buddy(buddy, alias); + case GAIM_BLIST_CONTACT_NODE: + GaimContact *contact = (GaimContact *) data; + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_contact_set_alias(contact, alias); + case GAIM_BLIST_GROUP_NODE: + GaimGroup *group = (GaimGroup *) data; + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_blist_rename_group(group, alias); + case GAIM_BLIST_CHAT_NODE: + GaimChat *chat = (GaimChat *) data; + gboolean new_chat = FALSE; + GList *list = NULL, *tmp; + gc = gaim_account_get_connection(chat->account); + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc); + GaimAccount *newaccount = gaim_request_fields_get_account(fields, "account"); + /* In Gaim2 each prot_chat_entry has a field "required". We use + * this to determine if a field is important enough to recreate + * the chat if it changes. Non-required fields we jsut change + * in-situ. In Gaim1.5 this field doesn't exist so we always +#if GAIM_MAJOR_VERSION >= 2 + if(newaccount != chat->account) + const char *oldvalue, *newvalue; + for (tmp = g_list_first(list); tmp && !new_chat; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; + if(!pce->required) /* Only checking required fields at this point */ + continue; /* Not yet */ + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + if(strcmp(oldvalue, newvalue) != 0) + const char *oldvalue, *newvalue; + GHashTable *components = + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; + oldvalue = g_hash_table_lookup(chat->components, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(components, g_strdup(pce->identifier), + GaimChat *newchat = gaim_chat_new(newaccount, NULL, components); + gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */ + data = (GaimBlistNode *) newchat; + else /* Just updating values in old chat */ + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; +#if GAIM_MAJOR_VERSION >= 2 + newvalue = gaim_request_fields_get_string(fields, pce->identifier); + g_hash_table_replace(chat->components, g_strdup(pce->identifier), + const char *alias = gaim_request_fields_get_string(fields, "alias"); + gaim_blist_alias_chat(chat, alias); + case GAIM_BLIST_OTHER_NODE: + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data); + if(olddata->type == GAIM_BLIST_BUDDY_NODE) + gaim_blist_remove_buddy((GaimBuddy *) olddata); + else if(olddata->type == GAIM_BLIST_CHAT_NODE) + gaim_blist_remove_chat((GaimChat *) olddata); + gaim_blist_schedule_save(); +static GaimAccount *buddyedit_account_filter_func_data; +buddyedit_account_filter_func(GaimAccount * account) + GaimPluginProtocolInfo *gppi1 = + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(account)->prpl); + GaimPluginProtocolInfo *gppi2 = + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(buddyedit_account_filter_func_data)-> +/* Node is either a contact or a buddy */ +buddy_edit_cb(GaimBlistNode * node, gpointer data) + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node); + GaimRequestFields *fields; + GaimRequestField *field; + GaimRequestFieldGroup *group; + char *request_title = NULL; + fields = gaim_request_fields_new(); + case GAIM_BLIST_BUDDY_NODE: + GaimBuddy *buddy = (GaimBuddy *) node; + group = gaim_request_field_group_new("Buddy Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_account_new("account", "Account", buddy->account); + gaim_request_field_account_set_show_all(field, TRUE); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("name", "Name", buddy->name, FALSE); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("alias", "Alias", buddy->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Buddy"; + case GAIM_BLIST_CONTACT_NODE: + GaimContact *contact = (GaimContact *) node; + group = gaim_request_field_group_new("Contact Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_string_new("alias", "Alias", contact->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Contact"; + case GAIM_BLIST_GROUP_NODE: + GaimGroup *grp = (GaimGroup *) node; + group = gaim_request_field_group_new("Group Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_string_new("alias", "Name", grp->name, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Group"; + case GAIM_BLIST_CHAT_NODE: + GaimChat *chat = (GaimChat *) node; + group = gaim_request_field_group_new("Chat Details"); + gaim_request_fields_add_group(fields, group); + field = gaim_request_field_account_new("account", "Account", chat->account); + gaim_request_field_account_set_filter(field, buddyedit_account_filter_func); + buddyedit_account_filter_func_data = chat->account; + gaim_request_field_group_add_field(group, field); + GList *list = NULL, *tmp; + gc = gaim_account_get_connection(chat->account); + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc); + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp)) + struct proto_chat_entry *pce = tmp->data; +#if GAIM_MAJOR_VERSION >= 2 + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, + "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier, + pce->label, pce->is_int, pce->required); + continue; /* Not yet */ + value = g_hash_table_lookup(chat->components, pce->identifier); + field = gaim_request_field_string_new(pce->identifier, pce->label, value, FALSE); +#if GAIM_MAJOR_VERSION >= 2 + gaim_request_field_set_required(field, pce->required); + gaim_request_field_group_add_field(group, field); + field = gaim_request_field_string_new("alias", "Alias", chat->alias, FALSE); + gaim_request_field_group_add_field(group, field); + request_title = "Edit Chat"; + request_title = "Edit"; + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-create-fields", fields, node); + gaim_request_fields(plugin_self, + request_title, NULL, NULL, + fields, "OK", G_CALLBACK(buddyedit_editcomplete_cb), "Cancel", NULL, node); +buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data) +#if GAIM_MAJOR_VERSION < 2 + GaimBlistNodeAction *action; + GaimMenuAction *action; + /* These are the types we handle */ + case GAIM_BLIST_BUDDY_NODE: + case GAIM_BLIST_CONTACT_NODE: + case GAIM_BLIST_GROUP_NODE: + case GAIM_BLIST_CHAT_NODE: + case GAIM_BLIST_OTHER_NODE: +#if GAIM_MAJOR_VERSION < 2 + action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL); + action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL); + *menu = g_list_append(*menu, action); +plugin_load(GaimPlugin * plugin) + void *blist_handle = gaim_blist_get_handle(); + gaim_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */ + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST)); + gaim_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */ + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */ + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST)); + gaim_signal_connect(blist_handle, "blist-node-extended-menu", plugin, + GAIM_CALLBACK(buddy_menu_cb), NULL); +static GaimPluginInfo info = { + G_STRINGIFY(PLUGIN_VERSION), + "Enable editing of buddy properties", + "A plugin that adds an edit to to buddies allowing you to change various details you can't normally change. " + "It also provides a mechanism for subsequent plugins to add themselves to that dialog. ", + "Martijn van Oosterhout <kleptog@svana.org>", + "http://svana.org/kleptog/gaim/", +init_plugin(GaimPlugin * plugin) +PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info); --- a/buddytime/buddytime.c Mon Oct 22 04:40:46 2007 -0400
+++ b/buddytime/buddytime.c Mon Oct 22 05:00:47 2007 -0400
@@ -1,6 +1,13 @@
- * Plugin Name - Summary
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> * This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -14,247 +21,468 @@
* 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
#include "../common/pp_internal.h"
-#define PLUGIN_ID "gtk-plugin_pack-buddytime"
-#define PLUGIN_STATIC_NAME "buddytime"
-#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
+#define PLUGIN_STATIC_NAME CORE_PLUGIN_STATIC_NAME +#define PLUGIN_ID CORE_PLUGIN_ID
+#define SETTING_NAME "timezone" +#define CONTROL_NAME PLUGIN_ID "-" SETTING_NAME
-/******************************************************************************
- *****************************************************************************/
-#define BT_NODE_SETTING "bt-timezone"
+#include "conversation.h" -/******************************************************************************
- *****************************************************************************/
- PurpleRequestField *timezone;
+#define TIMEZONE_FLAG ((void*)1) +#define DISABLED_FLAG ((void*)2) +BuddyTimeUiOps *ui_ops = NULL; +static PurplePlugin *plugin_self;
- PurpleConversation *conv;
-/******************************************************************************
- *****************************************************************************/
-static GList *dialogs = NULL;
-static GList *widgets = NULL;
+/* Resolve specifies what the return value should mean: + * If TRUE, it's for display, we want to know the *effect* thus hiding the + * "none" value and going to going level to find the default + * If false, we only want what the user enter, thus the string "none" if + * data is here so we can use this as a callback for IPC +buddy_get_timezone(PurpleBlistNode * node, gboolean resolve, void *data) + PurpleBlistNode *datanode = NULL; -/******************************************************************************
- *****************************************************************************/
-bt_widget_new(PurpleConversation *conv) {
+ case PURPLE_BLIST_BUDDY_NODE: + datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + timezone = purple_blist_node_get_string(datanode, SETTING_NAME); - g_return_val_if_fail(conv, NULL);
- ret = g_new0(BTWidget, 1);
+ /* The effect of "none" is to stop recursion */ + if (timezone && strcmp(timezone, "none") == 0) - ret->ebox = gtk_event_box_new();
+ if (datanode->type == PURPLE_BLIST_CONTACT_NODE) + /* There is no purple_blist_contact_get_group(), though there probably should be */ + datanode = datanode->parent; + timezone = purple_blist_node_get_string(datanode, SETTING_NAME); - ret->label = gtk_label_new("label");
- gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
+ if (timezone && strcmp(timezone, "none") == 0) -/******************************************************************************
- *****************************************************************************/
-bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* Calcuates the difference between two struct tm's. */ +timezone_calc_difference(struct tm *remote_tm, struct tm *tmp_tm) - dialogs = g_list_remove(dialogs, dialog);
+ /* Note this only works because the times are + * known to be within 24 hours of each other! */ + if (remote_tm->tm_mday != tmp_tm->tm_mday) + hours_diff += (remote_tm->tm_hour - tmp_tm->tm_hour); + minutes_diff = (remote_tm->tm_min - tmp_tm->tm_min); + return (double)minutes_diff / 60.0 + hours_diff;
-bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* data is here so we can use this as a callback for IPC */ +timezone_get_time(const char *timezone, struct tm *tm, double *diff, void *data) + struct state *tzinfo = timezone_load(timezone); + localsub(&now, 0, tm, tzinfo); + /* Store the current TZ value. */ + old_tz = g_getenv("TZ"); - dialogs = g_list_remove(dialogs, dialog);
+ g_setenv("TZ", timezone, TRUE); + tm_tmp = localtime(&now); + *tm = *tm_tmp; /* Must copy, localtime uses local buffer */ + /* Reset the old TZ value. */ + g_setenv("TZ", old_tz, TRUE); + /* Calculate user's localtime, and compare. If same, no output */ + tm_tmp = localtime(&now); + if (tm_tmp->tm_hour == tm->tm_hour && tm_tmp->tm_min == tm->tm_min) + *diff = timezone_calc_difference(tm, tm_tmp); -bt_show_dialog(PurpleBlistNode *node) {
- PurpleRequestFields *fields;
- PurpleRequestFieldGroup *group;
- PurpleAccount *account = NULL;
+timezone_createconv_cb(PurpleConversation * conv, void *data) + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM) - dialog = g_new0(BTDialog, 1);
+ name = purple_conversation_get_name(conv); + buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
+ timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE, NULL);
+ ret = timezone_get_time(timezone, &tm, &diff, NULL); - current = purple_blist_node_get_int(node, BT_NODE_SETTING);
- /* TODO: set account from node */
+ const char *text = purple_time_format(&tm); - /* build the request fields */
- fields = purple_request_fields_new();
- group = purple_request_field_group_new(NULL);
- purple_request_fields_add_group(fields, group);
+ str = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "Remote Local Time: %s (%.4g hour behind)", + "Remote Local Time: %s (%.4g hours behind)", diff), + str = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "Remote Local Time: %s (%.4g hour ahead)", + "Remote Local Time: %s (%.4g hours ahead)", diff), - dialog->timezone = purple_request_field_choice_new("timezone",
- purple_request_field_group_add_field(group, dialog->timezone);
+ purple_conversation_write(conv, PLUGIN_STATIC_NAME, str, PURPLE_MESSAGE_SYSTEM, time(NULL)); - purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
+buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data) + PurpleRequestField *list; + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data); + case PURPLE_BLIST_BUDDY_NODE: + node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data); + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + /* code handles either case */ + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: -// purple_request_field_choice_set_default_value(dialog->timezone, current);
-// purple_request_field_coice_set_value(dialog->timezone, current);
+ list = purple_request_fields_get_field(fields, CONTROL_NAME); + if (ui_ops != NULL && ui_ops->get_timezone_menu_selection != NULL) + const char *seldata = ui_ops->get_timezone_menu_selection(list->ui_data); + purple_blist_node_remove_setting(node, SETTING_NAME); + purple_blist_node_set_string(node, SETTING_NAME, seldata); + sellist = purple_request_field_list_get_selected(list); + seldata = purple_request_field_list_get_data(list, sellist->data); - /* TODO: set who from blist node */
- purple_request_fields(NULL, _("Select timezone"),
- _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
- _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
- account, NULL /* who */, NULL, dialog);
- dialogs = g_list_append(dialogs, dialog);
+ /* Otherwise, it's fixed value and this means deletion */ + if (seldata == TIMEZONE_FLAG) + purple_blist_node_set_string(node, SETTING_NAME, sellist->data); + else if (seldata == DISABLED_FLAG) + purple_blist_node_set_string(node, SETTING_NAME, "none"); + purple_blist_node_remove_setting(node, SETTING_NAME); +buddy_add_timezone_cb(char *filename, void *data) + PurpleRequestField *field = (PurpleRequestField *) data; + if (isupper(filename[0])) + purple_request_field_list_add(field, filename, TIMEZONE_FLAG); -bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
+buddytimezone_createfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data) + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_createfields_cb(%p,%p)\n", fields, data); + PurpleRequestField *field; + PurpleRequestFieldGroup *group; + case PURPLE_BLIST_BUDDY_NODE: + case PURPLE_BLIST_CONTACT_NODE: + case PURPLE_BLIST_GROUP_NODE: + case PURPLE_BLIST_CHAT_NODE: + case PURPLE_BLIST_OTHER_NODE: + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + timezone = buddy_get_timezone(data, FALSE, NULL); + if (ui_ops != NULL && ui_ops->create_menu) + purple_request_field_new(CONTROL_NAME, + is_default ? "Default timezone for group" : "Timezone of contact", + PURPLE_REQUEST_FIELD_LIST); + field->ui_data = ui_ops->create_menu(timezone); + purple_request_field_list_new(CONTROL_NAME, + is_default ? "Default timezone for group" : + "Timezone of contact (type to select)"); + purple_request_field_list_set_multi_select(field, FALSE); + purple_request_field_list_add(field, "<Default>", ""); + purple_request_field_list_add(field, "<Disabled>", DISABLED_FLAG); + recurse_directory("/usr/share/zoneinfo/", buddy_add_timezone_cb, field); + if (strcmp(timezone, "none") == 0) + purple_request_field_list_add_selected(field, "<Disabled>"); + purple_request_field_list_add_selected(field, timezone); + purple_request_field_list_add_selected(field, "<Default>"); + purple_request_field_group_add_field(group, field); +marshal_POINTER__POINTER_BOOL(PurpleCallback cb, va_list args, void *data, + void *arg1 = va_arg(args, void *); + gboolean arg2 = va_arg(args, gboolean); + ret_val = ((gpointer (*)(void *, gboolean, void *))cb)(arg1, arg2, data); + if (return_val != NULL) -bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
- PurpleMenuAction *action;
- if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
+marshal_POINTER__POINTER_POINTER_POINTER(PurpleCallback cb, va_list args, void *data, + void *arg1 = va_arg(args, void *); + void *arg2 = va_arg(args, void *); + void *arg3 = va_arg(args, void *); - /* ignore chats and groups */
- if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
- action = purple_menu_action_new(_("Timezone"),
- PURPLE_CALLBACK(bt_edit_timezone_cb),
- (*menu) = g_list_append(*menu, action);
+ ret_val = ((gpointer (*)(void *, void *, void *, void *))cb)(arg1, arg2, arg3, data); + if (return_val != NULL) -/******************************************************************************
- *****************************************************************************/
+load_ui_plugin(gpointer data) + PurplePlugin *ui_plugin; + ui_plugin_id = g_strconcat(purple_core_get_ui(), "-", PLUGIN_STATIC_NAME, NULL); + ui_plugin = purple_plugins_find_with_id(ui_plugin_id); + if (!purple_plugin_load(ui_plugin)) + purple_notify_error(ui_plugin, NULL, _("Failed to load the Buddy Timezone UI."), + ui_plugin->error ? ui_plugin->error : ""); -/******************************************************************************
- *****************************************************************************/
-plugin_load(PurplePlugin *plugin) {
- purple_signal_connect(purple_blist_get_handle(),
- "blist-node-extended-menu",
- PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
+plugin_load(PurplePlugin * plugin) + purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin, + PURPLE_CALLBACK(timezone_createconv_cb), NULL); + purple_plugin_ipc_register(plugin, BUDDYTIME_BUDDY_GET_TIMEZONE, + PURPLE_CALLBACK(buddy_get_timezone), + marshal_POINTER__POINTER_BOOL, + purple_value_new(PURPLE_TYPE_STRING), + purple_value_new(PURPLE_TYPE_SUBTYPE, + PURPLE_SUBTYPE_BLIST_NODE), + purple_value_new(PURPLE_TYPE_BOOLEAN)); + purple_plugin_ipc_register(plugin, BUDDYTIME_TIMEZONE_GET_TIME, + PURPLE_CALLBACK(timezone_get_time), + marshal_POINTER__POINTER_POINTER_POINTER, + purple_value_new(PURPLE_TYPE_INT), + purple_value_new(PURPLE_TYPE_POINTER), + purple_value_new(PURPLE_TYPE_POINTER)); + /* This is done as an idle callback to avoid an infinite loop + * when we try to load the UI plugin which depends on this plugin + * which isn't officially loaded yet. */ + purple_timeout_add(0, load_ui_plugin, NULL);
-plugin_unload(PurplePlugin *plugin) {
-static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC, /* Magic */
- PURPLE_MAJOR_VERSION, /* Purple Major Version */
- PURPLE_MINOR_VERSION, /* Purple Minor Version */
- PURPLE_PLUGIN_STANDARD, /* plugin type */
- PIDGIN_PLUGIN_TYPE, /* ui requirement */
- NULL, /* dependencies */
- PURPLE_PRIORITY_DEFAULT, /* priority */
- PLUGIN_ID, /* plugin id */
- PP_VERSION, /* version */
- NULL, /* description */
- PLUGIN_AUTHOR, /* author */
- PP_WEBSITE, /* website */
- plugin_load, /* load */
- plugin_unload, /* unload */
+static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, /**< type */ + NULL, /**< ui_requirement */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + PP_VERSION, /**< version */ + NULL, /**< description */ + PLUGIN_AUTHOR, /**< author */ + PP_WEBSITE, /**< homepage */ + plugin_load, /**< load */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + NULL, /**< reserved 1 */ + NULL, /**< reserved 2 */ + NULL, /**< reserved 3 */ + NULL /**< reserved 4 */ -init_plugin(PurplePlugin *plugin) {
+init_plugin(PurplePlugin * plugin) bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
info.name = _("Buddy Time");
- info.summary = _("summary");
- info.description = _("description");
+ info.summary = _("Quickly see the local time of a buddy"); + info.description = _("Quickly see the local time of a buddy"); -PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
+PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.c.old Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,260 @@
+ * Plugin Name - Summary + * 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., 51 Franklin Street, Fifth Floor, Boston, MA +#include "../common/pp_internal.h" +#define PLUGIN_ID "gtk-plugin_pack-buddytime" +#define PLUGIN_STATIC_NAME "buddytime" +#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>" +/****************************************************************************** + *****************************************************************************/ +#define BT_NODE_SETTING "bt-timezone" +/****************************************************************************** + *****************************************************************************/ + PurpleRequestField *timezone; + PurpleConversation *conv; +/****************************************************************************** + *****************************************************************************/ +static GList *dialogs = NULL; +static GList *widgets = NULL; +/****************************************************************************** + *****************************************************************************/ +bt_widget_new(PurpleConversation *conv) { + g_return_val_if_fail(conv, NULL); + ret = g_new0(BTWidget, 1); + ret->ebox = gtk_event_box_new(); + ret->label = gtk_label_new("label"); + gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label); +/****************************************************************************** + *****************************************************************************/ +bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) { + BTDialog *dialog = (BTDialog *)data; + dialogs = g_list_remove(dialogs, dialog); +bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) { + BTDialog *dialog = (BTDialog *)data; + dialogs = g_list_remove(dialogs, dialog); +bt_show_dialog(PurpleBlistNode *node) { + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleAccount *account = NULL; + dialog = g_new0(BTDialog, 1); + current = purple_blist_node_get_int(node, BT_NODE_SETTING); + /* TODO: set account from node */ + /* build the request fields */ + fields = purple_request_fields_new(); + group = purple_request_field_group_new(NULL); + purple_request_fields_add_group(fields, group); + dialog->timezone = purple_request_field_choice_new("timezone", + purple_request_field_group_add_field(group, dialog->timezone); + purple_request_field_choice_add(dialog->timezone, _("Clear setting")); + purple_request_field_choice_add(dialog->timezone, _("GMT-12")); + purple_request_field_choice_add(dialog->timezone, _("GMT-11")); + purple_request_field_choice_add(dialog->timezone, _("GMT-10")); + purple_request_field_choice_add(dialog->timezone, _("GMT-9")); + purple_request_field_choice_add(dialog->timezone, _("GMT-8")); + purple_request_field_choice_add(dialog->timezone, _("GMT-7")); + purple_request_field_choice_add(dialog->timezone, _("GMT-6")); + purple_request_field_choice_add(dialog->timezone, _("GMT-5")); + purple_request_field_choice_add(dialog->timezone, _("GMT-4")); + purple_request_field_choice_add(dialog->timezone, _("GMT-3")); + purple_request_field_choice_add(dialog->timezone, _("GMT-2")); + purple_request_field_choice_add(dialog->timezone, _("GMT-1")); + purple_request_field_choice_add(dialog->timezone, _("GMT")); + purple_request_field_choice_add(dialog->timezone, _("GMT+1")); + purple_request_field_choice_add(dialog->timezone, _("GMT+2")); + purple_request_field_choice_add(dialog->timezone, _("GMT+3")); + purple_request_field_choice_add(dialog->timezone, _("GMT+4")); + purple_request_field_choice_add(dialog->timezone, _("GMT+5")); + purple_request_field_choice_add(dialog->timezone, _("GMT+6")); + purple_request_field_choice_add(dialog->timezone, _("GMT+7")); + purple_request_field_choice_add(dialog->timezone, _("GMT+8")); + purple_request_field_choice_add(dialog->timezone, _("GMT+9")); + purple_request_field_choice_add(dialog->timezone, _("GMT+10")); + purple_request_field_choice_add(dialog->timezone, _("GMT+11")); + purple_request_field_choice_add(dialog->timezone, _("GMT+12")); +// purple_request_field_choice_set_default_value(dialog->timezone, current); +// purple_request_field_coice_set_value(dialog->timezone, current); + /* TODO: set who from blist node */ + purple_request_fields(NULL, _("Select timezone"), + _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb), + _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb), + account, NULL /* who */, NULL, dialog); + dialogs = g_list_append(dialogs, dialog); +bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) { +bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) { + PurpleMenuAction *action; + if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE) + /* ignore chats and groups */ + if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node)) + action = purple_menu_action_new(_("Timezone"), + PURPLE_CALLBACK(bt_edit_timezone_cb), + (*menu) = g_list_append(*menu, action); +/****************************************************************************** + *****************************************************************************/ +/****************************************************************************** + *****************************************************************************/ +plugin_load(PurplePlugin *plugin) { + purple_signal_connect(purple_blist_get_handle(), + "blist-node-extended-menu", + PURPLE_CALLBACK(bt_blist_drawing_menu_cb), +plugin_unload(PurplePlugin *plugin) { +static PurplePluginInfo info = { + PURPLE_PLUGIN_MAGIC, /* Magic */ + PURPLE_MAJOR_VERSION, /* Purple Major Version */ + PURPLE_MINOR_VERSION, /* Purple Minor Version */ + PURPLE_PLUGIN_STANDARD, /* plugin type */ + PIDGIN_PLUGIN_TYPE, /* ui requirement */ + NULL, /* dependencies */ + PURPLE_PRIORITY_DEFAULT, /* priority */ + PLUGIN_ID, /* plugin id */ + PP_VERSION, /* version */ + NULL, /* description */ + PLUGIN_AUTHOR, /* author */ + PP_WEBSITE, /* website */ + plugin_load, /* load */ + plugin_unload, /* unload */ +init_plugin(PurplePlugin *plugin) { + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _("Buddy Time"); + info.summary = _("summary"); + info.description = _("description"); +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info) --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,52 @@
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@users.sf.net> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> + * 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 +#define CORE_PLUGIN_STATIC_NAME "buddytime" +#define CORE_PLUGIN_ID "core-kleptog-" CORE_PLUGIN_STATIC_NAME +#define PLUGIN_AUTHOR "Martijn van Oosterhout <kleptog@svana.org>" \ + "\n\t\t\tRichard Laager <rlaager@pidgin.im>" +#define BUDDYTIME_BUDDY_GET_TIMEZONE "buddy_get_timezone" +#define BUDDYTIME_TIMEZONE_GET_TIME "timezone_get_time" +typedef struct _BuddyTimeUiOps BuddyTimeUiOps; + void *(*create_menu)(const char *selected); /**< Creates a timezone menu. */ + const char * (*get_timezone_menu_selection)(void *ui_data); /**< Retrieves the menu setting. */ + void (*_buddytime_reserved1)(void); + void (*_buddytime_reserved2)(void); + void (*_buddytime_reserved3)(void); + void (*_buddytime_reserved4)(void); +#endif /* _BUDDYTIME_H_ */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtkbuddytime.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,148 @@
+ * Buddy Time - Displays a buddy's local time + * A libpurple plugin that allows you to configure a timezone on a per-contact + * basis so it can display the localtime of your contact when a conversation + * starts. Convenient if you deal with contacts from many parts of the + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im> + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org> + * 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 +#include "../common/pp_internal.h" +#define PLUGIN_STATIC_NAME "gtkbuddytime" +#define PLUGIN_ID PIDGIN_UI "-buddytime" +PurplePlugin *core_plugin = NULL; +buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data) + timezone = purple_plugin_ipc_call(core_plugin, BUDDYTIME_BUDDY_GET_TIMEZONE, + ret = GPOINTER_TO_INT(purple_plugin_ipc_call(core_plugin, BUDDYTIME_TIMEZONE_GET_TIME, + NULL, timezone, &tm, &diff)); + newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone); + const char *timetext = purple_time_format(&tm); + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "%s\n<b>Local Time:</b> %s (%.4g hour behind)", + "%s\n<b>Local Time:</b> %s (%.4g hours behind)", diff), + *text, timetext, diff); + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE, + "%s\n<b>Local Time:</b> %s (%.4g hour ahead)", + "%s\n<b>Local Time:</b> %s (%.4g hours ahead)", diff), + *text, timetext, diff); } +plugin_load(PurplePlugin * plugin) + purple_signal_connect(pidgin_blist_get_handle(), "drawing-tooltip", plugin, + PURPLE_CALLBACK(buddytimezone_tooltip_cb), NULL); + core_plugin = purple_plugins_find_with_id(CORE_PLUGIN_ID); + return (core_plugin != NULL); +static PurplePluginInfo info = + PURPLE_PLUGIN_STANDARD, /**< type */ + PIDGIN_PLUGIN_TYPE, /**< ui_requirement */ + PURPLE_PLUGIN_FLAG_INVISIBLE, /**< flags */ + NULL, /**< dependencies */ + PURPLE_PRIORITY_DEFAULT, /**< priority */ + PP_VERSION, /**< version */ + NULL, /**< description */ + PLUGIN_AUTHOR, /**< author */ + PP_WEBSITE, /**< homepage */ + plugin_load, /**< load */ + NULL, /**< extra_info */ + NULL, /**< prefs_info */ + NULL, /**< reserved 1 */ + NULL, /**< reserved 2 */ + NULL, /**< reserved 3 */ + NULL /**< reserved 4 */ +init_plugin(PurplePlugin * plugin) + info.dependencies = g_list_append(info.dependencies, CORE_PLUGIN_ID); + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + info.name = _("Buddy Time (Pidgin UI)"); + info.summary = _("Pidgin user interface for the Buddy Time plugin."); + info.description = _("Pidgin user interface for the Buddy Time plugin."); +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezone.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,237 @@
+/************************************************************************* + * GTK Timezone widget module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * This module creates the GTK widget used to select timezones. It's here to + * clearly seperate the GTK stuff from the plugin itself. + *************************************************************************/ +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + struct nodestate stack[4]; +static inline const char * +menuitem_get_label(GtkMenuItem * menuitem) + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem)))); +menu_get_first_menuitem(GtkWidget * menu) + GList *list = gtk_container_get_children(GTK_CONTAINER(menu)); + GtkMenuItem *selection = GTK_MENU_ITEM(g_list_nth_data(list, 0)); +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu) + const char *label = menuitem_get_label(menuitem); + if(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = GTK_WIDGET(menu_get_first_menuitem(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent))); + label2 = menuitem_get_label(parentitem); + if(strcmp(label2, MORE_STRING) != 0) + temp = g_strconcat(label2, "/", str, NULL); + GtkMenuItem *selection = menu_get_first_menuitem(menu); + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu))); + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection))); + gtk_label_set_text(label, str); + gtk_widget_show(GTK_WIDGET(selection)); + gtk_option_menu_set_history(optionmenu, 0); + printf("optionmenu=%p, menu=%p, menuitem=%p, label=%p\n", optionmenu, menu, selection, +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + elements = g_strsplit(path, "/", 4); + for (i = 0; i < state->currdepth && state->stack[i].string; i++) + if(strcmp(elements[i], state->stack[i].string) != 0) + /* i is now the index of the first non-matching element, so free the rest */ + for (j = i; j < state->currdepth; j++) + g_free(state->stack[j].string); + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + menuitem = gtk_menu_item_new_with_label(elements[i]); + gtk_menu_append(parent, menuitem); + if(elements[i + 1] != NULL) /* Has submenu */ + state->stack[i].submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu); + state->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_timezone_menu(const char *selected) + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(MORE_STRING); + gtk_menu_append(menu, menuitem); + state.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state); + for (i = 0; i < state.currdepth; i++) + g_free(state.stack[i].string); + optionmenu = gtk_option_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu); + gtk_widget_show_all(optionmenu); + if(strcmp(selected, "") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2); + gtk_widget_hide(selection); + else if(strcmp(selected, "none") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1); + gtk_widget_hide(selection); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +get_timezone_menu_selection(void *widget) + GtkOptionMenu *menu = GTK_OPTION_MENU(widget); + int sel = gtk_option_menu_get_history(menu); + if(sel == 2) /* Default */ + if(sel == 1) /* Disabled */ + GtkLabel *l = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu))); + return gtk_label_get_text(l); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezonetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,343 @@
+/************************************************************************* + * GTK Timezone test program + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * A test program to play with different ways that user could select from + * the huge list of timezones. Eventually things tested here should migrate + * to the module itself. + *************************************************************************/ +#define PACKAGE "Hello World" +#define DISABLED_STRING "<Disabled>" +#define DEFAULT_STRING "<Default>" +#define MORE_STRING "More..." + * Terminate the main loop. +on_destroy(GtkWidget * widget, gpointer data) + struct nodestate stack[4]; +static inline const char * +menuitem_get_label(GtkMenuItem * menuitem) + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem)))); +menu_get_first_menuitem(GtkMenu * menu) + GList *list = gtk_container_get_children(GTK_CONTAINER(menu)); + GtkWidget *selection = GTK_WIDGET(g_list_nth_data(list, 0)); +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu) + const char *label = menuitem_get_label(menuitem); +// printf( "menuitem = %s(%p), menu = %s(%p)\n", G_OBJECT_TYPE_NAME(menuitem), menuitem, G_OBJECT_TYPE_NAME(menu), menu ); + if(strcmp(label, DEFAULT_STRING) == 0) + else if(strcmp(label, DISABLED_STRING) == 0) + selection = menu_get_first_menuitem(GTK_MENU(menu)); + gtk_widget_hide(selection); + char *str = g_strdup(label); + GtkMenuItem *parentitem; + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem)); +// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent); + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent))); +// printf( "parentitem = %s(%p)\n", G_OBJECT_TYPE_NAME(parentitem), parentitem); + label2 = menuitem_get_label(parentitem); + if(strcmp(label2, MORE_STRING) != 0) + temp = g_strconcat(label2, "/", str, NULL); + GtkWidget *selection = menu_get_first_menuitem(GTK_MENU(menu)); + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu))); + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection))); + gtk_label_set_text(label, str); + gtk_widget_show(GTK_WIDGET(selection)); + gtk_option_menu_set_history(optionmenu, 0); +make_menu_cb(char *path, struct state *state) + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */ + elements = g_strsplit(path, "/", 4); + for (i = 0; i < state->currdepth && state->stack[i].string; i++) + if(strcmp(elements[i], state->stack[i].string) != 0) + /* i is now the index of the first non-matching element, so free the rest */ + for (j = i; j < state->currdepth; j++) + g_free(state->stack[j].string); + GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter; + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu; + if(i == 0 && elements[1] == NULL) + gtk_tree_store_append(state->store, &state->stack[i].iter, parent); + gtk_tree_store_set(state->store, &state->stack[i].iter, STRING_COLUMN, elements[i], -1); + state->stack[i].string = g_strdup(elements[i]); + menuitem = gtk_menu_item_new_with_label(elements[i]); + gtk_menu_append(parent, menuitem); + if(elements[i + 1] != NULL) /* Has submenu */ + state->stack[i].submenu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu); + state->stack[i].string = g_strdup(elements[i]); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), +make_menu2(char *selected) + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */ + G_TYPE_STRING); /* Timezone */ + GtkCellRenderer *renderer; + GtkTreeIter iter1, iter2; + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */ + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DISABLED_STRING, -1); + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */ + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DEFAULT_STRING, -1); + gtk_tree_store_append(store, &iter2, &iter1); + gtk_tree_store_set(store, &iter1, STRING_COLUMN, MORE_STRING, -1); + GtkWidget *optionmenu, *menuitem, *selection; + menuitem = gtk_menu_item_new_with_label(selected); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu); + gtk_menu_append(menu, menuitem); + menuitem = gtk_menu_item_new_with_label(MORE_STRING); + gtk_menu_append(menu, menuitem); + state.extra = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra); + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state); + for (i = 0; i < state.currdepth; i++) + g_free(state.stack[i].string); + tree = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tree), renderer, TRUE); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tree), renderer, "text", 0, NULL); + gtk_widget_show_all(tree); + optionmenu = gtk_option_menu_new(); + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu); + gtk_widget_show_all(optionmenu); + if(strcmp(selected, "") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2); + gtk_widget_hide(selection); + else if(strcmp(selected, "none") == 0) + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1); + gtk_widget_hide(selection); + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0); +main(int argc, char *argv[]) + gtk_init(&argc, &argv); + /* create the main, top level, window */ + window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + /* give the window a 20px wide border */ + gtk_container_set_border_width(GTK_CONTAINER(window), 20); + /* give it the title */ + gtk_window_set_title(GTK_WINDOW(window), PACKAGE " " VERSION); + /* open it a bit wider so that both the label and title show up */ + gtk_window_set_default_size(GTK_WINDOW(window), 200, 50); + /* Connect the destroy event of the window with our on_destroy function + * When the window is about to be destroyed we get a notificaiton and + * stop the main GTK loop + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_destroy), NULL); + /* Create the "Hello, World" label */ + label = gtk_label_new("Select a timezone:"); + gtk_widget_show(label); + frame = gtk_vbox_new(FALSE, 0); + gtk_widget_show(frame); + /* and insert it into the main window */ + gtk_container_add(GTK_CONTAINER(window), frame); + gtk_container_add(GTK_CONTAINER(frame), label); + menu = make_menu2("none"); + gtk_container_add(GTK_CONTAINER(frame), menu); + /* make sure that everything, window and label, are visible */ + gtk_widget_show(window); + /* start the main loop */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,1250 @@
+/************************************************************************* + * copied from the Olson Timezone code, licence unchanged. + * by Martijn van Oosterhout <kleptog@svana.org> April 2006 + * Original Licence below (public domain). + * This code has been copied from the Olson Timezone code, but heavily + * adapted to meet my needs. In particular, you can load multiple timezones + * and specify which timezone to convert with. + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +#define TM_GMTOFF tm_gmtoff +** Leap second handling from Bradley White. +** POSIX-style TZ environment variable handling from Guy Harris. +#include "float.h" /* for FLT_MAX and DBL_MAX */ +#define TZ_ABBR_MAX_LEN 16 +#endif /* !defined TZ_ABBR_MAX_LEN */ +#ifndef TZ_ABBR_CHAR_SET +#define TZ_ABBR_CHAR_SET \ + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" +#endif /* !defined TZ_ABBR_CHAR_SET */ +#ifndef TZ_ABBR_ERR_CHAR +#define TZ_ABBR_ERR_CHAR '_' +#endif /* !defined TZ_ABBR_ERR_CHAR */ +** SunOS 4.1.1 headers lack O_BINARY. +#define OPEN_MODE (O_RDONLY | O_BINARY) +#endif /* defined O_BINARY */ +#define OPEN_MODE O_RDONLY +#endif /* !defined O_BINARY */ +** Someone might make incorrect use of a time zone abbreviation: +** 1. They might reference tzname[0] before calling tzset (explicitly +** 2. They might reference tzname[1] before calling tzset (explicitly +** 3. They might reference tzname[1] after setting to a time zone +** in which Daylight Saving Time is never observed. +** 4. They might reference tzname[0] after setting to a time zone +** in which Standard Time is never observed. +** 5. They might reference tm.TM_ZONE after calling offtime. +** What's best to do in the above cases is open to debate; +** for now, we just set things up so that in any of the five cases +** WILDABBR is used. Another possibility: initialize tzname[0] to the +** string "tzname[0] used before set", and similarly for the other cases. +** And another: initialize tzname[0] to "ERA", with an explanation in the +** manual page of what this "time zone abbreviation" means (doing this so +** that tzname[0] has the "normal" length of three characters). +#endif /* !defined WILDABBR */ +static char wildabbr[] = WILDABBR; +static const char gmt[] = "GMT"; +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** We default to US rules as of 1999-08-17. +** POSIX 1003.1 section 8.1.1 says that the default DST rules are +** implementation dependent; for historical reasons, US rules are a +#define TZDEFRULESTRING ",M4.1.0,M10.5.0" +#endif /* !defined TZDEFDST */ +struct ttinfo { /* time type information */ + long tt_gmtoff; /* UTC offset in seconds */ + int tt_isdst; /* used to set tm_isdst */ + int tt_abbrind; /* abbreviation list index */ + int tt_ttisstd; /* TRUE if transition is std time */ + int tt_ttisgmt; /* TRUE if transition is UTC */ +struct lsinfo { /* leap second information */ + time_t ls_trans; /* transition time */ + long ls_corr; /* correction to apply */ +#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) +#define MY_TZNAME_MAX TZNAME_MAX +#endif /* defined TZNAME_MAX */ +#define MY_TZNAME_MAX 255 +#endif /* !defined TZNAME_MAX */ + time_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; + struct ttinfo ttis[TZ_MAX_TYPES]; + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt), + (2 * (MY_TZNAME_MAX + 1)))]; + struct lsinfo lsis[TZ_MAX_LEAPS]; + int r_type; /* type of rule--see below */ + int r_day; /* day number of rule */ + int r_week; /* week number of rule */ + int r_mon; /* month number of rule */ + long r_time; /* transition time of rule */ +#define JULIAN_DAY 0 /* Jn - Julian day */ +#define DAY_OF_YEAR 1 /* n - day of year */ +#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ +** Prototypes for static functions. +static long detzcode P((const char * codep)); +static const char * getzname P((const char * strp)); +static const char * getqzname P((const char * strp, const char delim)); +static const char * getnum P((const char * strp, int * nump, int min, +static const char * getsecs P((const char * strp, long * secsp)); +static const char * getoffset P((const char * strp, long * offsetp)); +static const char * getrule P((const char * strp, struct rule * rulep)); +static void gmtload P((struct state * sp)); +struct tm * gmtsub P((const time_t * timep, long offset, +struct tm * localsub P((const time_t * timep, long offset, + struct tm * tmp, struct state *sp)); +static int increment_overflow P((int * number, int delta)); +static int leaps_thru_end_of P((int y)); +static struct tm * timesub P((const time_t * timep, long offset, + const struct state * sp, struct tm * tmp)); +static time_t transtime P((time_t janfirst, int year, + const struct rule * rulep, long offset)); +static int tzload P((const char * name, struct state * sp)); +static int tzparse P((const char * name, struct state * sp, +struct state *timezone_load P((const char * name)); +static struct state * gmtptr; +#endif /* defined ALL_STATE */ +static struct state gmtmem; +#define gmtptr (&gmtmem) +#define TZ_STRLEN_MAX 255 +#endif /* !defined TZ_STRLEN_MAX */ +//static char lcl_TZname[TZ_STRLEN_MAX + 1]; +//static int lcl_is_set; +** Section 4.12.3 of X3.159-1989 requires that +** Except for the strftime function, these functions [asctime, +** ctime, gmtime, localtime] return values in one of two static +** objects: a broken-down time structure and an array of char. +** Thanks to Paul Eggert for noting this. +#endif /* defined USG_COMPAT */ +#endif /* defined ALTZONE */ +const char * const codep; + result = (codep[0] & 0x80) ? ~0L : 0L; + for (i = 0; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); +register const char * name; +register struct state * const sp; + register const char * p; + if (name == NULL && (name = TZDEFAULT) == NULL) + ** Section 4.9.1 of the C standard says that + ** "FILENAME_MAX expands to an integral constant expression + ** that is the size needed for an array of char large enough + ** to hold the longest file name string that the implementation + ** guarantees can be opened." + char fullname[FILENAME_MAX + 1]; + doaccess = name[0] == '/'; + if ((p = tzdir) == NULL) + if ((strlen(p) + strlen(name) + 1) >= sizeof fullname) + (void) strcpy(fullname, p); + (void) strcat(fullname, "/"); + (void) strcat(fullname, name); + ** Set doaccess if '.' (as in "../") shows up in name. + if (strchr(name, '.') != NULL) + if (doaccess && access(name, R_OK) != 0) + if ((fid = open(name, OPEN_MODE)) == -1) + char buf[sizeof *sp + sizeof *tzhp]; + i = read(fid, u.buf, sizeof u.buf); + ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt); + ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt); + sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt); + sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt); + sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt); + sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt); + p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt; + if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || + sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || + sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || + sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || + (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || + (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) + if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */ + sp->timecnt + /* types */ + sp->typecnt * (4 + 2) + /* ttinfos */ + sp->charcnt + /* chars */ + sp->leapcnt * (4 + 4) + /* lsinfos */ + ttisstdcnt + /* ttisstds */ + ttisgmtcnt) /* ttisgmts */ + for (i = 0; i < sp->timecnt; ++i) { + sp->ats[i] = detzcode(p); + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = (unsigned char) *p++; + if (sp->types[i] >= sp->typecnt) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_gmtoff = detzcode(p); + ttisp->tt_isdst = (unsigned char) *p++; + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) + ttisp->tt_abbrind = (unsigned char) *p++; + if (ttisp->tt_abbrind < 0 || + ttisp->tt_abbrind > sp->charcnt) + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = '\0'; /* ensure '\0' at end */ + for (i = 0; i < sp->leapcnt; ++i) { + register struct lsinfo * lsisp; + lsisp->ls_trans = detzcode(p); + lsisp->ls_corr = detzcode(p); + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisstd = FALSE; + ttisp->tt_ttisstd = *p++; + if (ttisp->tt_ttisstd != TRUE && + ttisp->tt_ttisstd != FALSE) + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + ttisp->tt_ttisgmt = FALSE; + ttisp->tt_ttisgmt = *p++; + if (ttisp->tt_ttisgmt != TRUE && + ttisp->tt_ttisgmt != FALSE) + ** Out-of-sort ats should mean we're running on a + ** signed time_t system but using a data file with + ** unsigned values (or vice versa). + for (i = 0; i < sp->timecnt - 2; ++i) + if (sp->ats[i] > sp->ats[i + 1]) { + if (TYPE_SIGNED(time_t)) { + ** Ignore the end (easy). + ** Ignore the beginning (harder). + for (j = 0; j + i < sp->timecnt; ++j) { + sp->ats[j] = sp->ats[j + i]; + sp->types[j] = sp->types[j + i]; +struct state *timezone_load(name) + struct state *sp = malloc( sizeof(struct state) ); + res = tzload( name, sp ); +static const int mon_lengths[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +static const int year_lengths[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +** Given a pointer into a time zone string, scan until a character that is not +** a valid character in a zone name is found. Return a pointer to that +register const char * strp; + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && +** Given a pointer into an extended time zone string, scan until the ending +** delimiter of the zone name is located. Return a pointer to the delimiter. +** As with getzname above, the legal character set is actually quite +** restricted, with other characters producing undefined results. +** We choose not to care - allowing almost anything to be in the zone abbrev. +getqzname(register const char *strp, const char delim) +register const char * strp; + while ((c = *strp) != '\0' && c != delim) +** Given a pointer into a time zone string, extract a number from that string. +** Check that the number is within a specified range; if it is not, return +** Otherwise, return a pointer to the first character not part of the number. +getnum(strp, nump, min, max) +register const char * strp; + if (strp == NULL || !is_digit(c = *strp)) + num = num * 10 + (c - '0'); + return NULL; /* illegal value */ + return NULL; /* illegal value */ +** Given a pointer into a time zone string, extract a number of seconds, +** in hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the number +register const char * strp; + ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + ** "M10.4.6/26", which does not conform to Posix, + ** but which specifies the equivalent of + ** ``02:00 on the first Sunday on or after 23 Oct''. + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); + *secsp = num * (long) SECSPERHOUR; + strp = getnum(strp, &num, 0, MINSPERHOUR - 1); + *secsp += num * SECSPERMIN; + /* `SECSPERMIN' allows for leap seconds. */ + strp = getnum(strp, &num, 0, SECSPERMIN); +** Given a pointer into a time zone string, extract an offset, in +** [+-]hh[:mm[:ss]] form, from the string. +** If any error occurs, return NULL. +** Otherwise, return a pointer to the first character not part of the time. +getoffset(strp, offsetp) +register const char * strp; + } else if (*strp == '+') + strp = getsecs(strp, offsetp); + return NULL; /* illegal time */ +** Given a pointer into a time zone string, extract a rule in the form +** date[/time]. See POSIX section 8 for the format of "date" and "time". +** If a valid rule is not found, return NULL. +** Otherwise, return a pointer to the first character not part of the rule. +register struct rule * const rulep; + rulep->r_type = JULIAN_DAY; + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR); + } else if (*strp == 'M') { + rulep->r_type = MONTH_NTH_DAY_OF_WEEK; + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR); + strp = getnum(strp, &rulep->r_week, 1, 5); + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); + } else if (is_digit(*strp)) { + rulep->r_type = DAY_OF_YEAR; + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1); + } else return NULL; /* invalid format */ + strp = getsecs(strp, &rulep->r_time); + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ +** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the +** year, a rule, and the offset from UTC at the time that rule takes effect, +** calculate the Epoch-relative time that rule takes effect. +transtime(janfirst, year, rulep, offset) +register const struct rule * const rulep; + int d, m1, yy0, yy1, yy2, dow; + leapyear = isleap(year); + switch (rulep->r_type) { + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap + ** In non-leap years, or if the day number is 59 or less, just + ** add SECSPERDAY times the day number-1 to the time of + ** January 1, midnight, to get the day. + value = janfirst + (rulep->r_day - 1) * SECSPERDAY; + if (leapyear && rulep->r_day >= 60) + ** Just add SECSPERDAY times the day number to the time of + ** January 1, midnight, to get the day. + value = janfirst + rulep->r_day * SECSPERDAY; + case MONTH_NTH_DAY_OF_WEEK: + ** Mm.n.d - nth "dth day" of month m. + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; + ** Use Zeller's Congruence to get day-of-week of first day of + m1 = (rulep->r_mon + 9) % 12 + 1; + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year; + dow = ((26 * m1 - 2) / 10 + + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7; + ** "dow" is the day-of-week of the first day of the month. Get + ** the day-of-month (zero-origin) of the first "dow" day of the + d = rulep->r_day - dow; + for (i = 1; i < rulep->r_week; ++i) { + mon_lengths[leapyear][rulep->r_mon - 1]) + ** "d" is the day-of-month (zero-origin) of the day we want. + value += d * SECSPERDAY; + ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in + ** question. To get the Epoch-relative time of the specified local + ** time on that day, add the transition time and the current offset + return value + rulep->r_time + offset; +** Given a POSIX section 8-style TZ string, fill in the rule tables as +tzparse(name, sp, lastditch) +register struct state * const sp; + register unsigned char * typep; + register int load_result; + stdlen = strlen(name); /* length of standard zone name */ + if (stdlen >= sizeof sp->chars) + stdlen = (sizeof sp->chars) - 1; + name = getqzname(name, '>'); + stdlen = name - stdname; + stdlen = name - stdname; + name = getoffset(name, &stdoffset); + load_result = tzload(TZDEFRULES, sp); + sp->leapcnt = 0; /* so, we're off a little */ + name = getqzname(name, '>'); + dstlen = name - dstname; + dstlen = name - dstname; /* length of DST zone name */ + if (*name != '\0' && *name != ',' && *name != ';') { + name = getoffset(name, &dstoffset); + } else dstoffset = stdoffset - SECSPERHOUR; + if (*name == '\0' && load_result != 0) + name = TZDEFRULESTRING; + if (*name == ',' || *name == ';') { + register time_t janfirst; + if ((name = getrule(name, &start)) == NULL) + if ((name = getrule(name, &end)) == NULL) + sp->typecnt = 2; /* standard time and DST */ + ** Two transitions per year, from EPOCH_YEAR to 2037. + sp->timecnt = 2 * (2037 - EPOCH_YEAR + 1); + if (sp->timecnt > TZ_MAX_TIMES) + sp->ttis[0].tt_gmtoff = -dstoffset; + sp->ttis[0].tt_isdst = 1; + sp->ttis[0].tt_abbrind = stdlen + 1; + sp->ttis[1].tt_gmtoff = -stdoffset; + sp->ttis[1].tt_isdst = 0; + sp->ttis[1].tt_abbrind = 0; + for (year = EPOCH_YEAR; year <= 2037; ++year) { + starttime = transtime(janfirst, year, &start, + endtime = transtime(janfirst, year, &end, + if (starttime > endtime) { + *typep++ = 1; /* DST ends */ + *typep++ = 0; /* DST begins */ + *typep++ = 0; /* DST begins */ + *typep++ = 1; /* DST ends */ + janfirst += year_lengths[isleap(year)] * + register long theirstdoffset; + register long theirdstoffset; + register long theiroffset; + ** Initial values of theirstdoffset and theirdstoffset. + for (i = 0; i < sp->timecnt; ++i) { + if (!sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + for (i = 0; i < sp->timecnt; ++i) { + if (sp->ttis[j].tt_isdst) { + -sp->ttis[j].tt_gmtoff; + ** Initially we're assumed to be in standard time. + theiroffset = theirstdoffset; + ** Now juggle transition times and types + ** tracking offsets as you do. + for (i = 0; i < sp->timecnt; ++i) { + sp->types[i] = sp->ttis[j].tt_isdst; + if (sp->ttis[j].tt_ttisgmt) { + /* No adjustment to transition time */ + ** If summer time is in effect, and the + ** transition time was not specified as + ** standard time, add the summer time + ** offset to the transition time; + ** otherwise, add the standard time + ** offset to the transition time. + ** Transitions from DST to DDST + ** will effectively disappear since + ** POSIX provides for only one DST + if (isdst && !sp->ttis[j].tt_ttisstd) { + sp->ats[i] += dstoffset - + sp->ats[i] += stdoffset - + theiroffset = -sp->ttis[j].tt_gmtoff; + if (sp->ttis[j].tt_isdst) + theirdstoffset = theiroffset; + else theirstdoffset = theiroffset; + ** Finally, fill in ttis. + ** ttisstd and ttisgmt need not be handled. + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = FALSE; + sp->ttis[0].tt_abbrind = 0; + sp->ttis[1].tt_gmtoff = -dstoffset; + sp->ttis[1].tt_isdst = TRUE; + sp->ttis[1].tt_abbrind = stdlen + 1; + sp->typecnt = 1; /* only standard time */ + sp->ttis[0].tt_gmtoff = -stdoffset; + sp->ttis[0].tt_isdst = 0; + sp->ttis[0].tt_abbrind = 0; + sp->charcnt = stdlen + 1; + sp->charcnt += dstlen + 1; + if ((size_t) sp->charcnt > sizeof sp->chars) + (void) strncpy(cp, stdname, stdlen); + (void) strncpy(cp, dstname, dstlen); +struct state * const sp; + if (tzload(gmt, sp) != 0) + (void) tzparse(gmt, sp, TRUE); +** The easy way to behave "as if no library function calls" localtime +** is to not call it--so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior-- +** but it *is* desirable.) +** The unused offset argument is for the benefit of mktime variants. +localsub(timep, offset, tmp, sp) +const time_t * const timep; + register const struct ttinfo * ttisp; + register struct tm * result; + const time_t t = *timep; + return gmtsub(timep, offset, tmp); +#endif /* defined ALL_STATE */ + if (sp->timecnt == 0 || t < sp->ats[0]) { + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) { + for (i = 1; i < sp->timecnt; ++i) + i = (int) sp->types[i - 1]; + ** To get (wrong) behavior that's compatible with System V Release 2.0 + ** you'd replace the statement below with + ** t += ttisp->tt_gmtoff; + ** timesub(&t, 0L, sp, tmp); + result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); + tmp->tm_isdst = ttisp->tt_isdst; + tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind]; + tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; +#endif /* defined TM_ZONE */ +** gmtsub is to gmtime as localsub is to localtime. +gmtsub(timep, offset, tmp) +const time_t * const timep; + register struct tm * result; + gmtptr = (struct state *) malloc(sizeof *gmtptr); +#endif /* defined ALL_STATE */ + result = timesub(timep, offset, gmtptr, tmp); + ** Could get fancy here and deliver something such as + ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero, + ** but this is no time for a treasure hunt. + tmp->TM_ZONE = wildabbr; + else tmp->TM_ZONE = gmtptr->chars; +#endif /* defined ALL_STATE */ + tmp->TM_ZONE = gmtptr->chars; +#endif /* defined TM_ZONE */ +** Return the number of leap years through the end of the given year +** where, to make the math easy, the answer for year zero is defined as zero. + return (y >= 0) ? (y / 4 - y / 100 + y / 400) : + -(leaps_thru_end_of(-(y + 1)) + 1); +timesub(timep, offset, sp, tmp) +const time_t * const timep; +register const struct state * const sp; +register struct tm * const tmp; + register const struct lsinfo * lp; + register int idays; /* unsigned would be so 2003 */ + register const int * ip; + i = (sp == NULL) ? 0 : sp->leapcnt; +#endif /* defined ALL_STATE */ + if (*timep >= lp->ls_trans) { + if (*timep == lp->ls_trans) { + hit = ((i == 0 && lp->ls_corr > 0) || + lp->ls_corr > sp->lsis[i - 1].ls_corr); + sp->lsis[i].ls_trans == + sp->lsis[i - 1].ls_trans + 1 && + sp->lsis[i - 1].ls_corr + 1) { + tdays = *timep / SECSPERDAY; + rem = *timep - tdays * SECSPERDAY; + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { + register time_t tdelta; + tdelta = tdays / DAYSPERLYEAR; + if (tdelta - idelta >= 1 || idelta - tdelta >= 1) + idelta = (tdays < 0) ? -1 : 1; + if (increment_overflow(&newy, idelta)) + leapdays = leaps_thru_end_of(newy - 1) - + leaps_thru_end_of(y - 1); + tdays -= ((time_t) newy - y) * DAYSPERNYEAR; + seconds = tdays * SECSPERDAY + 0.5; + tdays = seconds / SECSPERDAY; + rem += seconds - tdays * SECSPERDAY; + ** Given the range, we can now fearlessly cast... + while (rem >= SECSPERDAY) { + if (increment_overflow(&y, -1)) + idays += year_lengths[isleap(y)]; + while (idays >= year_lengths[isleap(y)]) { + idays -= year_lengths[isleap(y)]; + if (increment_overflow(&y, 1)) + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) + ** The "extra" mods below avoid overflow problems. + tmp->tm_wday = EPOCH_WDAY + + ((y - EPOCH_YEAR) % DAYSPERWEEK) * + (DAYSPERNYEAR % DAYSPERWEEK) + + leaps_thru_end_of(y - 1) - + leaps_thru_end_of(EPOCH_YEAR - 1) + + tmp->tm_wday %= DAYSPERWEEK; + tmp->tm_wday += DAYSPERWEEK; + tmp->tm_hour = (int) (rem / SECSPERHOUR); + tmp->tm_min = (int) (rem / SECSPERMIN); + ** A positive leap second requires a special + ** representation. This uses "... ??:59:60" et seq. + tmp->tm_sec = (int) (rem % SECSPERMIN) + hit; + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = (int) (idays + 1); + tmp->TM_GMTOFF = offset; +#endif /* defined TM_GMTOFF */ +** Adapted from code provided by Robert Elz, who writes: +** The "best" way to do mktime I think is based on an idea of Bob +** Kridle's (so its said...) from a long time ago. +** It does a binary search of the time_t space. Since time_t's are +** just 32 bits, its a max of 32 iterations (even at 64 bits it +** would still be very reasonable). +#endif /* !defined WRONG */ +** Simplified normalize logic courtesy Paul Eggert. +increment_overflow(number, delta) + return (*number < number0) != (delta < 0); +int tz_init( const char *zoneinfo_dir ) + if( zoneinfo_dir == NULL ) + ptr = malloc( strlen(zoneinfo_dir) + 10 ); + sprintf( ptr, "%s/zone.tab", zoneinfo_dir ); + fd = open( ptr, O_RDONLY ); + tzdir = strdup(zoneinfo_dir); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,16 @@
+/************************************************************************* + * Header file for timezone module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + *************************************************************************/ +struct state *timezone_load (const char * name); +struct tm * gmtsub (const time_t * timep, long offset, +struct tm * localsub (const time_t * timep, long offset, + struct tm * tmp, struct state *sp); +int tz_init(const char *zoneinfo_dir); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/private.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,316 @@
+/************************************************************************* + * Private Header file for timezone module + * copied from Olson timezone code, licence unchanged + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +#define GRANDPARENTED "Local time zone must be set--see zic manual page" +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'. +#endif /* !defined HAVE_ADJTIME */ +#endif /* !defined HAVE_GETTEXT */ +#ifndef HAVE_INCOMPATIBLE_CTIME_R +#define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif /* !defined INCOMPATIBLE_CTIME_R */ +#ifndef HAVE_SETTIMEOFDAY +#define HAVE_SETTIMEOFDAY 3 +#endif /* !defined HAVE_SETTIMEOFDAY */ +#endif /* !defined HAVE_STRERROR */ +#endif /* !defined HAVE_SYMLINK */ +#define HAVE_SYS_STAT_H 1 +#endif /* !defined HAVE_SYS_STAT_H */ +#define HAVE_SYS_WAIT_H 1 +#endif /* !defined HAVE_SYS_WAIT_H */ +#endif /* !defined HAVE_UNISTD_H */ +#endif /* !defined HAVE_UTMPX_H */ +#define LOCALE_HOME "/usr/lib/locale" +#endif /* !defined LOCALE_HOME */ +#if HAVE_INCOMPATIBLE_CTIME_R +#define asctime_r _incompatible_asctime_r +#define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ +#include "sys/types.h" /* for time_t */ +#include "limits.h" /* for CHAR_BIT */ +#endif /* HAVE_GETTEXT */ +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */ +#endif /* HAVE_SYS_WAIT_H */ +#define WIFEXITED(status) (((status) & 0xff) == 0) +#endif /* !defined WIFEXITED */ +#define WEXITSTATUS(status) (((status) >> 8) & 0xff) +#endif /* !defined WEXITSTATUS */ +#include "unistd.h" /* for F_OK and R_OK */ +#endif /* HAVE_UNISTD_H */ +#endif /* !defined F_OK */ +#endif /* !defined R_OK */ +#endif /* !HAVE_UNISTD_H */ +/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */ +#define is_digit(c) ((unsigned)(c) - '0' <= 9) +** Workarounds for compilers/systems. +** SunOS 4.1.1 cc lacks prototypes. +** SunOS 4.1.1 headers lack EXIT_SUCCESS. +#endif /* !defined EXIT_SUCCESS */ +** SunOS 4.1.1 headers lack EXIT_FAILURE. +#endif /* !defined EXIT_FAILURE */ +** SunOS 4.1.1 headers lack FILENAME_MAX. +#endif /* defined unix */ +#endif /* !defined MAXPATHLEN */ +#define FILENAME_MAX MAXPATHLEN +#endif /* defined MAXPATHLEN */ +#define FILENAME_MAX 1024 /* Pure guesswork */ +#endif /* !defined MAXPATHLEN */ +#endif /* !defined FILENAME_MAX */ +** SunOS 4.1.1 libraries lack remove. +extern int unlink P((const char * filename)); +#endif /* !defined remove */ +** Some ancient errno.h implementations don't declare errno. +** But some newer errno.h implementations define it as a macro. +** Fix the former without affecting the latter. +#endif /* !defined errno */ +** Some time.h implementations don't declare asctime_r. +** Others might define it as a macro. +** Fix the former without affecting the latter. +extern char * asctime_r(); +** Private function declarations. +char * icalloc P((int nelem, int elsize)); +char * icatalloc P((char * old, const char * new)); +char * icpyalloc P((const char * string)); +char * imalloc P((int n)); +void * irealloc P((void * pointer, int size)); +void icfree P((char * pointer)); +void ifree P((char * pointer)); +const char *scheck P((const char *string, const char *format)); +** Finally, some convenience items. +#endif /* !defined TRUE */ +#endif /* !defined FALSE */ +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) +#endif /* !defined TYPE_BIT */ +#define TYPE_SIGNED(type) (((type) -1) < 0) +#endif /* !defined TYPE_SIGNED */ +** Since the definition of TYPE_INTEGRAL contains floating point numbers, +** it cannot be used in preprocessor directives. +#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) +#endif /* !defined TYPE_INTEGRAL */ +#ifndef INT_STRLEN_MAXIMUM +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ +#endif /* !defined INT_STRLEN_MAXIMUM */ +#endif /* defined lint */ +#endif /* defined __GNUC__ */ +#endif /* !defined lint */ +#endif /* !defined GNUC_or_lint */ +#define INITIALIZE(x) ((x) = 0) +#endif /* defined GNUC_or_lint */ +#endif /* !defined GNUC_or_lint */ +#endif /* !defined INITIALIZE */ +** For the benefit of GNU folk... +** `_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#endif /* !HAVE_GETTEXT */ +#endif /* !defined TZ_DOMAIN */ +#if HAVE_INCOMPATIBLE_CTIME_R +char *asctime_r P((struct tm const *, char *)); +char *ctime_r P((time_t const *, char *)); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ +** UNIX was a registered trademark of The Open Group in 2003. +#endif /* !defined PRIVATE_H */ --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,141 @@
+/************************************************************************* + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Provides a function to recurse a directory and call a callback for each + *************************************************************************/ +/* GLibc specific version. In this version, the entries are sorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + struct dirent **namelist; + if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0) + for (i = 0; i < ents; i++) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + else if(S_ISDIR(s.st_mode)) + char *newdirname, *newprefix; + if(ent->d_name[0] != '.') + asprintf(&newdirname, "%s%s/", dirname, ent->d_name); + asprintf(&newprefix, "%s%s/", prefix, ent->d_name); + ret = recurse_directory_int(newdirname, newprefix, func, data); +/* generic version, here they are unsorted */ +/* We assume dirname ends in a /, prefix also unless empty */ +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data) + dir = opendir(dirname); + while ((ent = readdir(dir)) != NULL) + asprintf(&ptr, "%s%s", dirname, ent->d_name); + asprintf(&ptr, "%s%s", prefix, ent->d_name); + else if(S_ISDIR(s.st_mode)) + char *newdirname, *newprefix; + if(ent->d_name[0] != '.') + asprintf(&newdirname, "%s%s/", dirname, ent->d_name); + asprintf(&newprefix, "%s%s/", prefix, ent->d_name); + ret = recurse_directory_int(newdirname, newprefix, func, data); +recurse_directory(char *dirname, DirRecurseMatch func, void *data) + char *newdirname = NULL; + if(dirname[strlen(dirname) - 1] != '/') + asprintf(&newdirname, "%s/", dirname); + ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,9 @@
+/************************************************************************* + * Header file for recursion module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + *************************************************************************/ +typedef int (*DirRecurseMatch)(char *filename, void *data); +int recurse_directory( char *dirname, DirRecurseMatch func, void *data ); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recursetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,25 @@
+/************************************************************************* + * Recursion test module + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Code to test the recursion module. + *************************************************************************/ +process_entry(char *str, void *ptr) + recurse_directory("/usr/share/zoneinfo", process_entry, main); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/timetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,28 @@
+/************************************************************************* + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + * Licenced under the GNU General Public Licence version 2. + * Code to test the timezone module. + *************************************************************************/ + time_t now = time(NULL); + state = timezone_load("Australia/Sydney"); + localsub(&now, 0, &tm, state); --- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/tzfile.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,175 @@
+/************************************************************************* + * Private Header file for timezone module + * copied from Olson timezone code, licence unchanged + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006 + *************************************************************************/ +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Information about time zone files. +#define TZDIR "/usr/share/zoneinfo" /* Default time zone object file directory */ +#endif /* !defined TZDIR */ +#define TZDEFAULT "localtime" +#endif /* !defined TZDEFAULT */ +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ +** Each file begins with. . . + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_reserved[16]; /* reserved for future use */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +** . . .followed by. . . +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UTC offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition +** time is standard time, if FALSE, +** transition time is wall clock time +** if absent, transition times are +** assumed to be wall clock time +** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition +** time is UTC, if FALSE, +** transition time is local time +** if absent, transition times are +** assumed to be local time +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +** The TZ_MAX_TIMES value below is enough to handle a bit more than a +** year's worth of solar time (corrected daily to the nearest second) or +** 138 years of Pacific Presidential Election time +** (where there are three time zone transitions every fourth year). +#define TZ_MAX_TIMES 370 +#endif /* !defined TZ_MAX_TIMES */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined NOSOLAR */ +** Must be at least 14 for Europe/Riga as of Jan 12 1995, +** as noted by Earl Chew. +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ +#endif /* !defined NOSOLAR */ +#endif /* !defined TZ_MAX_TYPES */ +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define TM_YEAR_BASE 1900 +#define EPOCH_WDAY TM_THURSDAY +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** isleap(a + b) == isleap((a + b) % 400) +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) +#endif /* !defined TZFILE_H */