summaryrefslogtreecommitdiffstats
path: root/libkcal/libical/vzic-1.3
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch)
tree67208f7c145782a7e90b123b982ca78d88cc2c87 /libkcal/libical/vzic-1.3
downloadtdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.tar.gz
tdepim-460c52653ab0dcca6f19a4f492ed2c5e4e963ab0.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'libkcal/libical/vzic-1.3')
-rw-r--r--libkcal/libical/vzic-1.3/ChangeLog57
-rw-r--r--libkcal/libical/vzic-1.3/Makefile83
-rw-r--r--libkcal/libical/vzic-1.3/Makefile.org89
-rw-r--r--libkcal/libical/vzic-1.3/README202
-rw-r--r--libkcal/libical/vzic-1.3/test-vzic.c422
-rw-r--r--libkcal/libical/vzic-1.3/vzic-dump.c409
-rw-r--r--libkcal/libical/vzic-1.3/vzic-dump.h58
-rwxr-xr-xlibkcal/libical/vzic-1.3/vzic-dump.pl222
-rwxr-xr-xlibkcal/libical/vzic-1.3/vzic-merge.pl127
-rw-r--r--libkcal/libical/vzic-1.3/vzic-output.c2325
-rw-r--r--libkcal/libical/vzic-1.3/vzic-output.h38
-rw-r--r--libkcal/libical/vzic-1.3/vzic-parse.c901
-rw-r--r--libkcal/libical/vzic-1.3/vzic-parse.h38
-rwxr-xr-xlibkcal/libical/vzic-1.3/vzic-test.pl164
-rw-r--r--libkcal/libical/vzic-1.3/vzic.c320
-rw-r--r--libkcal/libical/vzic-1.3/vzic.h196
16 files changed, 5651 insertions, 0 deletions
diff --git a/libkcal/libical/vzic-1.3/ChangeLog b/libkcal/libical/vzic-1.3/ChangeLog
new file mode 100644
index 000000000..f2d9569e5
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/ChangeLog
@@ -0,0 +1,57 @@
+2006-03-18 Damon Chaplin <damon@gnome.org>
+
+ * Released Vzic 1.3
+
+2006-03-18 Damon Chaplin <damon@gnome.org>
+
+ * vzic-output.c (expand_tzname): added special case for America/Nome.
+ (output_rrule): made hacks a bit more general, to handle Asia/Gaza
+ which now has a day=4 rule. At some point we should check what newer
+ versions of Outlook can handle so we can be more accurate.
+
+ * vzic-dump.c (dump_time_zone_names): try looking for timezone info
+ using original and linked name.
+
+ * README, *.c: fixed spelling 'compatable' -> 'compatible'.
+
+ * vzic.c: patch from Jonathan Guthrie to support a --olson-dir option.
+
+2003-10-25 Damon Chaplin <damon@gnome.org>
+
+ * Released Vzic 1.2
+
+2003-10-25 Damon Chaplin <damon@gnome.org>
+
+ * vzic-output.c:
+ * Makefile: moved the PRODUCT_ID and TZID_PREFIX settings to the
+ Makefile and changed the default so people don't accidentally use
+ the same IDs as Evolution.
+
+ * vzic-parse.c (parse_time): substitute 23:59:59 when we read a time
+ of 24:00:00. This is a bit of a kludge to avoid problems, since
+ 24:00:00 is not a valid iCalendar time. Since 24:00:00 is only used
+ for a few timezones in the 1930s it doesn't matter too much.
+
+ To write a correct fix we'd need to review all the code that deals
+ with times to see if it would be affected, e.g. a time of 24:00 on
+ one day should be considered equal to 0:00 the next day.
+
+ We'd also need to adjust the output times to use 0:00 the next day
+ rather than 24:00. If we need to output recurrence rules that would
+ be a problem, since 'last saturday at 24:00' can't be easily
+ converted to another rule that uses 0:00 instead.
+
+2003-10-22 Damon Chaplin <damon@gnome.org>
+
+ * Released Vzic 1.1
+
+2003-10-22 Damon Chaplin <damon@gnome.org>
+
+ * vzic-parse.c (parse_time): allow a time of 24:00, as used in
+ the America/Montreal and America/Toronto rules in the 1930s!
+ I'm not 100% sure the rest of the code will handle this OK, but
+ it only affects the 'pure' output.
+
+2003-09-01 Damon Chaplin <damon@gnome.org>
+
+ * Released Vzic 1.0
diff --git a/libkcal/libical/vzic-1.3/Makefile b/libkcal/libical/vzic-1.3/Makefile
new file mode 100644
index 000000000..7196c1529
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/Makefile
@@ -0,0 +1,83 @@
+
+#
+# You will need to set this to the directory that the Olson timezone data
+# files are in.
+#
+OLSON_DIR = ../tzdata
+
+
+# This is used as the PRODID property on the iCalendar files output.
+# It identifies the product which created the iCalendar objects.
+# So you need to substitute your own organization name and product.
+PRODUCT_ID = -//KDE//NONSGML KCal Olson-VTIMEZONE Converter//EN
+
+
+# This is used to create unique IDs for each VTIMEZONE component.
+# The prefix is put before each timezone city name. It should start and end
+# with a '/'. The first part, i.e. 'myorganization.org' below, should be
+# a unique vendor ID, e.g. use a hostname. The part after that can be
+# anything you want. We use a date and version number for libical. The %D
+# gets expanded to today's date. There is also a vzic-merge.pl which can be
+# used to merge changes into a master set of VTIMEZONEs. If a VTIMEZONE has
+# changed, it bumps the version number on the end of this prefix. */
+TZID_PREFIX = /kde.org/Olson_%D_1/
+
+
+# Set any -I include directories to find the libical header files, and the
+# libical library to link with. You only need these if you want to run the
+# tests. You may need to change the '#include <ical.h>' line at the top of
+# test-vzic.c as well.
+LIBICAL_CFLAGS =
+LIBICAL_LDADD = -lical-evolution
+
+
+#
+# You shouldn't need to change the rest of the file.
+#
+
+GLIB_CFLAGS = `pkg-config --cflags glib-2.0`
+GLIB_LDADD = `pkg-config --libs glib-2.0`
+
+CFLAGS = -g -DOLSON_DIR=\"$(OLSON_DIR)\" -DPRODUCT_ID='"$(PRODUCT_ID)"' -DTZID_PREFIX='"$(TZID_PREFIX)"' $(GLIB_CFLAGS) $(LIBICAL_CFLAGS)
+
+OBJECTS = vzic.o vzic-parse.o vzic-dump.o vzic-output.o
+
+all: vzic
+
+vzic: $(OBJECTS)
+ $(CC) $(OBJECTS) $(GLIB_LDADD) -o vzic
+
+test-vzic: test-vzic.o
+ $(CC) test-vzic.o $(LIBICAL_LDADD) -o test-vzic
+
+# Dependencies.
+$(OBJECTS): vzic.h
+vzic.o vzic-parse.o: vzic-parse.h
+vzic.o vzic-dump.o: vzic-dump.h
+vzic.o vzic-output.o: vzic-output.h
+
+test-parse: vzic
+ ./vzic-dump.pl $(OLSON_DIR)
+ ./vzic --dump --pure
+ @echo
+ @echo "#"
+ @echo "# If either of these diff commands outputs anything there may be a problem."
+ @echo "#"
+ diff -ru zoneinfo/ZonesPerl zoneinfo/ZonesVzic
+ diff -ru zoneinfo/RulesPerl zoneinfo/RulesVzic
+
+test-changes: vzic test-vzic
+ ./test-vzic --dump-changes
+ ./vzic --dump-changes --pure
+ @echo
+ @echo "#"
+ @echo "# If this diff command outputs anything there may be a problem."
+ @echo "#"
+ diff -ru zoneinfo/ChangesVzic test-output
+
+clean:
+ -rm -rf vzic $(OBJECTS) *~ ChangesVzic RulesVzic ZonesVzic RulesPerl ZonesPerl test-vzic test-vzic.o
+
+.PHONY: clean perl-dump test-parse
+
+
diff --git a/libkcal/libical/vzic-1.3/Makefile.org b/libkcal/libical/vzic-1.3/Makefile.org
new file mode 100644
index 000000000..4b5917c24
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/Makefile.org
@@ -0,0 +1,89 @@
+
+#
+# You will need to set this to the directory that the Olson timezone data
+# files are in.
+#
+OLSON_DIR = /home/damon/src/olson/tzdata2006b
+
+
+# This is used as the PRODID property on the iCalendar files output.
+# It identifies the product which created the iCalendar objects.
+# So you need to substitute your own organization name and product.
+PRODUCT_ID = -//My Organization//NONSGML My Product//EN
+
+# This is what libical-evolution uses.
+#PRODUCT_ID = -//Ximian//NONSGML Evolution Olson-VTIMEZONE Converter//EN
+
+
+# This is used to create unique IDs for each VTIMEZONE component.
+# The prefix is put before each timezone city name. It should start and end
+# with a '/'. The first part, i.e. 'myorganization.org' below, should be
+# a unique vendor ID, e.g. use a hostname. The part after that can be
+# anything you want. We use a date and version number for libical. The %D
+# gets expanded to today's date. There is also a vzic-merge.pl which can be
+# used to merge changes into a master set of VTIMEZONEs. If a VTIMEZONE has
+# changed, it bumps the version number on the end of this prefix. */
+TZID_PREFIX = /myorganization.org/%D_1/
+
+# This is what libical-evolution uses.
+#TZID_PREFIX = /softwarestudio.org/Olson_%D_1/
+
+
+# Set any -I include directories to find the libical header files, and the
+# libical library to link with. You only need these if you want to run the
+# tests. You may need to change the '#include <ical.h>' line at the top of
+# test-vzic.c as well.
+LIBICAL_CFLAGS =
+LIBICAL_LDADD = -lical-evolution
+
+
+#
+# You shouldn't need to change the rest of the file.
+#
+
+GLIB_CFLAGS = `pkg-config --cflags glib-2.0`
+GLIB_LDADD = `pkg-config --libs glib-2.0`
+
+CFLAGS = -g -DOLSON_DIR=\"$(OLSON_DIR)\" -DPRODUCT_ID='"$(PRODUCT_ID)"' -DTZID_PREFIX='"$(TZID_PREFIX)"' $(GLIB_CFLAGS) $(LIBICAL_CFLAGS)
+
+OBJECTS = vzic.o vzic-parse.o vzic-dump.o vzic-output.o
+
+all: vzic
+
+vzic: $(OBJECTS)
+ $(CC) $(OBJECTS) $(GLIB_LDADD) -o vzic
+
+test-vzic: test-vzic.o
+ $(CC) test-vzic.o $(LIBICAL_LDADD) -o test-vzic
+
+# Dependencies.
+$(OBJECTS): vzic.h
+vzic.o vzic-parse.o: vzic-parse.h
+vzic.o vzic-dump.o: vzic-dump.h
+vzic.o vzic-output.o: vzic-output.h
+
+test-parse: vzic
+ ./vzic-dump.pl $(OLSON_DIR)
+ ./vzic --dump --pure
+ @echo
+ @echo "#"
+ @echo "# If either of these diff commands outputs anything there may be a problem."
+ @echo "#"
+ diff -ru zoneinfo/ZonesPerl zoneinfo/ZonesVzic
+ diff -ru zoneinfo/RulesPerl zoneinfo/RulesVzic
+
+test-changes: vzic test-vzic
+ ./test-vzic --dump-changes
+ ./vzic --dump-changes --pure
+ @echo
+ @echo "#"
+ @echo "# If this diff command outputs anything there may be a problem."
+ @echo "#"
+ diff -ru zoneinfo/ChangesVzic test-output
+
+clean:
+ -rm -rf vzic $(OBJECTS) *~ ChangesVzic RulesVzic ZonesVzic RulesPerl ZonesPerl test-vzic test-vzic.o
+
+.PHONY: clean perl-dump test-parse
+
+
diff --git a/libkcal/libical/vzic-1.3/README b/libkcal/libical/vzic-1.3/README
new file mode 100644
index 000000000..962ff2e67
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/README
@@ -0,0 +1,202 @@
+
+
+VZIC README
+===========
+
+This is 'vzic', a program to convert the Olson timezone database files into
+VTIMEZONE files compatible with the iCalendar specification (RFC2445).
+
+(The name is based on the 'zic' program which converts the Olson files into
+time zone information files used by several Unix C libraries, including
+glibc. See zic(8) and tzfile(5).)
+
+
+
+REQUIREMENTS
+============
+
+You need the Olson timezone database files, which can be found at:
+
+ ftp://elsie.nci.nih.gov/pub/
+
+ (Old versions can be found at ftp://munnari.oz.au/pub/oldtz/)
+
+
+Vzic also uses the GLib library (for hash tables, dynamic arrays, and date
+calculations). You need version 2.0 or higher. You can get this from:
+
+ http://www.gtk.org
+
+
+
+BUILDING
+========
+
+Edit the Makefile to set the OLSON_DIR, PRODUCT_ID and TZID_PREFIX variables.
+
+Then run 'make'.
+
+
+
+RUNNING
+=======
+
+Run 'vzic'.
+
+The output is placed in the zoneinfo subdirectory by default,
+but you can use the --output-dir options to set another toplevel output
+directory.
+
+By default it outputs VTIMEZONEs that try to be compatible with Outlook
+(2000, at least). Outlook can't handle certain iCalendar constructs in
+VTIMEZONEs, such as RRULEs using BYMONTHDAY, so it has to adjust the RRULEs
+slightly to get Outlook to parse them. Unfortunately this means they are
+slightly wrong. If given the --pure option, vzic outputs the exact data,
+without worrying about compatability.
+
+NOTE: We don't convert all the Olson files. We skip 'backward', 'etcetera',
+'leapseconds', 'pacificnew', 'solar87', 'solar88' and 'solar89', 'factory'
+and 'systemv', since these don't really provide any useful timezones.
+See vzic.c.
+
+
+
+MERGING CHANGES INTO A MASTER SET OF VTIMEZONES
+===============================================
+
+The Olson timezone files are updated fairly often, so we need to build new
+sets of VTIMEZONE files. Though we have to be careful to ensure that the TZID
+of updated timezones is also updated, since it must remain unique.
+
+We use a version number on the end of the TZID prefix (see the TZIDPrefix
+variable in vzic-output.c) to ensure this uniqueness.
+
+But we don't want to update the version numbers of VTIMEZONEs which have not
+changed. So we use the vzic-merge.pl Perl script. This merges in the new set
+of VTIMEZONEs with a 'master' set. It compares each new VTIMEZONE file with
+the one in the master set (ignoring changes to the TZID). If the new
+VTIMEZONE file is different, it copies it to the master set and sets the
+version number to the old VTIMEZONE's version number + 1.
+
+To use vzic-merge.pl you must change the $MASTER_ZONEINFO_DIR and
+$NEW_ZONEINFO_DIR variables at the top of the file to point to your 2 sets of
+VTIMEZONEs. You then just run the script. (I recommend you keep a backup of
+the old master VTIMEZONE files, and use diff to compare the new master set
+with the old one, in case anything goes wrong.)
+
+You must merge in changes to the zones.tab file by hand.
+
+Note that some timezones are renamed or removed occasionally, so applications
+should be able to cope with this.
+
+
+
+COMPATABILITY NOTES
+===================
+
+It seems that Microsoft Outlook is very picky about the iCalendar files it
+will accept. (I've been testing with Outlook 2000. I hope the other versions
+are no worse.) Here's a few problems we've had with the VTIMEZONEs:
+
+ o Outlook doesn't like any years before 1600. We were using '1st Jan 0001'
+ in all VTIMEZONEs to specify the first UTC offset known for the timezone.
+ (The Olson data does not give a start date for this.)
+
+ Now we just skip this first component for most timezones. The UTC offset
+ can still be found from the TZOFFSETFROM property of the first component.
+
+ Though some timezones only specify one UTC offset that applies forever,
+ so in these cases we output '1st Jan 1970' (Indian/Cocos,
+ Pacific/Johnston).
+
+ o Outlook doesn't like the BYMONTHDAY specifier in RRULEs.
+
+ We have changed most of the VTIMEZONEs to use things like 'BYDAY=2SU'
+ rather than 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=SU', though some of
+ them were impossible to convert correctly so they are not always correct.
+
+ o Outlook doesn't like TZOFFSETFROM/TZOFFSETTO properties which include a
+ seconds component, e.g. 'TZOFFSETFROM:+110628'.
+ Quite a lot of the Olson timezones include seconds in their UTC offsets,
+ though no timezones currently have a UTC offset that uses the seconds
+ value.
+
+ We've rounded all UTC offsets to the nearest minute. Since all timezone
+ offsets currently used have '00' as the seconds offset, this doesn't lose
+ us much.
+
+ o Outlook doesn't like lines being split in certain places, even though
+ the iCalendar spec says they can be split anywhere.
+
+ o Outlook can only handle one RDATE or a pair of RRULEs. So we had to remove
+ all historical data.
+
+
+TESTING
+=======
+
+Do a 'make test-vic', then run ./test-vic.
+
+The test-vzic program compares our libical code and VTIMEZONE data against
+the Unix functions like mktime(). It steps over a period of time (1970-2037)
+converting from UTC to a given timezone and back again every 15 minutes.
+Any differences are output into the test-output directory.
+
+The output matches for all of the timezones, except in a few places where the
+result can't be determined. So I think we can be fairly confident that the
+VTIMEZONEs are correct.
+
+Note that you must use the same Olson data in libical that the OS is using
+for mktime() etc. For example, I am using RedHat 9 which uses tzdata2002d,
+so I converted this to VTIMEZONE files and installed it into the libical
+timezone data directory before testing. (You need to use '--pure' when
+creating the VTIMEZONE files as well.)
+
+
+Testing the Parsing Code
+------------------------
+
+Run 'make test-parse'.
+
+This runs 'vzic --dump' and 'perl-dump' and compares the output. The diff
+commands should not produce any output.
+
+'vzic --dump' dumps all the parsed data out in the original Olson format,
+but without comments. The files are written into the ZonesVzic and RulesVzic
+subdirectories of the zoneinfo directory.
+
+'make perl-dump' runs the vzic-dump.pl perl script which outputs the files
+in the same format as 'vzic --dump' in the ZonesPerl and RulesPerl
+subdirectories. The perl script doesn't actually parse the fields; it only
+strips comments and massages the fields so we have the same output format.
+
+Currently they both produce exactly the same output so we know the parsing
+code is OK.
+
+
+Testing the VTIMEZONE Files
+---------------------------
+
+Run 'make test-changes'.
+
+This runs 'vzic --dump-changes' and 'test-vzic --dump-changes' and compares
+the output. The diff command should not produce any output.
+
+Both commands output timezone changes for each zone up to a specific year
+(2030) into files for each timezone. It outputs the timezone changes in a
+list in this format:
+
+ Timezone Name Date and Time of Change in UTC New Offset from UTC
+
+ America/Dawson 26 Oct 1986 2:00:00 -0800
+
+Unfortunately there are some differences here, but they all happen before
+1970 so it doesn't matter too much. It looks like the libical code has
+problems determining things like 'last Sunday of the month' before 1970.
+This is because it uses mktime() etc. which can't really handle dates
+before 1970.
+
+
+
+Damon Chaplin <damon@gnome.org>, 25 Oct 2003.
+
diff --git a/libkcal/libical/vzic-1.3/test-vzic.c b/libkcal/libical/vzic-1.3/test-vzic.c
new file mode 100644
index 000000000..9da1abe1b
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/test-vzic.c
@@ -0,0 +1,422 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+/*
+ * test-vzic.c - test vzic + libical against mktime() and friends.
+ *
+ * Note that when we output VCALENDAR data compatible with Outlook the
+ * results aren't all correct.
+ *
+ * We have to modify some RRULEs which makes these timezones incorrect:
+ *
+ * Africa/Cairo
+ * America/Godthab
+ * America/Santiago
+ * Antarctica/Palmer
+ * Asia/Baghdad
+ * Asia/Damascus
+ * Asia/Jerusalem
+ *
+ * Also, we can only output one RDATE or a pair of RRULEs which may make some
+ * other timezones incorrect sometimes (e.g. if they change).
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <ical.h>
+/*#include <evolution/ical.h>*/
+
+#define CHANGES_MAX_YEAR 2030
+
+/* These are the years between which we test against the Unix timezone
+ functions, inclusive. When using 'vzic --pure' you can test the full
+ range from 1970 to 2037 and it should match against mktime() etc.
+ (assuming you are using the same Olson timezone data for both).
+
+ But when using VTIMEZONE's that are compatible with Outlook, it is only
+ worth testing times in the future. There will be lots of differences in
+ the past, since we can't include any historical changes in the files. */
+#if 1
+#define DUMP_START_YEAR 2003
+#define DUMP_END_YEAR 2037
+#else
+#define DUMP_START_YEAR 1970
+#define DUMP_END_YEAR 2037
+#endif
+
+/* The maximum size of any complete pathname. */
+#define PATHNAME_BUFFER_SIZE 1024
+
+#ifndef FALSE
+#define FALSE (0)
+#endif
+
+#ifndef TRUE
+#define TRUE (!FALSE)
+#endif
+
+int VzicDumpChanges = FALSE;
+
+/* We output beneath the current directory for now. */
+char *directory = "test-output";
+
+static void usage (void);
+static int parse_zone_name (char *name,
+ char **directory,
+ char **subdirectory,
+ char **filename);
+static void ensure_directory_exists (char *directory);
+static void dump_local_times (icaltimezone *zone,
+ FILE *fp);
+
+
+int main(int argc, char* argv[])
+{
+ icalarray *zones;
+ icaltimezone *zone;
+ char *zone_directory, *zone_subdirectory, *zone_filename, *location;
+ char output_directory[PATHNAME_BUFFER_SIZE];
+ char filename[PATHNAME_BUFFER_SIZE];
+ FILE *fp;
+ int i;
+ int skipping = TRUE;
+
+ /*
+ * Command-Line Option Parsing.
+ */
+ for (i = 1; i < argc; i++) {
+ /* --dump-changes: Dumps a list of times when each timezone changed,
+ and the new local time offset from UTC. */
+ if (!strcmp (argv[i], "--dump-changes"))
+ VzicDumpChanges = TRUE;
+
+ else
+ usage ();
+ }
+
+
+ zones = icaltimezone_get_builtin_timezones ();
+
+ ensure_directory_exists (directory);
+
+ for (i = 0; i < zones->num_elements; i++) {
+ zone = icalarray_element_at (zones, i);
+
+ location = icaltimezone_get_location (zone);
+
+#if 0
+ /* Use this to start at a certain zone. */
+ if (skipping && strcmp (location, "America/Boise"))
+ continue;
+#endif
+
+ skipping = FALSE;
+
+ /* Use this to only output data for certain timezones. */
+#if 0
+ if (strcmp (location, "America/Cancun")
+ && strcmp (location, "Asia/Baku")
+ && strcmp (location, "Asia/Nicosia")
+ && strcmp (location, "Asia/Novosibirsk")
+ && strcmp (location, "Asia/Samarkand")
+ && strcmp (location, "Asia/Tashkent")
+ && strcmp (location, "Asia/Tbilisi")
+ && strcmp (location, "Asia/Yerevan")
+ && strcmp (location, "Australia/Broken_Hill")
+ && strcmp (location, "Europe/Simferopol")
+ && strcmp (location, "Europe/Tallinn")
+ && strcmp (location, "Europe/Zaporozhye")
+ )
+ continue;
+#endif
+
+#if 0
+ printf ("%s\n", location);
+#endif
+
+ parse_zone_name (location, &zone_directory, &zone_subdirectory,
+ &zone_filename);
+
+ sprintf (output_directory, "%s/%s", directory, zone_directory);
+ ensure_directory_exists (output_directory);
+ sprintf (filename, "%s/%s", output_directory, zone_filename);
+
+ if (zone_subdirectory) {
+ sprintf (output_directory, "%s/%s/%s", directory, zone_directory,
+ zone_subdirectory);
+ ensure_directory_exists (output_directory);
+ sprintf (filename, "%s/%s", output_directory, zone_filename);
+ }
+
+ fp = fopen (filename, "w");
+ if (!fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", filename);
+ exit (1);
+ }
+
+ /* We can run 2 different tests - output all changes for each zone, or
+ test against mktime()/localtime(). Should have a command-line option
+ or something. */
+ if (VzicDumpChanges)
+ icaltimezone_dump_changes (zone, CHANGES_MAX_YEAR, fp);
+ else
+ dump_local_times (zone, fp);
+
+ if (ferror (fp)) {
+ fprintf (stderr, "Error writing file: %s\n", filename);
+ exit (1);
+ }
+
+ fclose (fp);
+ }
+
+ return 0;
+}
+
+
+static void
+usage (void)
+{
+ fprintf (stderr, "Usage: test-vzic [--dump-changes]\n");
+
+ exit (1);
+}
+
+
+/* This checks that the Zone name only uses the characters in [-+_/a-zA-Z0-9],
+ and outputs a warning if it isn't. */
+static int
+parse_zone_name (char *name,
+ char **directory,
+ char **subdirectory,
+ char **filename)
+{
+ static int invalid_zone_num = 1;
+
+ char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL;
+ int invalid = FALSE;
+
+ for (p = name; (ch = *p) != 0; p++) {
+ if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')
+ && (ch < '0' || ch > '9') && ch != '/' && ch != '_'
+ && ch != '-' && ch != '+') {
+ fprintf (stderr, "Warning: Unusual Zone name: %s\n", name);
+ invalid = TRUE;
+ break;
+ }
+
+ if (ch == '/') {
+ if (!first_slash_pos) {
+ first_slash_pos = p;
+ } else if (!second_slash_pos) {
+ second_slash_pos = p;
+ } else {
+ fprintf (stderr, "Warning: More than 2 '/' characters in Zone name: %s\n", name);
+ invalid = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!first_slash_pos) {
+ fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name);
+ return FALSE;
+ }
+
+ if (invalid) {
+ fprintf (stderr, "Invalid zone name: %s\n", name);
+ exit (0);
+ } else {
+ *first_slash_pos = '\0';
+ *directory = icalmemory_strdup (name);
+ *first_slash_pos = '/';
+
+ if (second_slash_pos) {
+ *second_slash_pos = '\0';
+ *subdirectory = icalmemory_strdup (first_slash_pos + 1);
+ *second_slash_pos = '/';
+
+ *filename = icalmemory_strdup (second_slash_pos + 1);
+ } else {
+ *subdirectory = NULL;
+ *filename = icalmemory_strdup (first_slash_pos + 1);
+ }
+ }
+}
+
+
+static void
+ensure_directory_exists (char *directory)
+{
+ struct stat filestat;
+
+ if (stat (directory, &filestat) != 0) {
+ /* If the directory doesn't exist, try to create it. */
+ if (errno == ENOENT) {
+ if (mkdir (directory, 0777) != 0) {
+ fprintf (stderr, "Can't create directory: %s\n", directory);
+ exit (1);
+ }
+ } else {
+ fprintf (stderr, "Error calling stat() on directory: %s\n", directory);
+ exit (1);
+ }
+ } else if (!S_ISDIR (filestat.st_mode)) {
+ fprintf (stderr, "Can't create directory, already exists: %s\n",
+ directory);
+ exit (1);
+ }
+}
+
+
+static void
+dump_local_times (icaltimezone *zone, FILE *fp)
+{
+ static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ icaltimezone *utc_timezone;
+ struct icaltimetype tt, tt_copy;
+ struct tm tm, local_tm;
+ time_t t;
+ char tzstring[256], *location;
+ int last_year_output = 0;
+ int total_error = 0, total_error2 = 0;
+
+ utc_timezone = icaltimezone_get_utc_timezone ();
+
+ /* This is our UTC time that we will use to iterate over the period. */
+ tt.year = DUMP_START_YEAR;
+ tt.month = 1;
+ tt.day = 1;
+ tt.hour = 0;
+ tt.minute = 0;
+ tt.second = 0;
+ tt.is_utc = 0;
+ tt.is_date = 0;
+ tt.zone = "";
+
+ tm.tm_year = tt.year - 1900;
+ tm.tm_mon = tt.month - 1;
+ tm.tm_mday = tt.day;
+ tm.tm_hour = tt.hour;
+ tm.tm_min = tt.minute;
+ tm.tm_sec = tt.second;
+ tm.tm_isdst = -1;
+
+ /* Convert it to a time_t by saying it is in UTC. */
+ putenv ("TZ=UTC");
+ t = mktime (&tm);
+
+ location = icaltimezone_get_location (zone);
+ sprintf (tzstring, "TZ=%s", location);
+
+ /*printf ("Zone: %s\n", location);*/
+ putenv (tzstring);
+
+ /* Loop around converting the UTC time to local time, outputting it, and
+ then adding on 15 minutes to the UTC time. */
+ while (tt.year <= DUMP_END_YEAR) {
+ if (tt.year > last_year_output) {
+ last_year_output = tt.year;
+#if 0
+ printf (" %i\n", last_year_output);
+ fprintf (fp, " %i\n", last_year_output);
+#endif
+ }
+
+#if 1
+ /* First use the Unix functions. */
+ /* Now convert it to a local time in the given timezone. */
+ local_tm = *localtime (&t);
+#endif
+
+#if 1
+ /* Now use libical. */
+ tt_copy = tt;
+ icaltimezone_convert_time (&tt_copy, utc_timezone, zone);
+#endif
+
+#if 1
+ if (local_tm.tm_year + 1900 != tt_copy.year
+ || local_tm.tm_mon + 1 != tt_copy.month
+ || local_tm.tm_mday != tt_copy.day
+ || local_tm.tm_hour != tt_copy.hour
+ || local_tm.tm_min != tt_copy.minute
+ || local_tm.tm_sec != tt_copy.second) {
+
+ /* The error format is:
+
+ ERROR: Original-UTC-Time Local-Time-From-mktime Local-Time-From-Libical
+
+ */
+
+ total_error++;
+
+ fprintf (fp, "ERROR:%2i %s %04i %2i:%02i:%02i UTC",
+ tt.day, months[tt.month - 1], tt.year,
+ tt.hour, tt.minute, tt.second);
+ fprintf (fp, " ->%2i %s %04i %2i:%02i:%02i",
+ local_tm.tm_mday, months[local_tm.tm_mon],
+ local_tm.tm_year + 1900,
+ local_tm.tm_hour, local_tm.tm_min, local_tm.tm_sec);
+ fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i\n",
+ tt_copy.day, months[tt_copy.month - 1], tt_copy.year,
+ tt_copy.hour, tt_copy.minute, tt_copy.second);
+ }
+#endif
+
+ /* Now convert it back, and check we get the original time. */
+ icaltimezone_convert_time (&tt_copy, zone, utc_timezone);
+ if (tt.year != tt_copy.year
+ || tt.month != tt_copy.month
+ || tt.day != tt_copy.day
+ || tt.hour != tt_copy.hour
+ || tt.minute != tt_copy.minute
+ || tt.second != tt_copy.second) {
+
+ total_error2++;
+
+ fprintf (fp, "ERROR 2: %2i %s %04i %2i:%02i:%02i UTC",
+ tt.day, months[tt.month - 1], tt.year,
+ tt.hour, tt.minute, tt.second);
+ fprintf (fp, " Us:%2i %s %04i %2i:%02i:%02i UTC\n",
+ tt_copy.day, months[tt_copy.month - 1], tt_copy.year,
+ tt_copy.hour, tt_copy.minute, tt_copy.second);
+ }
+
+
+ /* Increment the time. */
+ icaltime_adjust (&tt, 0, 0, 15, 0);
+
+ /* We assume leap seconds are not included in time_t values, which should
+ be true on POSIX systems. */
+ t += 15 * 60;
+ }
+
+ printf ("Zone: %40s Errors: %i (%i)\n", icaltimezone_get_location (zone),
+ total_error, total_error2);
+}
diff --git a/libkcal/libical/vzic-1.3/vzic-dump.c b/libkcal/libical/vzic-1.3/vzic-dump.c
new file mode 100644
index 000000000..405d7eef6
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-dump.c
@@ -0,0 +1,409 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+/*
+ * These functions are for dumping all the parsed Zones and Rules to
+ * files, to be compared with the output of vzic-dump.pl to check our parsing
+ * code is OK. Some of the functions are also used for producing debugging
+ * output.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "vzic.h"
+#include "vzic-dump.h"
+
+
+static void dump_add_rule (char *name,
+ GArray *rule_array,
+ GPtrArray *name_array);
+static int dump_compare_strings (const void *arg1,
+ const void *arg2);
+
+
+void
+dump_zone_data (GArray *zone_data,
+ char *filename)
+{
+ static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ FILE *fp;
+ ZoneData *zone;
+ ZoneLineData *zone_line;
+ int i, j;
+ gboolean output_month, output_day, output_time;
+
+ fp = fopen (filename, "w");
+ if (!fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", filename);
+ exit (1);
+ }
+
+ for (i = 0; i < zone_data->len; i++) {
+ zone = &g_array_index (zone_data, ZoneData, i);
+
+ fprintf (fp, "Zone\t%s\t", zone->zone_name);
+
+ for (j = 0; j < zone->zone_line_data->len; j++) {
+ zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j);
+
+ if (j != 0)
+ fprintf (fp, "\t\t\t");
+
+ fprintf (fp, "%s\t", dump_time (zone_line->stdoff_seconds, TIME_WALL,
+ FALSE));
+
+ if (zone_line->rules)
+ fprintf (fp, "%s\t", zone_line->rules);
+ else if (zone_line->save_seconds != 0)
+ fprintf (fp, "%s\t", dump_time (zone_line->save_seconds, TIME_WALL,
+ FALSE));
+ else
+ fprintf (fp, "-\t");
+
+ fprintf (fp, "%s\t", zone_line->format ? zone_line->format : "-");
+
+ if (zone_line->until_set) {
+ fprintf (fp, "%s\t", dump_year (zone_line->until_year));
+
+ output_month = output_day = output_time = FALSE;
+
+ if (zone_line->until_time_code != TIME_WALL
+ || zone_line->until_time_seconds != 0)
+ output_month = output_day = output_time = TRUE;
+ else if (zone_line->until_day_code != DAY_SIMPLE
+ || zone_line->until_day_number != 1)
+ output_month = output_day = TRUE;
+ else if (zone_line->until_month != 0)
+ output_month = TRUE;
+
+ if (output_month)
+ fprintf (fp, "%s", months[zone_line->until_month]);
+
+ fprintf (fp, "\t");
+
+ if (output_day)
+ fprintf (fp, "%s", dump_day_coded (zone_line->until_day_code,
+ zone_line->until_day_number,
+ zone_line->until_day_weekday));
+
+ fprintf (fp, "\t");
+
+ if (output_time)
+ fprintf (fp, "%s", dump_time (zone_line->until_time_seconds,
+ zone_line->until_time_code, FALSE));
+
+ } else {
+ fprintf (fp, "\t\t\t");
+ }
+
+ fprintf (fp, "\n");
+ }
+ }
+
+ fclose (fp);
+}
+
+
+void
+dump_rule_data (GHashTable *rule_data,
+ char *filename)
+{
+ FILE *fp;
+ GPtrArray *name_array;
+ GArray *rule_array;
+ int i;
+ char *name;
+
+ fp = fopen (filename, "w");
+ if (!fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", filename);
+ exit (1);
+ }
+
+ /* We need to sort the rules by their names, so they are in the same order
+ as the Perl output. So we place all the names in a temporary GPtrArray,
+ sort it, then output them. */
+ name_array = g_ptr_array_new ();
+ g_hash_table_foreach (rule_data, (GHFunc) dump_add_rule, name_array);
+ qsort (name_array->pdata, name_array->len, sizeof (char*),
+ dump_compare_strings);
+
+ for (i = 0; i < name_array->len; i++) {
+ name = g_ptr_array_index (name_array, i);
+ rule_array = g_hash_table_lookup (rule_data, name);
+ if (!rule_array) {
+ fprintf (stderr, "Couldn't access rules: %s\n", name);
+ exit (1);
+ }
+ dump_rule_array (name, rule_array, fp);
+ }
+
+ g_ptr_array_free (name_array, TRUE);
+
+ fclose (fp);
+}
+
+
+static void
+dump_add_rule (char *name,
+ GArray *rule_array,
+ GPtrArray *name_array)
+{
+ g_ptr_array_add (name_array, name);
+}
+
+
+static int
+dump_compare_strings (const void *arg1,
+ const void *arg2)
+{
+ char **a, **b;
+
+ a = (char**) arg1;
+ b = (char**) arg2;
+
+ return strcmp (*a, *b);
+}
+
+
+void
+dump_rule_array (char *name,
+ GArray *rule_array,
+ FILE *fp)
+{
+ static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ RuleData *rule;
+ int i;
+
+#if 0
+ fprintf (fp, "\n# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S");
+#endif
+
+ for (i = 0; i < rule_array->len; i++) {
+ rule = &g_array_index (rule_array, RuleData, i);
+
+ fprintf (fp, "Rule\t%s\t%s\t", name, dump_year (rule->from_year));
+
+ if (rule->to_year == rule->from_year)
+ fprintf (fp, "only\t");
+ else
+ fprintf (fp, "%s\t", dump_year (rule->to_year));
+
+ fprintf (fp, "%s\t", rule->type ? rule->type : "-");
+
+ fprintf (fp, "%s\t", months[rule->in_month]);
+
+ fprintf (fp, "%s\t",
+ dump_day_coded (rule->on_day_code, rule->on_day_number,
+ rule->on_day_weekday));
+
+ fprintf (fp, "%s\t", dump_time (rule->at_time_seconds, rule->at_time_code,
+ FALSE));
+
+ fprintf (fp, "%s\t", dump_time (rule->save_seconds, TIME_WALL, TRUE));
+
+ fprintf (fp, "%s", rule->letter_s ? rule->letter_s : "-");
+
+ fprintf (fp, "\n");
+ }
+}
+
+
+char*
+dump_time (int seconds,
+ TimeCode time_code,
+ gboolean use_zero)
+{
+ static char buffer[256], *sign;
+ int hours, minutes;
+ char *code;
+
+ if (time_code == TIME_STANDARD)
+ code = "s";
+ else if (time_code == TIME_UNIVERSAL)
+ code = "u";
+ else
+ code = "";
+
+ if (seconds < 0) {
+ seconds = -seconds;
+ sign = "-";
+ } else {
+ sign = "";
+ }
+
+ hours = seconds / 3600;
+ minutes = (seconds % 3600) / 60;
+ seconds = seconds % 60;
+
+ if (use_zero && hours == 0 && minutes == 0 && seconds == 0)
+ return "0";
+ else if (seconds == 0)
+ sprintf (buffer, "%s%i:%02i%s", sign, hours, minutes, code);
+ else
+ sprintf (buffer, "%s%i:%02i:%02i%s", sign, hours, minutes, seconds, code);
+
+ return buffer;
+}
+
+
+char*
+dump_day_coded (DayCode day_code,
+ int day_number,
+ int day_weekday)
+{
+ static char buffer[256];
+ static char *weekdays[] = { "Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat" };
+
+ switch (day_code) {
+ case DAY_SIMPLE:
+ sprintf (buffer, "%i", day_number);
+ break;
+ case DAY_WEEKDAY_ON_OR_AFTER:
+ sprintf (buffer, "%s>=%i", weekdays[day_weekday], day_number);
+ break;
+ case DAY_WEEKDAY_ON_OR_BEFORE:
+ sprintf (buffer, "%s<=%i", weekdays[day_weekday], day_number);
+ break;
+ case DAY_LAST_WEEKDAY:
+ sprintf (buffer, "last%s", weekdays[day_weekday]);
+ break;
+ default:
+ fprintf (stderr, "Invalid day code: %i\n", day_code);
+ exit (1);
+ }
+
+ return buffer;
+}
+
+
+char*
+dump_year (int year)
+{
+ static char buffer[256];
+
+ if (year == YEAR_MINIMUM)
+ return "min";
+ if (year == YEAR_MAXIMUM)
+ return "max";
+
+ sprintf (buffer, "%i", year);
+ return buffer;
+}
+
+
+void
+dump_time_zone_names (GList *names,
+ char *output_dir,
+ GHashTable *zones_hash)
+{
+ char filename[PATHNAME_BUFFER_SIZE], *zone_name, *zone_name_in_hash = NULL;
+ char strings_filename[PATHNAME_BUFFER_SIZE];
+ FILE *fp, *strings_fp = NULL;
+ GList *elem;
+ ZoneDescription *zone_desc;
+
+ sprintf (filename, "%s/zones.tab", output_dir);
+ sprintf (strings_filename, "%s/zones.h", output_dir);
+
+ fp = fopen (filename, "w");
+ if (!fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", filename);
+ exit (1);
+ }
+
+ if (VzicDumpZoneTranslatableStrings) {
+ strings_fp = fopen (strings_filename, "w");
+ if (!strings_fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", strings_filename);
+ exit (1);
+ }
+ }
+
+ names = g_list_sort (names, (GCompareFunc) strcmp);
+
+ elem = names;
+ while (elem) {
+ zone_name = (char*) elem->data;
+
+ zone_desc = g_hash_table_lookup (zones_hash, zone_name);
+
+ /* SPECIAL CASES: These timezones are links from other zones and are
+ almost exactly the same - they are basically there so users can find
+ them a bit easier. But they don't have entries in the zone.tab file,
+ so we use the entry from the timezone linked from. */
+ if (!zone_desc) {
+ if (!strcmp (zone_name, "America/Indiana/Indianapolis"))
+ zone_name_in_hash = "America/Indianapolis";
+ else if (!strcmp (zone_name, "America/Kentucky/Louisville"))
+ zone_name_in_hash = "America/Louisville";
+ else if (!strcmp (zone_name, "Asia/Istanbul"))
+ zone_name_in_hash = "Europe/Istanbul";
+ else if (!strcmp (zone_name, "Europe/Nicosia"))
+ zone_name_in_hash = "Asia/Nicosia";
+
+ if (zone_name_in_hash)
+ zone_desc = g_hash_table_lookup (zones_hash, zone_name_in_hash);
+ }
+
+ if (zone_desc) {
+ fprintf (fp, "%+04i%02i%02i %+04i%02i%02i %s\n",
+ zone_desc->latitude[0], zone_desc->latitude[1],
+ zone_desc->latitude[2],
+ zone_desc->longitude[0], zone_desc->longitude[1],
+ zone_desc->longitude[2],
+ zone_name);
+ } else {
+ g_print ("Zone description not found for: %s\n", zone_name);
+ fprintf (fp, "%s\n", zone_name);
+ }
+
+
+ if (VzicDumpZoneTranslatableStrings) {
+#if 0
+ char zone_name_buffer[1024], *src, *dest;
+
+ for (src = zone_name, dest = zone_name_buffer; *src; src++, dest++)
+ *dest = (*src == '_') ? ' ' : *src;
+ *dest = '\0';
+#endif
+
+ fprintf (strings_fp, "N_(\"%s\");\n", zone_name);
+ }
+
+ elem = elem->next;
+ }
+
+ fclose (fp);
+
+ if (VzicDumpZoneTranslatableStrings)
+ fclose (strings_fp);
+}
+
diff --git a/libkcal/libical/vzic-1.3/vzic-dump.h b/libkcal/libical/vzic-1.3/vzic-dump.h
new file mode 100644
index 000000000..ab9fe001f
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-dump.h
@@ -0,0 +1,58 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+/*
+ * These functions are for dumping all the parsed Zones and Rules to
+ * files, to be compared with the output of vzic-dump.pl to check our parsing
+ * code is OK. Some of the functions are also used for producing debugging
+ * output.
+ */
+
+#ifndef _VZIC_DUMP_H_
+#define _VZIC_DUMP_H_
+
+#include <glib.h>
+
+void dump_zone_data (GArray *zone_data,
+ char *filename);
+void dump_rule_data (GHashTable *rule_data,
+ char *filename);
+
+void dump_rule_array (char *name,
+ GArray *rule_array,
+ FILE *fp);
+
+char* dump_year (int year);
+char* dump_day_coded (DayCode day_code,
+ int day_number,
+ int day_weekday);
+char* dump_time (int seconds,
+ TimeCode time_code,
+ gboolean use_zero);
+
+void dump_time_zone_names (GList *names,
+ char *output_dir,
+ GHashTable *zones_hash);
+
+#endif /* _VZIC_DUMP_H_ */
diff --git a/libkcal/libical/vzic-1.3/vzic-dump.pl b/libkcal/libical/vzic-1.3/vzic-dump.pl
new file mode 100755
index 000000000..dc789bc81
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-dump.pl
@@ -0,0 +1,222 @@
+#!/usr/bin/perl -w
+
+#
+# Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+# files compatible with the iCalendar specification (RFC2445).
+#
+# Copyright (C) 2000-2001 Ximian, Inc.
+# Copyright (C) 2003 Damon Chaplin.
+#
+# Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+#
+
+#
+# This reads the Olson timezone files, strips any comments, and outputs them
+# in a very simple format with tab-separated fields. It is used to compare
+# with the output of dump_zone_data() and dump_rule_data() to double-check
+# that we have parsed the files correctly.
+#
+
+my $zones_fh = "zonesfile";
+my $rules_fh = "rulesfile";
+
+my %Rules;
+
+if ($#ARGV != 0) {
+ die "Usage: $0 <OlsonDirectory>";
+}
+
+my $OLSON_DIR = $ARGV[0];
+
+# We place output in subdirectories of the current directory.
+my $OUTPUT_DIR = "zoneinfo";
+
+if (! -d "$OUTPUT_DIR") {
+ mkdir ("$OUTPUT_DIR", 0777)
+ || die "Can't create directory: $OUTPUT_DIR";
+}
+if (! -d "$OUTPUT_DIR/ZonesPerl") {
+ mkdir ("$OUTPUT_DIR/ZonesPerl", 0777)
+ || die "Can't create directory: $OUTPUT_DIR/ZonesPerl";
+}
+if (! -d "$OUTPUT_DIR/RulesPerl") {
+ mkdir ("$OUTPUT_DIR/RulesPerl", 0777)
+ || die "Can't create directory: $OUTPUT_DIR/RulesPerl";
+}
+
+
+&ReadOlsonFile ("africa");
+&ReadOlsonFile ("antarctica");
+&ReadOlsonFile ("asia");
+&ReadOlsonFile ("australasia");
+&ReadOlsonFile ("europe");
+&ReadOlsonFile ("northamerica");
+&ReadOlsonFile ("southamerica");
+
+# These are backwards-compatability and weird stuff.
+#&ReadOlsonFile ("backward");
+#&ReadOlsonFile ("etcetera");
+#&ReadOlsonFile ("leapseconds");
+#&ReadOlsonFile ("pacificnew");
+#&ReadOlsonFile ("solar87");
+#&ReadOlsonFile ("solar88");
+#&ReadOlsonFile ("solar89");
+
+# We don't do this one since it is not useful and the use of '"' in the Zone
+# line messes up our split() command.
+#&ReadOlsonFile ("factory");
+
+# We don't do this since the vzic program can't do it.
+#&ReadOlsonFile ("systemv");
+
+
+
+
+1;
+
+
+sub ReadOlsonFile {
+ my ($file) = @_;
+
+# print ("Reading olson file: $file\n");
+
+ open (OLSONFILE, "$OLSON_DIR/$file")
+ || die "Can't open file: $file";
+
+ open ($zones_fh, ">$OUTPUT_DIR/ZonesPerl/$file")
+ || die "Can't open file: $OUTPUT_DIR/ZonesPerl/$file";
+
+ open ($rules_fh, ">$OUTPUT_DIR/RulesPerl/$file")
+ || die "Can't open file: $OUTPUT_DIR/RulesPerl/$file";
+
+ %Rules = ();
+
+ my $zone_continues = 0;
+
+ while (<OLSONFILE>) {
+ next if (m/^#/);
+
+ # '#' characters can appear in strings, but the Olson files don't use
+ # that feature at present so we treat all '#' as comments for now.
+ s/#.*//;
+
+ next if (m/^\s*$/);
+
+ if ($zone_continues) {
+ $zone_continues = &ReadZoneContinuationLine;
+
+ } elsif (m/^Rule\s/) {
+ &ReadRuleLine;
+
+ } elsif (m/^Zone\s/) {
+ $zone_continues = &ReadZoneLine;
+
+ } elsif (m/^Link\s/) {
+# print "Link: $link_from, $link_to\n";
+
+ } elsif (m/^Leap\s/) {
+# print "Leap\n";
+
+ } else {
+ die "Invalid line: $_";
+ }
+ }
+
+# print ("Read olson file: $file\n");
+
+ foreach $key (sort (keys (%Rules))) {
+ print $rules_fh "$Rules{$key}"
+ }
+
+ close ($zones_fh);
+ close ($rules_fh);
+ close (OLSONFILE);
+}
+
+
+sub ReadZoneLine {
+ my ($zone, $name, $gmtoff, $rules_save, $format,
+ $until_year, $until_month, $until_day, $until_time, $remainder)
+ = split ' ', $_, 10;
+
+ return &ReadZoneLineCommon ($zone, $name, $gmtoff, $rules_save, $format,
+ $until_year, $until_month, $until_day,
+ $until_time);
+}
+
+
+sub ReadZoneContinuationLine {
+ my ($gmtoff, $rules_save, $format,
+ $until_year, $until_month, $until_day, $until_time, $remainder)
+ = split ' ', $_, 8;
+
+ return &ReadZoneLineCommon ("", "", $gmtoff, $rules_save, $format,
+ $until_year, $until_month, $until_day,
+ $until_time);
+}
+
+
+sub ReadZoneLineCommon {
+ my ($zone, $name, $gmtoff, $rules_save, $format,
+ $until_year, $until_month, $until_day, $until_time) = @_;
+
+ if (!defined ($until_year)) { $until_year = ""; }
+ if (!defined ($until_month)) { $until_month = ""; }
+ if (!defined ($until_day)) { $until_day = ""; }
+ if (!defined ($until_time)) { $until_time = ""; }
+
+ # A few of the gmtoffsets have an unnecessary :00 seconds.
+ $gmtoff =~ s/(\d+):(\d+):00/$1:$2/;
+
+ # Make sure the gmtoff does have minutes.
+ $gmtoff =~ s/^(-?\d+)$/$1:00/;
+
+ # Fix a few other bits so they all use the same format.
+ if ($gmtoff eq "0") { $gmtoff = "0:00"; }
+ $until_time =~ s/^0(\d):/$1:/;
+ if ($until_time eq "0:00") { $until_time = ""; }
+ if ($until_day eq "1" && $until_time eq "") { $until_day = ""; }
+ if ($until_month eq "Jan" && $until_day eq "" && $until_time eq "") {
+ $until_month = "";
+ }
+
+ # For Zone continuation lines we need to insert an extra TAB.
+ if (!$zone) { $zone = "\t" };
+
+ print $zones_fh "$zone\t$name\t$gmtoff\t$rules_save\t$format\t$until_year\t$until_month\t$until_day\t$until_time\n";
+
+ if (defined ($until_year) && $until_year) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+
+sub ReadRuleLine {
+ my ($rule, $name, $from, $to, $type, $in, $on, $at, $save, $letter_s,
+ $remainder) = split;
+
+ $at =~ s/(\d+:\d+):00/$1/;
+ $save =~ s/(\d+:\d+):00/$1/;
+ if ($save eq "0:00") { $save = "0"; }
+
+ $Rules{$name} .= "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n";
+
+# print $rules_fh "$rule\t$name\t$from\t$to\t$type\t$in\t$on\t$at\t$save\t$letter_s\n";
+}
+
diff --git a/libkcal/libical/vzic-1.3/vzic-merge.pl b/libkcal/libical/vzic-1.3/vzic-merge.pl
new file mode 100755
index 000000000..d1e545ee0
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-merge.pl
@@ -0,0 +1,127 @@
+#!/usr/bin/perl -w
+
+#
+# Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+# files compatible with the iCalendar specification (RFC2445).
+#
+# Copyright (C) 2001 Ximian, Inc.
+# Copyright (C) 2003 Damon Chaplin.
+#
+# Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+#
+
+#
+# This merges in a new set of VTIMEZONE files with the 'master' set. It only
+# updates the files in the master set if the VTIMEZONE component has really
+# been changes. Note that the TZID normally includes the date the VTIMEZONE
+# file was generated on, so we have to ignore this when comparing the files.
+#
+
+# Set these to the toplevel directories of the 2 sets of VTIMEZONE files.
+#$MASTER_ZONEINFO_DIR = "/home/damon/cvs/libical/zoneinfo";
+$MASTER_ZONEINFO_DIR = "/usr/share/libical-evolution/zoneinfo";
+$NEW_ZONEINFO_DIR = "/home/damon/src/vzic-1.0/zoneinfo";
+
+# Set this to 1 if you have version numbers in the TZID like libical.
+$LIBICAL_VERSIONING = 1;
+
+# Set this to 0 for dry-runs, and 1 to actually update.
+$DO_UPDATES = 1;
+
+# Save this so we can restore it later.
+$input_record_separator = $/;
+
+chdir $NEW_ZONEINFO_DIR
+ || die "Can't cd to $NEW_ZONEINFO_DIR";
+
+foreach $new_file (`find -name "*.ics"`) {
+ # Get rid of './' at start and whitespace at end.
+ $new_file =~ s/^\.\///;
+ $new_file =~ s/\s+$//;
+
+# print "File: $new_file\n";
+
+ open (NEWZONEFILE, "$new_file")
+ || die "Can't open file: $NEW_ZONEINFO_DIR/$new_file";
+ undef $/;
+ $new_contents = <NEWZONEFILE>;
+ $/ = $input_record_separator;
+ close (NEWZONEFILE);
+
+ $master_file = $MASTER_ZONEINFO_DIR . "/$new_file";
+
+# print "Master File: $master_file\n";
+
+ $copy_to_master = 0;
+
+ # If the ics file exists in the master copy we have to compare them,
+ # otherwise we can just copy the new file into the master directory.
+ if (-e $master_file) {
+ open (MASTERZONEFILE, "$master_file")
+ || die "Can't open file: $master_file";
+ undef $/;
+ $master_contents = <MASTERZONEFILE>;
+ $/ = $input_record_separator;
+ close (MASTERZONEFILE);
+
+ $new_contents_copy = $new_contents;
+
+ # Strip the TZID from both contents.
+ $new_contents_copy =~ s/^TZID:\S+$//m;
+ $new_tzid = $&;
+ $master_contents =~ s/^TZID:\S+$//m;
+ $master_tzid = $&;
+
+# print "Matched: $master_tzid\n";
+
+
+ if ($new_contents_copy ne $master_contents) {
+ print "$new_file has changed. Updating...\n";
+ $copy_to_master = 1;
+
+ if ($LIBICAL_VERSIONING) {
+ # We bump the version number in the new file.
+ $master_tzid =~ m%_(\d+)/%;
+ $version_num = $1;
+# print "Version: $version_num\n";
+
+ $version_num++;
+ $new_tzid =~ s%_(\d+)/%_$version_num/%;
+
+# print "New TZID: $new_tzid\n";
+ $new_contents =~ s/^TZID:\S+$/$new_tzid/m;
+ }
+ }
+
+ } else {
+ print "$new_file doesn't exist in master directory. Copying...\n";
+ $copy_to_master = 1;
+ }
+
+ if ($copy_to_master) {
+# print "Updating: $new_file\n";
+
+ if ($DO_UPDATES) {
+ open (MASTERZONEFILE, ">$master_file")
+ || die "Can't create file: $master_file";
+ print MASTERZONEFILE $new_contents;
+ close (MASTERZONEFILE);
+ }
+ }
+
+}
+
diff --git a/libkcal/libical/vzic-1.3/vzic-output.c b/libkcal/libical/vzic-1.3/vzic-output.c
new file mode 100644
index 000000000..a23ece13a
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-output.c
@@ -0,0 +1,2325 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+/* ALGORITHM:
+ *
+ * First we expand all the Rule arrays, so that each element only represents 1
+ * year. If a Rule extends to infinity we expand it up to a few years past the
+ * maximum UNTIL year used in any of the timezones. We do this to make sure
+ * that the last of the expanded Rules (which may be infinite) is only used
+ * in the last of the time periods (i.e. the last Zone line).
+ *
+ * The Rule arrays are also sorted by the start time (FROM + IN + ON + AT).
+ * Doing all this makes it much easier to find which rules apply to which
+ * periods.
+ *
+ * For each timezone (i.e. ZoneData element), we step through each of the
+ * time periods, the ZoneLineData elements (which represent each Zone line
+ * from the Olson file.)
+ *
+ * We calculate the start & end time of the period.
+ * - For the first line the start time is -infinity.
+ * - For the last line the end time is +infinity.
+ * - The end time of each line is also the start time of the next.
+ *
+ * We create an array of time changes which occur in this period, including
+ * the one implied by the Zone line itself (though this is later taken out
+ * if it is found to be at exactly the same time as the first Rule).
+ *
+ * Now we iterate over the time changes, outputting them as STANDARD or
+ * DAYLIGHT components. We also try to merge them together into RRULEs or
+ * use RDATEs.
+ */
+
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "vzic.h"
+#include "vzic-output.h"
+
+#include "vzic-dump.h"
+
+
+/* These come from the Makefile. See the comments there. */
+char *ProductID = PRODUCT_ID;
+char *TZIDPrefix = TZID_PREFIX;
+
+/* We expand the TZIDPrefix, replacing %D with the date, in here. */
+char TZIDPrefixExpanded[1024];
+
+
+/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences,
+ since otherwise RDATEs are more efficient. Actually, I've set this high
+ so we only use RRULEs for infinite recurrences. Since expanding RRULEs is
+ very time-consuming, this seems sensible. */
+#define MIN_RRULE_OCCURRENCES 100
+
+
+/* The year we go up to when dumping the list of timezone changes (used
+ for testing & debugging). */
+#define MAX_CHANGES_YEAR 2030
+
+/* This is the maximum year that time_t value can typically hold on 32-bit
+ systems. */
+#define MAX_TIME_T_YEAR 2037
+
+
+/* The year we use to start RRULEs. */
+#define RRULE_START_YEAR 1970
+
+/* The year we use for RDATEs. */
+#define RDATE_YEAR 1970
+
+
+static char *WeekDays[] = { "SU", "MO", "TU", "WE", "TH", "FR", "SA" };
+static int DaysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+char *CurrentZoneName;
+
+
+typedef struct _VzicTime VzicTime;
+struct _VzicTime
+{
+ /* Normal years, e.g. 2001. */
+ int year;
+
+ /* 0 (Jan) to 11 (Dec). */
+ int month;
+
+ /* The day, either a simple month day number, 1-31, or a rule such as
+ the last Sunday, or the first Monday on or after the 8th. */
+ DayCode day_code;
+ int day_number; /* 1 to 31. */
+ int day_weekday; /* 0 (Sun) to 6 (Sat). */
+
+ /* The time, in seconds from midnight. The code specifies whether the
+ time is a wall clock time, local standard time, or universal time. */
+ int time_seconds;
+ TimeCode time_code;
+
+ /* The offset from UTC for local standard time. */
+ int stdoff;
+
+ /* The offset from UTC for local wall clock time. If this is different to
+ stdoff then this is a DAYLIGHT component. This is TZOFFSETTO. */
+ int walloff;
+
+ /* TRUE if the time change recurs every year to infinity. */
+ gboolean is_infinite;
+
+ /* TRUE if the change has already been output. */
+ gboolean output;
+
+ /* These are the offsets of the previous VzicTime, and are used when
+ calculating the time of the change. We place them here in
+ output_zone_components() to simplify the output code. */
+ int prev_stdoff;
+ int prev_walloff;
+
+ /* The abbreviated form of the timezone name. Note that this may not be
+ unique. */
+ char *tzname;
+};
+
+
+static void expand_and_sort_rule_array (gpointer key,
+ gpointer value,
+ gpointer data);
+static int rule_sort_func (const void *arg1,
+ const void *arg2);
+static void output_zone (char *directory,
+ ZoneData *zone,
+ char *zone_name,
+ GHashTable *rule_data);
+static gboolean parse_zone_name (char *name,
+ char **directory,
+ char **subdirectory,
+ char **filename);
+static void output_zone_to_files (ZoneData *zone,
+ char *zone_name,
+ GHashTable *rule_data,
+ FILE *fp,
+ FILE *changes_fp);
+static gboolean add_rule_changes (ZoneLineData *zone_line,
+ char *zone_name,
+ GArray *changes,
+ GHashTable *rule_data,
+ VzicTime *start,
+ VzicTime *end,
+ char **start_letter_s,
+ int *save_seconds);
+static char* expand_tzname (char *zone_name,
+ char *format,
+ gboolean have_letter_s,
+ char *letter_s,
+ gboolean is_daylight);
+static int compare_times (VzicTime *time1,
+ int stdoff1,
+ int walloff1,
+ VzicTime *time2,
+ int stdoff2,
+ int walloff2);
+static gboolean times_match (VzicTime *time1,
+ int stdoff1,
+ int walloff1,
+ VzicTime *time2,
+ int stdoff2,
+ int walloff2);
+static void output_zone_components (FILE *fp,
+ char *name,
+ GArray *changes);
+static void set_previous_offsets (GArray *changes);
+static gboolean check_for_recurrence (FILE *fp,
+ GArray *changes,
+ int idx);
+static void check_for_rdates (FILE *fp,
+ GArray *changes,
+ int idx);
+static gboolean timezones_match (char *tzname1,
+ char *tzname2);
+static int output_component_start (char *buffer,
+ VzicTime *vzictime,
+ gboolean output_rdate,
+ gboolean use_same_tz_offset);
+static void output_component_end (FILE *fp,
+ VzicTime *vzictime);
+
+static void vzictime_init (VzicTime *vzictime);
+static int calculate_actual_time (VzicTime *vzictime,
+ TimeCode time_code,
+ int stdoff,
+ int walloff);
+static int calculate_wall_time (int time,
+ TimeCode time_code,
+ int stdoff,
+ int walloff,
+ int *day_offset);
+static int calculate_until_time (int time,
+ TimeCode time_code,
+ int stdoff,
+ int walloff,
+ int *year,
+ int *month,
+ int *day);
+static void fix_time_overflow (int *year,
+ int *month,
+ int *day,
+ int day_offset);
+
+static char* format_time (int year,
+ int month,
+ int day,
+ int time);
+static char* format_tz_offset (int tz_offset,
+ gboolean round_seconds);
+static gboolean output_rrule (char *rrule_buffer,
+ int month,
+ DayCode day_code,
+ int day_number,
+ int day_weekday,
+ int day_offset,
+ char *until);
+static gboolean output_rrule_2 (char *buffer,
+ int month,
+ int day_number,
+ int day_weekday);
+
+static char* format_vzictime (VzicTime *vzictime);
+
+static void dump_changes (FILE *fp,
+ char *zone_name,
+ GArray *changes);
+static void dump_change (FILE *fp,
+ char *zone_name,
+ VzicTime *vzictime,
+ int year);
+
+static void expand_tzid_prefix (void);
+
+
+void
+output_vtimezone_files (char *directory,
+ GArray *zone_data,
+ GHashTable *rule_data,
+ GHashTable *link_data,
+ int max_until_year)
+{
+ ZoneData *zone;
+ GList *links;
+ char *link_to;
+ int i;
+
+ /* Insert today's date into the TZIDs we output. */
+ expand_tzid_prefix ();
+
+ /* Expand the rule data so that each entry specifies only one year, and
+ sort it so we can easily find the rules applicable to each Zone span. */
+ g_hash_table_foreach (rule_data, expand_and_sort_rule_array,
+ GINT_TO_POINTER (max_until_year));
+
+ /* Output each timezone. */
+ for (i = 0; i < zone_data->len; i++) {
+ zone = &g_array_index (zone_data, ZoneData, i);
+ output_zone (directory, zone, zone->zone_name, rule_data);
+
+ /* Look for any links from this zone. */
+ links = g_hash_table_lookup (link_data, zone->zone_name);
+
+ while (links) {
+ link_to = links->data;
+
+ /* We ignore Links that don't have a '/' in them (things like 'EST5EDT').
+ */
+ if (strchr (link_to, '/')) {
+ output_zone (directory, zone, link_to, rule_data);
+ }
+
+ links = links->next;
+ }
+ }
+}
+
+
+static void
+expand_and_sort_rule_array (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ char *name = key;
+ GArray *rule_array = value;
+ RuleData *rule, tmp_rule;
+ int len, max_year, i, from, to, year;
+ gboolean is_infinite;
+
+ /* We expand the rule data to a year greater than any year used in a Zone
+ UNTIL value. This is so that we can easily get parts of the array to
+ use for each Zone line. */
+ max_year = GPOINTER_TO_INT (data) + 2;
+
+ /* If any of the rules apply to several years, we turn it into a single rule
+ for each year. If the Rule is infinite we go up to max_year.
+ We change the FROM field in the copies of the Rule, setting it to each
+ of the years, and set TO to FROM, except if TO was YEAR_MAXIMUM we set
+ the last TO to YEAR_MAXIMUM, so we still know the Rule is infinite. */
+ len = rule_array->len;
+ for (i = 0; i < len; i++) {
+ rule = &g_array_index (rule_array, RuleData, i);
+
+ /* None of the Rules currently use the TYPE field, but we'd better check.
+ */
+ if (rule->type) {
+ fprintf (stderr, "Rules %s has a TYPE: %s\n", name, rule->type);
+ exit (1);
+ }
+
+ if (rule->from_year != rule->to_year) {
+ from = rule->from_year;
+ to = rule->to_year;
+
+ tmp_rule = *rule;
+
+ /* Flag that this is a shallow copy so we don't free anything twice. */
+ tmp_rule.is_shallow_copy = TRUE;
+
+ /* See if it is an infinite Rule. */
+ if (to == YEAR_MAXIMUM) {
+ is_infinite = TRUE;
+ to = max_year;
+ if (from < to)
+ rule->to_year = rule->from_year;
+ } else {
+ is_infinite = FALSE;
+ }
+
+ /* Create a copy of the Rule for each year. */
+ for (year = from + 1; year <= to; year++) {
+ tmp_rule.from_year = year;
+
+ /* If the Rule is infinite, mark the last copy as infinite. */
+ if (year == to && is_infinite)
+ tmp_rule.to_year = YEAR_MAXIMUM;
+ else
+ tmp_rule.to_year = year;
+
+ g_array_append_val (rule_array, tmp_rule);
+ }
+ }
+ }
+
+ /* Now sort the rules. */
+ qsort (rule_array->data, rule_array->len, sizeof (RuleData), rule_sort_func);
+
+#if 0
+ dump_rule_array (name, rule_array, stdout);
+#endif
+}
+
+
+/* This is used to sort the rules, after the rules have all been expanded so
+ that each one is only for one year. */
+static int
+rule_sort_func (const void *arg1,
+ const void *arg2)
+{
+ RuleData *rule1, *rule2;
+ int time1_year, time1_month, time1_day;
+ int time2_year, time2_month, time2_day;
+ int month_diff, result;
+ VzicTime t1, t2;
+
+ rule1 = (RuleData*) arg1;
+ rule2 = (RuleData*) arg2;
+
+ time1_year = rule1->from_year;
+ time1_month = rule1->in_month;
+ time2_year = rule2->from_year;
+ time2_month = rule2->in_month;
+
+ /* If there is more that one month difference we don't need to calculate
+ the day or time. */
+ month_diff = (time1_year - time2_year) * 12 + time1_month - time2_month;
+
+ if (month_diff > 1)
+ return 1;
+ if (month_diff < -1)
+ return -1;
+
+ /* Now we have to calculate the day and time of the Rule start and the
+ VzicTime, using the given offsets. */
+ t1.year = time1_year;
+ t1.month = time1_month;
+ t1.day_code = rule1->on_day_code;
+ t1.day_number = rule1->on_day_number;
+ t1.day_weekday = rule1->on_day_weekday;
+ t1.time_code = rule1->at_time_code;
+ t1.time_seconds = rule1->at_time_seconds;
+
+ t2.year = time2_year;
+ t2.month = time2_month;
+ t2.day_code = rule2->on_day_code;
+ t2.day_number = rule2->on_day_number;
+ t2.day_weekday = rule2->on_day_weekday;
+ t2.time_code = rule2->at_time_code;
+ t2.time_seconds = rule2->at_time_seconds;
+
+ /* FIXME: We don't know the offsets yet, but I don't think any Rules are
+ close enough together that the offsets can make a difference. Should
+ check this. */
+ calculate_actual_time (&t1, TIME_WALL, 0, 0);
+ calculate_actual_time (&t2, TIME_WALL, 0, 0);
+
+ /* Now we can compare the entire time. */
+ if (t1.year > t2.year)
+ result = 1;
+ else if (t1.year < t2.year)
+ result = -1;
+
+ else if (t1.month > t2.month)
+ result = 1;
+ else if (t1.month < t2.month)
+ result = -1;
+
+ else if (t1.day_number > t2.day_number)
+ result = 1;
+ else if (t1.day_number < t2.day_number)
+ result = -1;
+
+ else if (t1.time_seconds > t2.time_seconds)
+ result = 1;
+ else if (t1.time_seconds < t2.time_seconds)
+ result = -1;
+
+ else {
+ printf ("WARNING: Rule dates matched.\n");
+ result = 0;
+ }
+
+ return result;
+}
+
+
+static void
+output_zone (char *directory,
+ ZoneData *zone,
+ char *zone_name,
+ GHashTable *rule_data)
+{
+ FILE *fp, *changes_fp = NULL;
+ char output_directory[PATHNAME_BUFFER_SIZE];
+ char filename[PATHNAME_BUFFER_SIZE];
+ char changes_filename[PATHNAME_BUFFER_SIZE];
+ char *zone_directory, *zone_subdirectory, *zone_filename;
+
+ /* Set a global for the zone_name, to be used only for debug messages. */
+ CurrentZoneName = zone_name;
+
+ /* Use this to only output a particular zone. */
+#if 0
+ if (strcmp (zone_name, "Atlantic/Azores"))
+ return;
+#endif
+
+#if 0
+ printf ("Outputting Zone: %s\n", zone_name);
+#endif
+
+ if (!parse_zone_name (zone_name, &zone_directory, &zone_subdirectory,
+ &zone_filename))
+ return;
+
+ if (VzicDumpZoneNamesAndCoords) {
+ VzicTimeZoneNames = g_list_prepend (VzicTimeZoneNames,
+ g_strdup (zone_name));
+ }
+
+ sprintf (output_directory, "%s/%s", directory, zone_directory);
+ ensure_directory_exists (output_directory);
+ sprintf (filename, "%s/%s.ics", output_directory, zone_filename);
+
+ if (VzicDumpChanges) {
+ sprintf (output_directory, "%s/ChangesVzic/%s", directory, zone_directory);
+ ensure_directory_exists (output_directory);
+ sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
+ }
+
+ if (zone_subdirectory) {
+ sprintf (output_directory, "%s/%s/%s", directory, zone_directory,
+ zone_subdirectory);
+ ensure_directory_exists (output_directory);
+ sprintf (filename, "%s/%s.ics", output_directory, zone_filename);
+
+ if (VzicDumpChanges) {
+ sprintf (output_directory, "%s/ChangesVzic/%s/%s", directory,
+ zone_directory, zone_subdirectory);
+ ensure_directory_exists (output_directory);
+ sprintf (changes_filename, "%s/%s", output_directory, zone_filename);
+ }
+ }
+
+ /* Create the files. */
+ fp = fopen (filename, "w");
+ if (!fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", filename);
+ exit (1);
+ }
+
+ if (VzicDumpChanges) {
+ changes_fp = fopen (changes_filename, "w");
+ if (!changes_fp) {
+ fprintf (stderr, "Couldn't create file: %s\n", changes_filename);
+ exit (1);
+ }
+ }
+
+ fprintf (fp, "BEGIN:VCALENDAR\nPRODID:%s\nVERSION:2.0\n", ProductID);
+
+ output_zone_to_files (zone, zone_name, rule_data, fp, changes_fp);
+
+ if (ferror (fp)) {
+ fprintf (stderr, "Error writing file: %s\n", filename);
+ exit (1);
+ }
+
+ fprintf (fp, "END:VCALENDAR\n");
+
+ fclose (fp);
+
+ g_free (zone_directory);
+ g_free (zone_subdirectory);
+ g_free (zone_filename);
+}
+
+
+/* This checks that the Zone name only uses the characters in [-+_/a-zA-Z0-9],
+ and outputs a warning if it isn't. */
+static gboolean
+parse_zone_name (char *name,
+ char **directory,
+ char **subdirectory,
+ char **filename)
+{
+ static int invalid_zone_num = 1;
+
+ char *p, ch, *first_slash_pos = NULL, *second_slash_pos = NULL;
+ gboolean invalid = FALSE;
+
+ for (p = name; (ch = *p) != 0; p++) {
+ if ((ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')
+ && (ch < '0' || ch > '9') && ch != '/' && ch != '_'
+ && ch != '-' && ch != '+') {
+ fprintf (stderr, "WARNING: Unusual Zone name: %s\n", name);
+ invalid = TRUE;
+ break;
+ }
+
+ if (ch == '/') {
+ if (!first_slash_pos) {
+ first_slash_pos = p;
+ } else if (!second_slash_pos) {
+ second_slash_pos = p;
+ } else {
+ fprintf (stderr, "WARNING: More than 2 '/' characters in Zone name: %s\n", name);
+ invalid = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (!first_slash_pos) {
+#if 0
+ fprintf (stderr, "No '/' character in Zone name: %s. Skipping.\n", name);
+#endif
+ return FALSE;
+ }
+
+ if (invalid) {
+ *directory = g_strdup ("Invalid");
+ *filename = g_strdup_printf ("Zone%i", invalid_zone_num++);
+ } else {
+ *first_slash_pos = '\0';
+ *directory = g_strdup (name);
+ *first_slash_pos = '/';
+
+ if (second_slash_pos) {
+ *second_slash_pos = '\0';
+ *subdirectory = g_strdup (first_slash_pos + 1);
+ *second_slash_pos = '/';
+
+ *filename = g_strdup (second_slash_pos + 1);
+ } else {
+ *subdirectory = NULL;
+ *filename = g_strdup (first_slash_pos + 1);
+ }
+ }
+
+ return invalid ? FALSE : TRUE;
+}
+
+
+static void
+output_zone_to_files (ZoneData *zone,
+ char *zone_name,
+ GHashTable *rule_data,
+ FILE *fp,
+ FILE *changes_fp)
+{
+ ZoneLineData *zone_line;
+ GArray *changes;
+ int i, stdoff, walloff, start_index, save_seconds;
+ VzicTime start, end, *vzictime_start, *vzictime, *vzictime_first_rule_change;
+ gboolean is_daylight, found_letter_s;
+ char *start_letter_s;
+
+ changes = g_array_new (FALSE, FALSE, sizeof (VzicTime));
+
+ vzictime_init (&start);
+ vzictime_init (&end);
+
+ /* The first period starts at -infinity. */
+ start.year = YEAR_MINIMUM;
+
+ for (i = 0; i < zone->zone_line_data->len; i++) {
+ zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, i);
+
+ /* This is the local standard time offset from GMT for this period. */
+ start.stdoff = stdoff = zone_line->stdoff_seconds;
+ start.walloff = walloff = stdoff + zone_line->save_seconds;
+
+ if (zone_line->until_set) {
+ end.year = zone_line->until_year;
+ end.month = zone_line->until_month;
+ end.day_code = zone_line->until_day_code;
+ end.day_number = zone_line->until_day_number;
+ end.day_weekday = zone_line->until_day_weekday;
+ end.time_seconds = zone_line->until_time_seconds;
+ end.time_code = zone_line->until_time_code;
+ } else {
+ /* The last period ends at +infinity. */
+ end.year = YEAR_MAXIMUM;
+ }
+
+ /* Add a time change for the start of the period. This may be removed
+ later if one of the rules expands to exactly the same time. */
+ start_index = changes->len;
+ g_array_append_val (changes, start);
+
+ /* If there are Rules associated with this period, add all the relevant
+ time changes. */
+ save_seconds = 0;
+ if (zone_line->rules)
+ found_letter_s = add_rule_changes (zone_line, zone_name, changes,
+ rule_data, &start, &end,
+ &start_letter_s, &save_seconds);
+ else
+ found_letter_s = FALSE;
+
+ /* FIXME: I'm not really sure what to do about finding a LETTER_S for the
+ first part of the period (i.e. before the first Rule comes into effect).
+ Currently we try to use the same LETTER_S as the first Rule of the
+ period which is in local standard time. */
+ if (zone_line->save_seconds)
+ save_seconds = zone_line->save_seconds;
+ is_daylight = save_seconds ? TRUE : FALSE;
+ vzictime_start = &g_array_index (changes, VzicTime, start_index);
+ walloff = vzictime_start->walloff = stdoff + save_seconds;
+
+ /* TEST: See if the first Rule time is exactly the same as the change from
+ the Zone line. In which case we can remove the Zone line change. */
+ if (changes->len > start_index + 1) {
+ int prev_stdoff, prev_walloff;
+
+ if (start_index > 0) {
+ VzicTime *v = &g_array_index (changes, VzicTime, start_index - 1);
+ prev_stdoff = v->stdoff;
+ prev_walloff = v->walloff;
+ } else {
+ prev_stdoff = 0;
+ prev_walloff = 0;
+ }
+ vzictime_first_rule_change = &g_array_index (changes, VzicTime,
+ start_index + 1);
+ if (times_match (vzictime_start, prev_stdoff, prev_walloff,
+ vzictime_first_rule_change, stdoff, walloff)) {
+#if 0
+ printf ("Removing zone-line change (using new offsets)\n");
+#endif
+ g_array_remove_index (changes, start_index);
+ vzictime_start = NULL;
+ } else if (times_match (vzictime_start, prev_stdoff, prev_walloff,
+ vzictime_first_rule_change, prev_stdoff, prev_walloff)) {
+#if 0
+ printf ("Removing zone-line change (using previous offsets)\n");
+#endif
+ g_array_remove_index (changes, start_index);
+ vzictime_start = NULL;
+ }
+ }
+
+
+ if (vzictime_start) {
+ vzictime_start->tzname = expand_tzname (zone_name, zone_line->format,
+ found_letter_s,
+ start_letter_s, is_daylight);
+ }
+
+ /* The start of the next Zone line is the end time of this one. */
+ start = end;
+ }
+
+ set_previous_offsets (changes);
+
+ output_zone_components (fp, zone_name, changes);
+
+ if (VzicDumpChanges)
+ dump_changes (changes_fp, zone_name, changes);
+
+ /* Free all the TZNAME fields. */
+ for (i = 0; i < changes->len; i++) {
+ vzictime = &g_array_index (changes, VzicTime, i);
+ g_free (vzictime->tzname);
+ }
+
+ g_array_free (changes, TRUE);
+}
+
+
+/* This appends any timezone changes specified by the rules associated with
+ the timezone, that happen between the start and end times.
+ It returns the letter_s field of the first STANDARD rule found in the
+ search. We need this to fill in any %s in the FORMAT field of the first
+ component of the time period (the Zone line). */
+static gboolean
+add_rule_changes (ZoneLineData *zone_line,
+ char *zone_name,
+ GArray *changes,
+ GHashTable *rule_data,
+ VzicTime *start,
+ VzicTime *end,
+ char **start_letter_s,
+ int *save_seconds)
+{
+ GArray *rule_array;
+ RuleData *rule, *prev_rule = NULL;
+ int stdoff, walloff, i, prev_stdoff, prev_walloff;
+ VzicTime vzictime;
+ gboolean is_daylight, found_start_letter_s = FALSE;
+ gboolean checked_for_previous = FALSE;
+
+ *save_seconds = 0;
+
+ rule_array = g_hash_table_lookup (rule_data, zone_line->rules);
+ if (!rule_array) {
+ fprintf (stderr, "Couldn't access rules: %s\n", zone_line->rules);
+ exit (1);
+ }
+
+ /* The stdoff is the same for all the rules. */
+ stdoff = start->stdoff;
+
+ /* The walloff changes as we go through the rules. */
+ walloff = start->walloff;
+
+ /* Get the stdoff & walloff from the last change before this period. */
+ if (changes->len >= 2) {
+ VzicTime *change = &g_array_index (changes, VzicTime, changes->len - 2);
+ prev_stdoff = change->stdoff;
+ prev_walloff = change->walloff;
+ } else {
+ prev_stdoff = prev_walloff = 0;
+ }
+
+
+ for (i = 0; i < rule_array->len; i++) {
+ rule = &g_array_index (rule_array, RuleData, i);
+
+ is_daylight = rule->save_seconds != 0 ? TRUE : FALSE;
+
+ vzictime_init (&vzictime);
+ vzictime.year = rule->from_year;
+ vzictime.month = rule->in_month;
+ vzictime.day_code = rule->on_day_code;
+ vzictime.day_number = rule->on_day_number;
+ vzictime.day_weekday = rule->on_day_weekday;
+ vzictime.time_seconds = rule->at_time_seconds;
+ vzictime.time_code = rule->at_time_code;
+ vzictime.stdoff = stdoff;
+ vzictime.walloff = stdoff + rule->save_seconds;
+ vzictime.is_infinite = (rule->to_year == YEAR_MAXIMUM) ? TRUE : FALSE;
+
+ /* If the rule time is before the given start time, skip it. */
+ if (compare_times (&vzictime, stdoff, walloff,
+ start, prev_stdoff, prev_walloff) < 0)
+ continue;
+
+ /* If the previous Rule was a daylight Rule, then we may want to use the
+ walloff from that. */
+ if (!checked_for_previous) {
+ checked_for_previous = TRUE;
+ if (i > 0) {
+ prev_rule = &g_array_index (rule_array, RuleData, i - 1);
+ if (prev_rule->save_seconds) {
+ walloff = start->walloff = stdoff + prev_rule->save_seconds;
+ *save_seconds = prev_rule->save_seconds;
+ found_start_letter_s = TRUE;
+ *start_letter_s = prev_rule->letter_s;
+#if 0
+ printf ("Could use save_seconds from previous Rule: %s\n",
+ zone_name);
+#endif
+ }
+ }
+ }
+
+ /* If an end time has been given, then if the rule time is on or after it
+ break out of the loop. */
+ if (end->year != YEAR_MAXIMUM
+ && compare_times (&vzictime, stdoff, walloff,
+ end, stdoff, walloff) >= 0)
+ break;
+
+ vzictime.tzname = expand_tzname (zone_name, zone_line->format, TRUE,
+ rule->letter_s, is_daylight);
+
+ g_array_append_val (changes, vzictime);
+
+ /* When we find the first STANDARD time we set letter_s. */
+ if (!found_start_letter_s && !is_daylight) {
+ found_start_letter_s = TRUE;
+ *start_letter_s = rule->letter_s;
+ }
+
+ /* Now that we have added the Rule, the new walloff comes into effect
+ for any following Rules. */
+ walloff = vzictime.walloff;
+ }
+
+ return found_start_letter_s;
+}
+
+
+/* This expands the Zone line FORMAT field, using the given LETTER_S from a
+ Rule line. There are 3 types of FORMAT field:
+ 1. a string with an %s in, e.g. "WE%sT". The %s is replaced with LETTER_S.
+ 2. a string with an '/' in, e.g. "CAT/CAWT". The first part is used for
+ standard time and the second part for when daylight-saving is in effect.
+ 3. a plain string, e.g. "LMT", which we leave as-is.
+ Note that (1) is the only type in which letter_s is required.
+*/
+static char*
+expand_tzname (char *zone_name,
+ char *format,
+ gboolean have_letter_s,
+ char *letter_s,
+ gboolean is_daylight)
+{
+ char *p, buffer[256], *guess = NULL;
+ int len;
+
+#if 0
+ printf ("Expanding %s with %s\n", format, letter_s);
+#endif
+
+ if (!format || !format[0]) {
+ fprintf (stderr, "Missing FORMAT\n");
+ exit (1);
+ }
+
+ /* 1. Look for a "%s". */
+ p = strchr (format, '%');
+ if (p && *(p + 1) == 's') {
+ if (!have_letter_s) {
+
+ /* NOTE: These are a few hard-coded TZNAMEs that I've looked up myself.
+ These are needed in a few places where a Zone line comes into effect
+ but no Rule has been found, so we have no LETTER_S to use.
+ I've tried to use whatever is the normal LETTER_S in the Rules for
+ the particular zone, in local standard time. */
+ if (!strcmp (zone_name, "Asia/Macao")
+ && !strcmp (format, "C%sT"))
+ guess = "CST";
+ else if (!strcmp (zone_name, "Asia/Macau")
+ && !strcmp (format, "C%sT"))
+ guess = "CST";
+ else if (!strcmp (zone_name, "Asia/Ashgabat")
+ && !strcmp (format, "ASH%sT"))
+ guess = "ASHT";
+ else if (!strcmp (zone_name, "Asia/Ashgabat")
+ && !strcmp (format, "TM%sT"))
+ guess = "TMT";
+ else if (!strcmp (zone_name, "Asia/Samarkand")
+ && !strcmp (format, "TAS%sT"))
+ guess = "TAST";
+ else if (!strcmp (zone_name, "Atlantic/Azores")
+ && !strcmp (format, "WE%sT"))
+ guess = "WET";
+ else if (!strcmp (zone_name, "Europe/Paris")
+ && !strcmp (format, "WE%sT"))
+ guess = "WET";
+ else if (!strcmp (zone_name, "Europe/Warsaw")
+ && !strcmp (format, "CE%sT"))
+ guess = "CET";
+ else if (!strcmp (zone_name, "America/Phoenix")
+ && !strcmp (format, "M%sT"))
+ guess = "MST";
+ else if (!strcmp (zone_name, "America/Nome")
+ && !strcmp (format, "Y%sT"))
+ guess = "YST";
+
+ if (guess) {
+#if 0
+ fprintf (stderr,
+ "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Guessing: %s\n",
+ format, zone_name, guess);
+#endif
+ return g_strdup (guess);
+ }
+
+#if 1
+ fprintf (stderr,
+ "WARNING: Couldn't find a LETTER_S to use in FORMAT: %s in Zone: %s Leaving TZNAME empty\n",
+ format, zone_name);
+#endif
+
+#if 0
+ /* This is useful to spot exactly which component had a problem. */
+ sprintf (buffer, "FIXME: %s", format);
+ return g_strdup (buffer);
+#else
+ /* We give up and don't output a TZNAME. */
+ return NULL;
+#endif
+ }
+
+ sprintf (buffer, format, letter_s ? letter_s : "");
+ return g_strdup (buffer);
+ }
+
+ /* 2. Look for a "/". */
+ p = strchr (format, '/');
+ if (p) {
+ if (is_daylight) {
+ return g_strdup (p + 1);
+ } else {
+ len = p - format;
+ strncpy (buffer, format, len);
+ buffer[len] = '\0';
+ return g_strdup (buffer);
+ }
+ }
+
+ /* 3. Just use format as it is. */
+ return g_strdup (format);
+}
+
+
+/* Compares 2 VzicTimes, returning strcmp()-like values, i.e. 0 if equal,
+ 1 if the 1st is after the 2nd and -1 if the 1st is before the 2nd. */
+static int
+compare_times (VzicTime *time1,
+ int stdoff1,
+ int walloff1,
+ VzicTime *time2,
+ int stdoff2,
+ int walloff2)
+{
+ VzicTime t1, t2;
+ int result;
+
+ t1 = *time1;
+ t2 = *time2;
+
+ calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1);
+ calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2);
+
+ /* Now we can compare the entire time. */
+ if (t1.year > t2.year)
+ result = 1;
+ else if (t1.year < t2.year)
+ result = -1;
+
+ else if (t1.month > t2.month)
+ result = 1;
+ else if (t1.month < t2.month)
+ result = -1;
+
+ else if (t1.day_number > t2.day_number)
+ result = 1;
+ else if (t1.day_number < t2.day_number)
+ result = -1;
+
+ else if (t1.time_seconds > t2.time_seconds)
+ result = 1;
+ else if (t1.time_seconds < t2.time_seconds)
+ result = -1;
+
+ else
+ result = 0;
+
+#if 0
+ printf ("%i/%i/%i %i <=> %i/%i/%i %i -> %i\n",
+ t1.day_number, t1.month + 1, t1.year, t1.time_seconds,
+ t2.day_number, t2.month + 1, t2.year, t2.time_seconds,
+ result);
+#endif
+
+ return result;
+}
+
+
+/* Returns TRUE if the 2 times are exactly the same. It will calculate the
+ actual day, but doesn't convert times. */
+static gboolean
+times_match (VzicTime *time1,
+ int stdoff1,
+ int walloff1,
+ VzicTime *time2,
+ int stdoff2,
+ int walloff2)
+{
+ VzicTime t1, t2;
+
+ t1 = *time1;
+ t2 = *time2;
+
+ calculate_actual_time (&t1, TIME_UNIVERSAL, stdoff1, walloff1);
+ calculate_actual_time (&t2, TIME_UNIVERSAL, stdoff2, walloff2);
+
+ if (t1.year == t2.year
+ && t1.month == t2.month
+ && t1.day_number == t2.day_number
+ && t1.time_seconds == t2.time_seconds)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static void
+output_zone_components (FILE *fp,
+ char *name,
+ GArray *changes)
+{
+ VzicTime *vzictime;
+ int i, start_index = 0;
+ gboolean only_one_change = FALSE;
+ char start_buffer[1024];
+
+ fprintf (fp, "BEGIN:VTIMEZONE\nTZID:%s%s\n", TZIDPrefixExpanded, name);
+
+ if (VzicUrlPrefix != NULL)
+ fprintf (fp, "TZURL:%s/%s\n", VzicUrlPrefix, name);
+
+ /* We use an 'X-' property to place the city name in. */
+ fprintf (fp, "X-LIC-LOCATION:%s\n", name);
+
+ /* We try to find any recurring components first, or they may get output
+ as lots of RDATES instead. */
+ if (!VzicNoRRules) {
+ int num_rrules_output = 0;
+
+ for (i = 1; i < changes->len; i++) {
+ if (check_for_recurrence (fp, changes, i)) {
+ num_rrules_output++;
+ }
+ }
+
+#if 0
+ printf ("Zone: %s had %i infinite RRULEs\n", CurrentZoneName,
+ num_rrules_output);
+#endif
+
+ if (!VzicPureOutput && num_rrules_output == 2) {
+#if 0
+ printf ("Zone: %s using 2 RRULEs\n", CurrentZoneName);
+#endif
+ fprintf (fp, "END:VTIMEZONE\n");
+ return;
+ }
+ }
+
+ /* We skip the first change, which starts at -infinity, unless it is the only
+ change for the timezone. */
+ if (changes->len > 1)
+ start_index = 1;
+ else
+ only_one_change = TRUE;
+
+ /* For pure output, we start at the start of the array and step through it
+ outputting RDATEs. For Outlook-compatible output we start at the end
+ and step backwards to find the first STANDARD time to output. */
+ if (VzicPureOutput)
+ i = start_index - 1;
+ else
+ i = changes->len;
+
+ for (;;) {
+ if (VzicPureOutput)
+ i++;
+ else
+ i--;
+
+ if (VzicPureOutput) {
+ if (i >= changes->len)
+ break;
+ } else {
+ if (i < start_index)
+ break;
+ }
+
+ vzictime = &g_array_index (changes, VzicTime, i);
+
+ /* If we have already output this component as part of an RRULE or RDATE,
+ then we skip it. */
+ if (vzictime->output)
+ continue;
+
+ /* For Outlook-compatible output we only want to output the last STANDARD
+ time as a DTSTART, so skip any DAYLIGHT changes. */
+ if (!VzicPureOutput && vzictime->stdoff != vzictime->walloff) {
+ printf ("Skipping DAYLIGHT change\n");
+ continue;
+ }
+
+#if 0
+ printf ("Zone: %s using DTSTART Year: %i\n", CurrentZoneName,
+ vzictime->year);
+#endif
+
+ if (VzicPureOutput) {
+ output_component_start (start_buffer, vzictime, TRUE, only_one_change);
+ } else {
+ /* For Outlook compatability we don't output the RDATE and use the same
+ TZOFFSET for TZOFFSETFROM and TZOFFSETTO. */
+ vzictime->year = RDATE_YEAR;
+ vzictime->month = 0;
+ vzictime->day_code = DAY_SIMPLE;
+ vzictime->day_number = 1;
+ vzictime->time_code = TIME_WALL;
+ vzictime->time_seconds = 0;
+
+ output_component_start (start_buffer, vzictime, FALSE, TRUE);
+ }
+
+ fprintf (fp, "%s", start_buffer);
+
+ /* This will look for matching components and output them as RDATEs
+ instead of separate components. */
+ if (VzicPureOutput && !VzicNoRDates)
+ check_for_rdates (fp, changes, i);
+
+ output_component_end (fp, vzictime);
+
+ vzictime->output = TRUE;
+
+ if (!VzicPureOutput)
+ break;
+ }
+
+ fprintf (fp, "END:VTIMEZONE\n");
+}
+
+
+/* This sets the prev_stdoff and prev_walloff (i.e. the TZOFFSETFROM) of each
+ VzicTime, using the stdoff and walloff of the previous VzicTime. It makes
+ the rest of the code much simpler. */
+static void
+set_previous_offsets (GArray *changes)
+{
+ VzicTime *vzictime, *prev_vzictime;
+ int i;
+
+ prev_vzictime = &g_array_index (changes, VzicTime, 0);
+ prev_vzictime->prev_stdoff = 0;
+ prev_vzictime->prev_walloff = 0;
+
+ for (i = 1; i < changes->len; i++) {
+ vzictime = &g_array_index (changes, VzicTime, i);
+
+ vzictime->prev_stdoff = prev_vzictime->stdoff;
+ vzictime->prev_walloff = prev_vzictime->walloff;
+
+ prev_vzictime = vzictime;
+ }
+}
+
+
+/* Returns TRUE if we output an infinite recurrence. */
+static gboolean
+check_for_recurrence (FILE *fp,
+ GArray *changes,
+ int idx)
+{
+ VzicTime *vzictime_start, *vzictime, vzictime_start_copy;
+ gboolean is_daylight_start, is_daylight;
+ int last_match, i, next_year, day_offset;
+ char until[256], rrule_buffer[2048], start_buffer[1024];
+ GList *matching_elements = NULL, *elem;
+
+ vzictime_start = &g_array_index (changes, VzicTime, idx);
+
+ /* If this change has already been output, skip it. */
+ if (vzictime_start->output)
+ return FALSE;
+
+ /* There can't possibly be an RRULE starting from YEAR_MINIMUM. */
+ if (vzictime_start->year == YEAR_MINIMUM)
+ return FALSE;
+
+ is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff)
+ ? TRUE : FALSE;
+
+#if 0
+ printf ("\nChecking: %s OFFSETFROM: %i %s\n",
+ format_vzictime (vzictime_start), vzictime_start->prev_walloff,
+ is_daylight_start ? "DAYLIGHT" : "");
+#endif
+
+ /* If this is an infinitely recurring change, output the RRULE and return.
+ There won't be any changes after it that we could merge. */
+ if (vzictime_start->is_infinite) {
+
+ /* Change the year to our minimum start year. */
+ vzictime_start_copy = *vzictime_start;
+ if (!VzicPureOutput)
+ vzictime_start_copy.year = RRULE_START_YEAR;
+
+ day_offset = output_component_start (start_buffer, &vzictime_start_copy,
+ FALSE, FALSE);
+
+ if (!output_rrule (rrule_buffer, vzictime_start_copy.month,
+ vzictime_start_copy.day_code,
+ vzictime_start_copy.day_number,
+ vzictime_start_copy.day_weekday, day_offset, "")) {
+ if (vzictime_start->year != MAX_TIME_T_YEAR) {
+ fprintf (stderr, "WARNING: Failed to output infinite recurrence with start year: %i\n", vzictime_start->year);
+ }
+ return TRUE;
+ }
+
+ fprintf (fp, "%s%s", start_buffer, rrule_buffer);
+ output_component_end (fp, vzictime_start);
+ vzictime_start->output = TRUE;
+ return TRUE;
+ }
+
+ last_match = idx;
+ next_year = vzictime_start->year + 1;
+ for (i = idx + 1; i < changes->len; i++) {
+ vzictime = &g_array_index (changes, VzicTime, i);
+
+ is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE;
+
+ if (vzictime->output)
+ continue;
+
+#if 0
+ printf (" %s OFFSETFROM: %i %s\n",
+ format_vzictime (vzictime), vzictime->prev_walloff,
+ is_daylight ? "DAYLIGHT" : "");
+#endif
+
+ /* If it is more than one year ahead, we are finished, since we want
+ consecutive years. */
+ if (vzictime->year > next_year) {
+ break;
+ }
+
+ /* It must be the same type of component - STANDARD or DAYLIGHT. */
+ if (is_daylight != is_daylight_start) {
+ continue;
+ }
+
+ /* It must be the following year, with the same month, day & time.
+ It is possible that the time has a different code but does in fact
+ match when normalized, but we don't care (for now at least). */
+ if (vzictime->year != next_year
+ || vzictime->month != vzictime_start->month
+ || vzictime->day_code != vzictime_start->day_code
+ || vzictime->day_number != vzictime_start->day_number
+ || vzictime->day_weekday != vzictime_start->day_weekday
+ || vzictime->time_seconds != vzictime_start->time_seconds
+ || vzictime->time_code != vzictime_start->time_code) {
+ continue;
+ }
+
+ /* The TZOFFSETFROM and TZOFFSETTO must match. */
+ if (vzictime->prev_walloff != vzictime_start->prev_walloff) {
+ continue;
+ }
+
+ if (vzictime->walloff != vzictime_start->walloff) {
+ continue;
+ }
+
+ /* TZNAME must match. */
+ if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) {
+ continue;
+ }
+
+ /* We have a match. */
+ last_match = i;
+ next_year = vzictime->year + 1;
+
+ matching_elements = g_list_prepend (matching_elements, vzictime);
+ }
+
+ if (last_match == idx)
+ return FALSE;
+
+#if 0
+ printf ("Found recurrence %i - %i!!!\n", vzictime_start->year,
+ next_year - 1);
+#endif
+
+ vzictime = &g_array_index (changes, VzicTime, last_match);
+
+/* We only use RRULEs if there are at least MIN_RRULE_OCCURRENCES occurrences,
+ since otherwise RDATEs are more efficient. */
+ if (!vzictime->is_infinite) {
+ int years = vzictime->year - vzictime_start->year + 1;
+#if 0
+ printf ("RRULE Years: %i\n", years);
+#endif
+ if (years < MIN_RRULE_OCCURRENCES)
+ return FALSE;
+ }
+
+ if (vzictime->is_infinite) {
+ until[0] = '\0';
+ } else {
+ VzicTime t1 = *vzictime;
+
+ printf ("RRULE with UNTIL - aborting\n");
+ abort ();
+
+ calculate_actual_time (&t1, TIME_UNIVERSAL, vzictime->prev_stdoff,
+ vzictime->prev_walloff);
+
+ /* Output UNTIL, in UTC. */
+ sprintf (until, ";UNTIL=%sZ", format_time (t1.year, t1.month,
+ t1.day_number,
+ t1.time_seconds));
+ }
+
+ /* Change the year to our minimum start year. */
+ vzictime_start_copy = *vzictime_start;
+ if (!VzicPureOutput)
+ vzictime_start_copy.year = RRULE_START_YEAR;
+
+ day_offset = output_component_start (start_buffer, &vzictime_start_copy,
+ FALSE, FALSE);
+ if (output_rrule (rrule_buffer, vzictime_start_copy.month,
+ vzictime_start_copy.day_code,
+ vzictime_start_copy.day_number,
+ vzictime_start_copy.day_weekday, day_offset, until)) {
+ fprintf (fp, "%s%s", start_buffer, rrule_buffer);
+ output_component_end (fp, vzictime_start);
+
+ /* Mark all the changes as output. */
+ vzictime_start->output = TRUE;
+ for (elem = matching_elements; elem; elem = elem->next) {
+ vzictime = elem->data;
+ vzictime->output = TRUE;
+ }
+ }
+
+ g_list_free (matching_elements);
+
+ return TRUE;
+}
+
+
+static void
+check_for_rdates (FILE *fp,
+ GArray *changes,
+ int idx)
+{
+ VzicTime *vzictime_start, *vzictime, tmp_vzictime;
+ gboolean is_daylight_start, is_daylight;
+ int i, year, month, day, time;
+
+ vzictime_start = &g_array_index (changes, VzicTime, idx);
+
+ is_daylight_start = (vzictime_start->stdoff != vzictime_start->walloff)
+ ? TRUE : FALSE;
+
+#if 0
+ printf ("\nChecking: %s OFFSETFROM: %i %s\n",
+ format_vzictime (vzictime_start), vzictime_start->prev_walloff,
+ is_daylight_start ? "DAYLIGHT" : "");
+#endif
+
+ /* We want to go backwards through the array now, for Outlook compatability.
+ (It only looks at the first DTSTART/RDATE.) */
+ for (i = idx + 1; i < changes->len; i++) {
+ vzictime = &g_array_index (changes, VzicTime, i);
+
+ is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE;
+
+ if (vzictime->output)
+ continue;
+
+#if 0
+ printf (" %s OFFSETFROM: %i %s\n", format_vzictime (vzictime),
+ vzictime->prev_walloff, is_daylight ? "DAYLIGHT" : "");
+#endif
+
+ /* It must be the same type of component - STANDARD or DAYLIGHT. */
+ if (is_daylight != is_daylight_start) {
+ continue;
+ }
+
+ /* The TZOFFSETFROM and TZOFFSETTO must match. */
+ if (vzictime->prev_walloff != vzictime_start->prev_walloff) {
+ continue;
+ }
+
+ if (vzictime->walloff != vzictime_start->walloff) {
+ continue;
+ }
+
+ /* TZNAME must match. */
+ if (!timezones_match (vzictime->tzname, vzictime_start->tzname)) {
+ continue;
+ }
+
+ /* We have a match. */
+
+ tmp_vzictime = *vzictime;
+ calculate_actual_time (&tmp_vzictime, TIME_WALL, vzictime->prev_stdoff,
+ vzictime->prev_walloff);
+
+ fprintf (fp, "RDATE:%s\n", format_time (tmp_vzictime.year,
+ tmp_vzictime.month,
+ tmp_vzictime.day_number,
+ tmp_vzictime.time_seconds));
+
+ vzictime->output = TRUE;
+ }
+}
+
+
+static gboolean
+timezones_match (char *tzname1,
+ char *tzname2)
+{
+ if (tzname1 && tzname2 && !strcmp (tzname1, tzname2))
+ return TRUE;
+
+ if (!tzname1 && !tzname2)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/* Outputs the start of a VTIMEZONE component, with the BEGIN line,
+ the DTSTART, TZOFFSETFROM, TZOFFSETTO & TZNAME properties. */
+static int
+output_component_start (char *buffer,
+ VzicTime *vzictime,
+ gboolean output_rdate,
+ gboolean use_same_tz_offset)
+{
+ gboolean is_daylight, skip_day_offset = FALSE;
+ gint year, month, day, time, day_offset = 0;
+ GDate old_date, new_date;
+ char *formatted_time;
+ char line1[1024], line2[1024], line3[1024];
+ char line4[1024], line5[1024], line6[1024];
+ VzicTime tmp_vzictime;
+ int prev_walloff;
+
+ is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE;
+
+ tmp_vzictime = *vzictime;
+ day_offset = calculate_actual_time (&tmp_vzictime, TIME_WALL,
+ vzictime->prev_stdoff,
+ vzictime->prev_walloff);
+
+ sprintf (line1, "BEGIN:%s\n", is_daylight ? "DAYLIGHT" : "STANDARD");
+
+ /* If the timezone only has one change, that means it uses the same offset
+ forever, so we use the same TZOFFSETFROM as the TZOFFSETTO. (If the zone
+ has more than one change, we don't output the first one.) */
+ if (use_same_tz_offset)
+ prev_walloff = vzictime->walloff;
+ else
+ prev_walloff = vzictime->prev_walloff;
+
+ sprintf (line2, "TZOFFSETFROM:%s\n",
+ format_tz_offset (prev_walloff, !VzicPureOutput));
+
+ sprintf (line3, "TZOFFSETTO:%s\n",
+ format_tz_offset (vzictime->walloff, !VzicPureOutput));
+
+ if (vzictime->tzname)
+ sprintf (line4, "TZNAME:%s\n", vzictime->tzname);
+ else
+ line4[0] = '\0';
+
+ formatted_time = format_time (tmp_vzictime.year, tmp_vzictime.month,
+ tmp_vzictime.day_number,
+ tmp_vzictime.time_seconds);
+ sprintf (line5, "DTSTART:%s\n", formatted_time);
+ if (output_rdate)
+ sprintf (line6, "RDATE:%s\n", formatted_time);
+ else
+ line6[0] = '\0';
+
+ sprintf (buffer, "%s%s%s%s%s%s", line1, line2, line3, line4, line5, line6);
+
+ return day_offset;
+}
+
+
+/* Outputs the END line of the VTIMEZONE component. */
+static void
+output_component_end (FILE *fp,
+ VzicTime *vzictime)
+{
+ gboolean is_daylight;
+
+ is_daylight = (vzictime->stdoff != vzictime->walloff) ? TRUE : FALSE;
+
+ fprintf (fp, "END:%s\n", is_daylight ? "DAYLIGHT" : "STANDARD");
+}
+
+
+/* Initializes a VzicTime to 1st Jan in YEAR_MINIMUM at midnight, with all
+ offsets set to 0. */
+static void
+vzictime_init (VzicTime *vzictime)
+{
+ vzictime->year = YEAR_MINIMUM;
+ vzictime->month = 0;
+ vzictime->day_code = DAY_SIMPLE;
+ vzictime->day_number = 1;
+ vzictime->day_weekday = 0;
+ vzictime->time_seconds = 0;
+ vzictime->time_code = TIME_UNIVERSAL;
+ vzictime->stdoff = 0;
+ vzictime->walloff = 0;
+ vzictime->is_infinite = FALSE;
+ vzictime->output = FALSE;
+ vzictime->prev_stdoff = 0;
+ vzictime->prev_walloff = 0;
+ vzictime->tzname = NULL;
+}
+
+
+/* This calculates the actual local time that a change will occur, given
+ the offsets from standard and wall-clock time. It returns -1 or 1 if it
+ had to move backwards or forwards one day while converting to local time.
+ If it does this then we need to change the RRULEs we output. */
+static int
+calculate_actual_time (VzicTime *vzictime,
+ TimeCode time_code,
+ int stdoff,
+ int walloff)
+{
+ GDate date;
+ gint day_offset, days_in_month, weekday, offset, result;
+
+ vzictime->time_seconds = calculate_wall_time (vzictime->time_seconds,
+ vzictime->time_code,
+ stdoff, walloff, &day_offset);
+
+ if (vzictime->day_code != DAY_SIMPLE) {
+ if (vzictime->year == YEAR_MINIMUM || vzictime->year == YEAR_MAXIMUM) {
+ fprintf (stderr, "In calculate_actual_time: invalid year\n");
+ exit (0);
+ }
+
+ g_date_clear (&date, 1);
+ days_in_month = g_date_days_in_month (vzictime->month + 1, vzictime->year);
+
+ /* Note that the day_code refers to the date before we convert it to
+ a wall-clock date and time. So we find the day it was referring to,
+ then make any adjustments needed due to converting the time. */
+ if (vzictime->day_code == DAY_LAST_WEEKDAY) {
+ /* Find out what day the last day of the month is. */
+ g_date_set_dmy (&date, days_in_month, vzictime->month + 1,
+ vzictime->year);
+ weekday = g_date_weekday (&date) % 7;
+
+ /* Calculate how many days we have to go back to get to day_weekday. */
+ offset = (weekday + 7 - vzictime->day_weekday) % 7;
+
+ vzictime->day_number = days_in_month - offset;
+ } else {
+ /* Find out what day day_number actually is. */
+ g_date_set_dmy (&date, vzictime->day_number, vzictime->month + 1,
+ vzictime->year);
+ weekday = g_date_weekday (&date) % 7;
+
+ if (vzictime->day_code == DAY_WEEKDAY_ON_OR_AFTER)
+ offset = (vzictime->day_weekday + 7 - weekday) % 7;
+ else
+ offset = - ((weekday + 7 - vzictime->day_weekday) % 7);
+
+ vzictime->day_number = vzictime->day_number + offset;
+ }
+
+ vzictime->day_code = DAY_SIMPLE;
+
+ if (vzictime->day_number <= 0 || vzictime->day_number > days_in_month) {
+ fprintf (stderr, "Day overflow: %i\n", vzictime->day_number);
+ exit (1);
+ }
+ }
+
+#if 0
+ fprintf (stderr, "%s -> %i/%i/%i\n",
+ dump_day_coded (vzictime->day_code, vzictime->day_number,
+ vzictime->day_weekday),
+ vzictime->day_number, vzictime->month + 1, vzictime->year);
+#endif
+
+ fix_time_overflow (&vzictime->year, &vzictime->month,
+ &vzictime->day_number, day_offset);
+
+ /* If we want UTC time, we have to convert it now. */
+ if (time_code == TIME_UNIVERSAL) {
+ vzictime->time_seconds = calculate_until_time (vzictime->time_seconds,
+ TIME_WALL, stdoff, walloff,
+ &vzictime->year,
+ &vzictime->month,
+ &vzictime->day_number);
+ }
+
+ return day_offset;
+}
+
+
+/* This converts the given time into universal time (UTC), to be used in
+ the UNTIL property. */
+static int
+calculate_until_time (int time,
+ TimeCode time_code,
+ int stdoff,
+ int walloff,
+ int *year,
+ int *month,
+ int *day)
+{
+ int result, day_offset;
+
+ day_offset = 0;
+
+ switch (time_code) {
+ case TIME_WALL:
+ result = time - walloff;
+ break;
+ case TIME_STANDARD:
+ result = time - stdoff;
+ break;
+ case TIME_UNIVERSAL:
+ return time;
+ default:
+ fprintf (stderr, "Invalid time code\n");
+ exit (1);
+ }
+
+ if (result < 0) {
+ result += 24 * 60 * 60;
+ day_offset = -1;
+ } else if (result >= 24 * 60 * 60) {
+ result -= 24 * 60 * 60;
+ day_offset = 1;
+ }
+
+ /* Sanity check - we shouldn't have an overflow any more. */
+ if (result < 0 || result >= 24 * 60 * 60) {
+ fprintf (stderr, "Time overflow: %i\n", result);
+ abort ();
+ }
+
+ fix_time_overflow (year, month, day, day_offset);
+
+ return result;
+}
+
+
+/* This converts the given time into wall clock time (the local standard time
+ with any adjustment for daylight-saving). */
+static int
+calculate_wall_time (int time,
+ TimeCode time_code,
+ int stdoff,
+ int walloff,
+ int *day_offset)
+{
+ int result;
+
+ *day_offset = 0;
+
+ switch (time_code) {
+ case TIME_WALL:
+ return time;
+ case TIME_STANDARD:
+ /* We have a local standard time, so we have to subtract stdoff to get
+ back to UTC, then add walloff to get wall time. */
+ result = time - stdoff + walloff;
+ break;
+ case TIME_UNIVERSAL:
+ result = time + walloff;
+ break;
+ default:
+ fprintf (stderr, "Invalid time code\n");
+ exit (1);
+ }
+
+ if (result < 0) {
+ result += 24 * 60 * 60;
+ *day_offset = -1;
+ } else if (result >= 24 * 60 * 60) {
+ result -= 24 * 60 * 60;
+ *day_offset = 1;
+ }
+
+ /* Sanity check - we shouldn't have an overflow any more. */
+ if (result < 0 || result >= 24 * 60 * 60) {
+ fprintf (stderr, "Time overflow: %i\n", result);
+ exit (1);
+ }
+
+#if 0
+ printf ("%s -> ", dump_time (time, time_code, TRUE));
+ printf ("%s (%i)\n", dump_time (result, TIME_WALL, TRUE), *day_offset);
+#endif
+
+ return result;
+}
+
+
+static void
+fix_time_overflow (int *year,
+ int *month,
+ int *day,
+ int day_offset)
+{
+ if (day_offset == -1) {
+ *day = *day - 1;
+
+ if (*day == 0) {
+ *month = *month - 1;
+ if (*month == -1) {
+ *month = 11;
+ *year = *year - 1;
+ }
+ *day = g_date_days_in_month (*month + 1, *year);
+ }
+ } else if (day_offset == 1) {
+ *day = *day + 1;
+
+ if (*day > g_date_days_in_month (*month + 1, *year)) {
+ *month = *month + 1;
+ if (*month == 12) {
+ *month = 0;
+ *year = *year + 1;
+ }
+ *day = 1;
+ }
+ }
+}
+
+
+static char*
+format_time (int year,
+ int month,
+ int day,
+ int time)
+{
+ static char buffer[128];
+ int hour, minute, second;
+
+ /* When we are outputting the first component year will be YEAR_MINIMUM.
+ We used to use 1 when outputting this, but Outlook doesn't like any years
+ less that 1600, so we use 1600 instead. We don't output the first change
+ for most zones now, so it doesn't matter too much. */
+ if (year == YEAR_MINIMUM)
+ year = 1601;
+
+ /* We just use 9999 here, so we keep to 4 characters. But this should only
+ be needed when debugging - it shouldn't be needed in the VTIMEZONEs. */
+ if (year == YEAR_MAXIMUM) {
+ fprintf (stderr, "format_time: YEAR_MAXIMUM used\n");
+ year = 9999;
+ }
+
+ hour = time / 3600;
+ minute = (time % 3600) / 60;
+ second = time % 60;
+
+ sprintf (buffer, "%04i%02i%02iT%02i%02i%02i",
+ year, month + 1, day, hour, minute, second);
+
+ return buffer;
+}
+
+
+/* Outlook doesn't support 6-digit values, i.e. including the seconds, so
+ we round to the nearest minute. No current offsets use the seconds value,
+ so we aren't losing much. */
+static char*
+format_tz_offset (int tz_offset,
+ gboolean round_seconds)
+{
+ static char buffer[128];
+ char *sign = "+";
+ int hours, minutes, seconds;
+
+ if (tz_offset < 0) {
+ tz_offset = -tz_offset;
+ sign = "-";
+ }
+
+ if (round_seconds)
+ tz_offset += 30;
+
+ hours = tz_offset / 3600;
+ minutes = (tz_offset % 3600) / 60;
+ seconds = tz_offset % 60;
+
+ if (round_seconds)
+ seconds = 0;
+
+ /* Sanity check. Standard timezone offsets shouldn't be much more than 12
+ hours, and daylight saving shouldn't change it by more than a few hours.
+ (The maximum offset is 15 hours 56 minutes at present.) */
+ if (hours < 0 || hours >= 24 || minutes < 0 || minutes >= 60
+ || seconds < 0 || seconds >= 60) {
+ fprintf (stderr, "WARNING: Strange timezone offset: H:%i M:%i S:%i\n",
+ hours, minutes, seconds);
+ }
+
+ if (seconds == 0)
+ sprintf (buffer, "%s%02i%02i", sign, hours, minutes);
+ else
+ sprintf (buffer, "%s%02i%02i%02i", sign, hours, minutes, seconds);
+
+ return buffer;
+}
+
+
+static gboolean
+output_rrule (char *rrule_buffer,
+ int month,
+ DayCode day_code,
+ int day_number,
+ int day_weekday,
+ int day_offset,
+ char *until)
+{
+ char buffer[1024], buffer2[1024];
+
+ buffer[0] = '\0';
+
+ if (day_offset > 1 || day_offset < -1) {
+ fprintf (stderr, "Invalid day_offset: %i\n", day_offset);
+ exit (0);
+ }
+
+ /* If the DTSTART time was moved to another day when converting to local
+ time, we need to adjust the RRULE accordingly. e.g. If the original RRULE
+ was on the 19th of the month, but DTSTART was moved 1 day forward, then
+ we output the 20th of the month instead. */
+ if (day_offset == 1) {
+ if (day_code != DAY_LAST_WEEKDAY)
+ day_number++;
+ day_weekday = (day_weekday + 1) % 7;
+
+ /* Check we don't use February 29th. */
+ if (month == 1 && day_number > 28) {
+ fprintf (stderr, "Can't format RRULE - out of bounds. Month: %i Day number: %i\n", month + 1, day_number);
+ exit (0);
+ }
+
+ /* If we go past the end of the month, move to the next month. */
+ if (day_code != DAY_LAST_WEEKDAY && day_number > DaysInMonth[month]) {
+ month++;
+ day_number = 1;
+ }
+
+ } else if (day_offset == -1) {
+ if (day_code != DAY_LAST_WEEKDAY)
+ day_number--;
+ day_weekday = (day_weekday + 6) % 7;
+
+ if (day_code != DAY_LAST_WEEKDAY && day_number < 1)
+ fprintf (stderr, "Month: %i Day number: %i\n", month + 1, day_number);
+ }
+
+ switch (day_code) {
+ case DAY_SIMPLE:
+ /* Outlook (2000) will not parse the simple YEARLY RRULEs in VTIMEZONEs,
+ or BYMONTHDAY, or BYYEARDAY, which makes this option difficult!
+ Currently we use something like BYDAY=1SU, which will be incorrect
+ at times. This only affects Asia/Baghdad, Asia/Gaza, Asia/Jerusalem &
+ Asia/Damascus at present (and Jerusalem doesn't have specific rules
+ at the moment anyway, so that isn't a big loss). */
+ if (!VzicPureOutput) {
+ if (day_number < 8) {
+ printf ("WARNING: %s: Outputting BYDAY=1SU instead of BYMONTHDAY=1-7 for Outlook compatability\n", CurrentZoneName);
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1SU",
+ month + 1);
+ } else if (day_number < 15) {
+ printf ("WARNING: %s: Outputting BYDAY=2SU instead of BYMONTHDAY=8-14 for Outlook compatability\n", CurrentZoneName);
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2SU",
+ month + 1);
+ } else if (day_number < 22) {
+ printf ("WARNING: %s: Outputting BYDAY=3SU instead of BYMONTHDAY=15-21 for Outlook compatability\n", CurrentZoneName);
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3SU",
+ month + 1);
+ } else {
+ printf ("ERROR: %s: Couldn't output RRULE (day=%i) compatible with Outlook\n", CurrentZoneName, day_number);
+ exit (1);
+ }
+ } else {
+ sprintf (buffer, "RRULE:FREQ=YEARLY");
+ }
+ break;
+
+ case DAY_WEEKDAY_ON_OR_AFTER:
+ if (day_number > DaysInMonth[month] - 6) {
+ /* This isn't actually needed at present. */
+#if 0
+ fprintf (stderr, "DAY_WEEKDAY_ON_OR_AFTER: %i %i\n", day_number,
+ month + 1);
+#endif
+
+ if (!VzicPureOutput) {
+ printf ("ERROR: %s: Couldn't output RRULE (day>=x) compatible with Outlook\n", CurrentZoneName);
+ exit (1);
+ } else {
+ /* We do 6 days at the end of this month, and 1 at the start of the
+ next. We can't do this if we want Outlook compatability, as it
+ needs BYMONTHDAY, which Outlook doesn't support. */
+ sprintf (buffer,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s",
+ month + 1,
+ day_number, day_number + 1, day_number + 2, day_number + 3,
+ day_number + 4, day_number + 5,
+ WeekDays[day_weekday]);
+
+ sprintf (buffer2,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s",
+ (month + 1) % 12 + 1,
+ WeekDays[day_weekday]);
+
+ sprintf (rrule_buffer, "%s%s\n%s%s\n",
+ buffer, until, buffer2, until);
+
+ return TRUE;
+ }
+ }
+
+ if (!output_rrule_2 (buffer, month, day_number, day_weekday))
+ return FALSE;
+
+ break;
+
+ case DAY_WEEKDAY_ON_OR_BEFORE:
+ if (day_number < 7) {
+ /* FIXME: This is unimplemented, but it isn't needed at present anway. */
+ fprintf (stderr, "DAY_WEEKDAY_ON_OR_BEFORE: %i. Unimplemented. Exiting...\n", day_number);
+ exit (0);
+ }
+
+ if (!output_rrule_2 (buffer, month, day_number - 6, day_weekday))
+ return FALSE;
+
+ break;
+
+ case DAY_LAST_WEEKDAY:
+ if (day_offset == 1) {
+ if (month == 1) {
+ fprintf (stderr, "DAY_LAST_WEEKDAY - day moved, in February - can't fix\n");
+ exit (0);
+ }
+
+ /* This is only used once at present, for Africa/Cairo. */
+#if 0
+ fprintf (stderr, "DAY_LAST_WEEKDAY - day moved\n");
+#endif
+
+ if (!VzicPureOutput) {
+ printf ("WARNING: %s: Modifying RRULE (last weekday) for Outlook compatability\n", CurrentZoneName);
+ sprintf (buffer,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s",
+ month + 1, WeekDays[day_weekday]);
+ printf (" Outputting: %s\n", buffer);
+ } else {
+ /* We do 6 days at the end of this month, and 1 at the start of the
+ next. We can't do this if we want Outlook compatability, as it needs
+ BYMONTHDAY, which Outlook doesn't support. */
+ day_number = DaysInMonth[month];
+ sprintf (buffer,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i;BYDAY=%s",
+ month + 1,
+ day_number - 5, day_number - 4, day_number - 3,
+ day_number - 2, day_number - 1, day_number,
+ WeekDays[day_weekday]);
+
+ sprintf (buffer2,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=1;BYDAY=%s",
+ (month + 1) % 12 + 1,
+ WeekDays[day_weekday]);
+
+ sprintf (rrule_buffer, "%s%s\n%s%s\n",
+ buffer, until, buffer2, until);
+
+ return TRUE;
+ }
+
+ } else if (day_offset == -1) {
+ /* We do 7 days 1 day before the end of this month. */
+ day_number = DaysInMonth[month];
+
+ if (!output_rrule_2 (buffer, month, day_number - 7, day_weekday))
+ return FALSE;
+
+ sprintf (rrule_buffer, "%s%s\n", buffer, until);
+ return TRUE;
+ }
+
+ sprintf (buffer,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s",
+ month + 1, WeekDays[day_weekday]);
+ break;
+
+ default:
+ fprintf (stderr, "Invalid day code\n");
+ exit (1);
+ }
+
+ sprintf (rrule_buffer, "%s%s\n", buffer, until);
+ return TRUE;
+}
+
+
+/* This tries to convert a RRULE like 'BYMONTHDAY=8,9,10,11,12,13,14;BYDAY=FR'
+ into 'BYDAY=2FR'. We need this since Outlook doesn't accept BYMONTHDAY.
+ It returns FALSE if conversion is not possible. */
+static gboolean
+output_rrule_2 (char *buffer,
+ int month,
+ int day_number,
+ int day_weekday)
+{
+
+ if (day_number == 1) {
+ /* Convert it to a BYDAY=1SU type of RRULE. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s",
+ month + 1, WeekDays[day_weekday]);
+
+ } else if (day_number == 8) {
+ /* Convert it to a BYDAY=2SU type of RRULE. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s",
+ month + 1, WeekDays[day_weekday]);
+
+ } else if (day_number == 15) {
+ /* Convert it to a BYDAY=3SU type of RRULE. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=3%s",
+ month + 1, WeekDays[day_weekday]);
+
+ } else if (day_number == 22) {
+ /* Convert it to a BYDAY=4SU type of RRULE. (Currently not used.) */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=4%s",
+ month + 1, WeekDays[day_weekday]);
+
+ } else if (month != 1 && day_number == DaysInMonth[month] - 6) {
+ /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.) */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s",
+ month + 1, WeekDays[day_weekday]);
+
+ } else {
+ /* Can't convert to a correct RRULE. If we want Outlook compatability we
+ have to use a slightly incorrect RRULE, so the time change will be 1
+ week out every 7 or so years. Alternatively we could possibly move the
+ change by an hour or so so we would always be 1 or 2 hours out, but
+ never 1 week out. Yes, that sounds a better idea. */
+ if (!VzicPureOutput) {
+ printf ("WARNING: %s: Modifying RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1);
+
+ if (day_number == 2) {
+ /* Convert it to a BYDAY=1SU type of RRULE.
+ This is needed for Asia/Karachi. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=1%s",
+ month + 1, WeekDays[day_weekday]);
+ } else if (day_number == 9) {
+ /* Convert it to a BYDAY=2SU type of RRULE.
+ This is needed for Antarctica/Palmer & America/Santiago. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=2%s",
+ month + 1, WeekDays[day_weekday]);
+ } else if (month != 1 && day_number == DaysInMonth[month] - 7) {
+ /* Convert it to a BYDAY=-1SU type of RRULE. (But never for February.)
+ This is needed for America/Godthab. */
+ sprintf (buffer, "RRULE:FREQ=YEARLY;BYMONTH=%i;BYDAY=-1%s",
+ month + 1, WeekDays[day_weekday]);
+ } else {
+ printf ("ERROR: %s: Couldn't modify RRULE to be compatible with Outlook (day >= %i, month = %i)\n", CurrentZoneName, day_number, month + 1);
+ exit (1);
+ }
+
+ } else {
+ sprintf (buffer,
+ "RRULE:FREQ=YEARLY;BYMONTH=%i;BYMONTHDAY=%i,%i,%i,%i,%i,%i,%i;BYDAY=%s",
+ month + 1,
+ day_number, day_number + 1, day_number + 2, day_number + 3,
+ day_number + 4, day_number + 5, day_number + 6,
+ WeekDays[day_weekday]);
+ }
+ }
+
+ return TRUE;
+}
+
+
+static char*
+format_vzictime (VzicTime *vzictime)
+{
+ static char buffer[1024];
+
+ sprintf (buffer, "%s %2i %s %s %i %i %s",
+ dump_year (vzictime->year), vzictime->month + 1,
+ dump_day_coded (vzictime->day_code, vzictime->day_number,
+ vzictime->day_weekday),
+ dump_time (vzictime->time_seconds, vzictime->time_code, TRUE),
+ vzictime->stdoff, vzictime->walloff,
+ vzictime->is_infinite ? "INFINITE" : "");
+
+ return buffer;
+}
+
+
+static void
+dump_changes (FILE *fp,
+ char *zone_name,
+ GArray *changes)
+{
+ VzicTime *vzictime, *vzictime2 = NULL;
+ int i, year_offset, year;
+
+ for (i = 0; i < changes->len; i++) {
+ vzictime = &g_array_index (changes, VzicTime, i);
+
+ if (vzictime->year > MAX_CHANGES_YEAR)
+ return;
+
+ dump_change (fp, zone_name, vzictime, vzictime->year);
+ }
+
+ if (changes->len < 2)
+ return;
+
+ /* Now see if the changes array ends with a pair of recurring changes. */
+ vzictime = &g_array_index (changes, VzicTime, changes->len - 2);
+ vzictime2 = &g_array_index (changes, VzicTime, changes->len - 1);
+ if (!vzictime->is_infinite || !vzictime2->is_infinite)
+ return;
+
+ year_offset = 1;
+ for (;;) {
+ year = vzictime->year + year_offset;
+ if (year > MAX_CHANGES_YEAR)
+ break;
+ dump_change (fp, zone_name, vzictime, year);
+
+ year = vzictime2->year + year_offset;
+ if (year > MAX_CHANGES_YEAR)
+ break;
+ dump_change (fp, zone_name, vzictime2, year);
+
+ year_offset++;
+ }
+}
+
+
+static void
+dump_change (FILE *fp,
+ char *zone_name,
+ VzicTime *vzictime,
+ int year)
+{
+ int hour, minute, second;
+ VzicTime tmp_vzictime;
+ static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ /* Output format is:
+
+ Zone-Name [tab] Date [tab] Time [tab] UTC-Offset
+
+ The Date and Time fields specify the time change in UTC.
+
+ The UTC Offset is for local (wall-clock) time. It is the amount of time
+ to add to UTC to get local time.
+ */
+
+ fprintf (fp, "%s\t", zone_name);
+
+ if (year == YEAR_MINIMUM) {
+ fprintf (fp, " 1 Jan 0001\t 0:00:00", zone_name);
+ } else if (year == YEAR_MAXIMUM) {
+ fprintf (stderr, "Maximum year found in change time\n");
+ exit (1);
+ } else {
+ tmp_vzictime = *vzictime;
+ tmp_vzictime.year = year;
+ calculate_actual_time (&tmp_vzictime, TIME_UNIVERSAL,
+ vzictime->prev_stdoff, vzictime->prev_walloff);
+
+ hour = tmp_vzictime.time_seconds / 3600;
+ minute = (tmp_vzictime.time_seconds % 3600) / 60;
+ second = tmp_vzictime.time_seconds % 60;
+
+ fprintf (fp, "%2i %s %04i\t%2i:%02i:%02i",
+ tmp_vzictime.day_number, months[tmp_vzictime.month],
+ tmp_vzictime.year, hour, minute, second);
+ }
+
+ fprintf (fp, "\t%s", format_tz_offset (vzictime->walloff, FALSE));
+
+ fprintf (fp, "\n");
+}
+
+
+void
+ensure_directory_exists (char *directory)
+{
+ struct stat filestat;
+
+ if (stat (directory, &filestat) != 0) {
+ /* If the directory doesn't exist, try to create it. */
+ if (errno == ENOENT) {
+ if (mkdir (directory, 0777) != 0) {
+ fprintf (stderr, "Can't create directory: %s\n", directory);
+ exit (1);
+ }
+ } else {
+ fprintf (stderr, "Error calling stat() on directory: %s\n", directory);
+ exit (1);
+ }
+ } else if (!S_ISDIR (filestat.st_mode)) {
+ fprintf (stderr, "Can't create directory, already exists: %s\n",
+ directory);
+ exit (1);
+ }
+}
+
+
+static void
+expand_tzid_prefix (void)
+{
+ char *src, *dest;
+ char date_buf[16];
+ char ch1, ch2;
+ time_t t;
+ struct tm *tm;
+
+ /* Get today's date as a string in the format "YYYYMMDD". */
+ t = time (NULL);
+ tm = localtime (&t);
+ sprintf (date_buf, "%4i%02i%02i", tm->tm_year + 1900,
+ tm->tm_mon + 1, tm->tm_mday);
+
+ src = TZIDPrefix;
+ dest = TZIDPrefixExpanded;
+
+ while (ch1 = *src++) {
+
+ /* Look for a '%'. */
+ if (ch1 == '%') {
+ ch2 = *src++;
+
+ if (ch2 == 'D') {
+ /* '%D' gets expanded into the date string. */
+ strcpy (dest, date_buf);
+ dest += strlen (dest);
+ } else if (ch2 == '%') {
+ /* '%%' gets converted into one '%'. */
+ *dest++ = '%';
+ } else {
+ /* Anything else is output as is. */
+ *dest++ = '%';
+ *dest++ = ch2;
+ }
+ } else {
+ *dest++ = ch1;
+ }
+ }
+
+#if 0
+ printf ("TZID : %s\n", TZIDPrefix);
+ printf ("Expanded: %s\n", TZIDPrefixExpanded);
+#endif
+}
diff --git a/libkcal/libical/vzic-1.3/vzic-output.h b/libkcal/libical/vzic-1.3/vzic-output.h
new file mode 100644
index 000000000..4552ac7b9
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-output.h
@@ -0,0 +1,38 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+#ifndef _VZIC_OUTPUT_H_
+#define _VZIC_OUTPUT_H_
+
+#include <glib.h>
+
+void output_vtimezone_files (char *directory,
+ GArray *zone_data,
+ GHashTable *rule_data,
+ GHashTable *link_data,
+ int max_until_year);
+
+void ensure_directory_exists (char *directory);
+
+#endif /* _VZIC_OUTPUT_H_ */
diff --git a/libkcal/libical/vzic-1.3/vzic-parse.c b/libkcal/libical/vzic-1.3/vzic-parse.c
new file mode 100644
index 000000000..6ecdb7218
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-parse.c
@@ -0,0 +1,901 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "vzic.h"
+#include "vzic-parse.h"
+
+/* This is the maximum line length we allow. */
+#define MAX_LINE_LEN 1024
+
+/* The maximum number of fields on a line. */
+#define MAX_FIELDS 12
+
+
+typedef enum
+{
+ ZONE_ID = 0, /* The 'Zone' at the start of the line. */
+ ZONE_NAME = 1,
+ ZONE_GMTOFF = 2,
+ ZONE_RULES_SAVE = 3,
+ ZONE_FORMAT = 4,
+ ZONE_UNTIL_YEAR = 5,
+ ZONE_UNTIL_MONTH = 6,
+ ZONE_UNTIL_DAY = 7,
+ ZONE_UNTIL_TIME = 8
+} ZoneFieldNumber;
+
+
+typedef enum
+{
+ RULE_ID = 0, /* The 'Rule' at the start of the line. */
+ RULE_NAME = 1,
+ RULE_FROM = 2,
+ RULE_TO = 3,
+ RULE_TYPE = 4,
+ RULE_IN = 5,
+ RULE_ON = 6,
+ RULE_AT = 7,
+ RULE_SAVE = 8,
+ RULE_LETTER_S = 9
+} RuleFieldNumber;
+
+
+typedef enum
+{
+ LINK_ID = 0, /* The 'Link' at the start of the line. */
+ LINK_FROM = 1,
+ LINK_TO = 2
+} LinkFieldNumber;
+
+
+/* This struct contains information used while parsing the files, and is
+ passed to most parsing functions. */
+typedef struct _ParsingData ParsingData;
+struct _ParsingData
+{
+ /* This is the line being parsed. buffer is a copy that we break into fields
+ and sub-fields as it is parsed. */
+ char line[MAX_LINE_LEN];
+ char buffer[MAX_LINE_LEN];
+
+ /* These are pointers to the start of each field in buffer. */
+ char *fields[MAX_FIELDS];
+ int num_fields;
+
+ /* These are just for producing error messages. */
+ char *filename;
+ int line_number;
+
+
+ /* This is an array of ZoneData structs, 1 for each timezone read. */
+ GArray *zone_data;
+
+ /* This is a hash table of arrays of RuleData structs. As each Rule line is
+ read in, a new RuleData struct is filled in and appended to the
+ appropriate GArray in the hash table. */
+ GHashTable *rule_data;
+
+ /* A hash containing data on the Link lines. The keys are the timezones
+ where the link is from (i.e. the timezone we will be outputting anyway)
+ and the data is a GList of timezones to link to (where we will copy the
+ timezone data to). */
+ GHashTable *link_data;
+
+ int max_until_year;
+};
+
+
+/*
+ * Parsing functions, used when reading the Olson timezone data file.
+ */
+static void parse_fields (ParsingData *data);
+static gboolean parse_zone_line (ParsingData *data);
+static gboolean parse_zone_continuation_line (ParsingData *data);
+static gboolean parse_zone_common (ParsingData *data,
+ int offset);
+static void parse_rule_line (ParsingData *data);
+static void parse_link_line (ParsingData *data);
+
+static int parse_year (ParsingData *data,
+ char *field,
+ gboolean accept_only,
+ int only_value);
+static int parse_month (ParsingData *data,
+ char *field);
+static DayCode parse_day (ParsingData *data,
+ char *field,
+ int *day,
+ int *weekday);
+static int parse_weekday (ParsingData *data,
+ char *field);
+static int parse_time (ParsingData *data,
+ char *field,
+ TimeCode *time_code);
+static int parse_number (ParsingData *data,
+ char **num);
+static int parse_rules_save (ParsingData *data,
+ char *field,
+ char **rules);
+
+static void parse_coord (char *coord,
+ int len,
+ int *result);
+
+void
+parse_olson_file (char *filename,
+ GArray **zone_data,
+ GHashTable **rule_data,
+ GHashTable **link_data,
+ int *max_until_year)
+{
+ ParsingData data;
+ FILE *fp;
+ int zone_continues = 0;
+
+ *zone_data = g_array_new (FALSE, FALSE, sizeof (ZoneData));
+ *rule_data = g_hash_table_new (g_str_hash, g_str_equal);
+ *link_data = g_hash_table_new (g_str_hash, g_str_equal);
+
+ fp = fopen (filename, "r");
+ if (!fp) {
+ fprintf (stderr, "Couldn't open file: %s\n", filename);
+ exit (1);
+ }
+
+ data.filename = filename;
+ data.zone_data = *zone_data;
+ data.rule_data = *rule_data;
+ data.link_data = *link_data;
+ data.max_until_year = 0;
+
+ for (data.line_number = 0; ; data.line_number++) {
+ if (fgets (data.line, sizeof (data.line), fp) != data.line)
+ break;
+
+ strcpy (data.buffer, data.line);
+
+ parse_fields (&data);
+ if (data.num_fields == 0)
+ continue;
+
+ if (zone_continues) {
+ zone_continues = parse_zone_continuation_line (&data);
+ } else if (!strcmp (data.fields[0], "Zone")) {
+ zone_continues = parse_zone_line (&data);
+ } else if (!strcmp (data.fields[0], "Rule")) {
+ parse_rule_line (&data);
+ } else if (!strcmp (data.fields[0], "Link")) {
+ parse_link_line (&data);
+ } else if (!strcmp (data.fields[0], "Leap")) {
+ /* We don't care about Leap lines. */
+ } else {
+ fprintf (stderr, "%s:%i: Invalid line.\n%s\n", filename,
+ data.line_number, data.line);
+ exit (1);
+ }
+ }
+
+ if (ferror (fp)) {
+ fprintf (stderr, "Error reading file: %s\n", filename);
+ exit (1);
+ }
+
+ if (zone_continues) {
+ fprintf (stderr, "%s:%i: Zone continuation line expected.\n%s\n",
+ filename, data.line_number, data.line);
+ exit (1);
+ }
+
+ fclose (fp);
+
+#if 0
+ printf ("Max UNTIL year: %i\n", data.max_until_year);
+#endif
+ *max_until_year = data.max_until_year;
+}
+
+
+/* Converts the line into fields. */
+static void
+parse_fields (ParsingData *data)
+{
+ int i;
+ char *p, *s, ch;
+
+ /* Reset all fields to NULL. */
+ for (i = 0; i < MAX_FIELDS; i++)
+ data->fields[i] = 0;
+
+ data->num_fields = 0;
+ p = data->buffer;
+
+ for (;;) {
+ /* Skip whitespace. */
+ while (isspace (*p))
+ p++;
+
+ /* See if we have reached the end of the line or a comment. */
+ if (*p == '\0' || *p == '#')
+ break;
+
+ /* We must have another field, so save the start position. */
+ data->fields[data->num_fields++] = p;
+
+ /* Now find the end of the field. If the field contains '"' characters
+ they are removed and we have to move the rest of the chars back. */
+ s = p;
+ for (;;) {
+ ch = *p;
+ if (ch == '\0' || ch == '#') {
+ /* Don't move p on since this is the end of the line. */
+ *s = '\0';
+ break;
+ } else if (isspace (ch)) {
+ *s = '\0';
+ p++;
+ break;
+ } else if (ch == '"') {
+ p++;
+ for (;;) {
+ ch = *p;
+ if (ch == '\0') {
+ fprintf (stderr,
+ "%s:%i: Closing quote character ('\"') missing.\n%s\n",
+ data->filename, data->line_number, data->line);
+ exit (1);
+ } else if (ch == '"') {
+ p++;
+ break;
+ } else {
+ *s++ = ch;
+ }
+ p++;
+ }
+ } else {
+ *s++ = ch;
+ }
+ p++;
+ }
+ }
+
+#if 0
+ printf ("%i fields: ", data->num_fields);
+ for (i = 0; i < data->num_fields; i++)
+ printf ("'%s' ", data->fields[i]);
+ printf ("\n");
+#endif
+}
+
+
+static gboolean
+parse_zone_line (ParsingData *data)
+{
+ ZoneData zone;
+
+ /* All 5 fields up to FORMAT must be present. */
+ if (data->num_fields < 5 || data->num_fields > 9) {
+ fprintf (stderr, "%s:%i: Invalid Zone line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ zone.zone_name = g_strdup (data->fields[ZONE_NAME]);
+ zone.zone_line_data = g_array_new (FALSE, FALSE, sizeof (ZoneLineData));
+
+ g_array_append_val (data->zone_data, zone);
+
+ return parse_zone_common (data, 0);
+}
+
+
+static gboolean
+parse_zone_continuation_line (ParsingData *data)
+{
+ /* All 3 fields up to FORMAT must be present. */
+ if (data->num_fields < 3 || data->num_fields > 7) {
+ fprintf (stderr,
+ "%s:%i: Invalid Zone continuation line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ return parse_zone_common (data, -2);
+}
+
+
+static gboolean
+parse_zone_common (ParsingData *data,
+ int offset)
+{
+ ZoneData *zone;
+ ZoneLineData zone_line;
+ TimeCode time_code;
+
+ zone_line.stdoff_seconds = parse_time (data,
+ data->fields[ZONE_GMTOFF + offset],
+ &time_code);
+ zone_line.save_seconds = parse_rules_save (data,
+ data->fields[ZONE_RULES_SAVE + offset],
+ &zone_line.rules);
+
+ if (!VzicPureOutput) {
+ /* We round the UTC offsets to the nearest minute, to be compatible with
+ Outlook. This also works with -ve numbers, I think.
+ -56 % 60 = -59. -61 % 60 = -1. */
+ if (zone_line.stdoff_seconds >= 0)
+ zone_line.stdoff_seconds += 30;
+ else
+ zone_line.stdoff_seconds -= 29;
+ zone_line.stdoff_seconds -= zone_line.stdoff_seconds % 60;
+
+ if (zone_line.save_seconds >= 0)
+ zone_line.save_seconds += 30;
+ else
+ zone_line.save_seconds -= 29;
+ zone_line.save_seconds -= zone_line.save_seconds % 60;
+ }
+
+ zone_line.format = g_strdup (data->fields[ZONE_FORMAT + offset]);
+
+ if (data->num_fields - offset >= 6) {
+ zone_line.until_set = TRUE;
+ zone_line.until_year = parse_year (data,
+ data->fields[ZONE_UNTIL_YEAR + offset],
+ FALSE, 0);
+ zone_line.until_month = parse_month (data,
+ data->fields[ZONE_UNTIL_MONTH + offset]);
+ zone_line.until_day_code = parse_day (data,
+ data->fields[ZONE_UNTIL_DAY + offset],
+ &zone_line.until_day_number,
+ &zone_line.until_day_weekday);
+ zone_line.until_time_seconds = parse_time (data,
+ data->fields[ZONE_UNTIL_TIME + offset],
+ &zone_line.until_time_code);
+
+ /* We also want to know the maximum year used in any UNTIL value, so we
+ know where to expand all the infinite Rule data to. */
+ if (zone_line.until_year != YEAR_MAXIMUM
+ && zone_line.until_year != YEAR_MINIMUM)
+ data->max_until_year = MAX (data->max_until_year, zone_line.until_year);
+
+ } else {
+ zone_line.until_set = FALSE;
+ }
+
+ /* Append it to the last Zone, since that is the one we are currently
+ reading. */
+ zone = &g_array_index (data->zone_data, ZoneData, data->zone_data->len - 1);
+ g_array_append_val (zone->zone_line_data, zone_line);
+
+ return zone_line.until_set;
+}
+
+
+static void
+parse_rule_line (ParsingData *data)
+{
+ GArray *rule_array;
+ RuleData rule;
+ char *name;
+ TimeCode time_code;
+
+ /* All 10 fields must be present. */
+ if (data->num_fields != 10) {
+ fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ name = data->fields[RULE_NAME];
+
+ /* Create the GArray and add it to the hash table if it doesn't already
+ exist. */
+ rule_array = g_hash_table_lookup (data->rule_data, name);
+ if (!rule_array) {
+ rule_array = g_array_new (FALSE, FALSE, sizeof (RuleData));
+ g_hash_table_insert (data->rule_data, g_strdup (name), rule_array);
+ }
+
+ rule.from_year = parse_year (data, data->fields[RULE_FROM], FALSE, 0);
+ if (rule.from_year == YEAR_MAXIMUM) {
+ fprintf (stderr, "%s:%i: Invalid Rule FROM value: '%s'\n",
+ data->filename, data->line_number, data->fields[RULE_FROM]);
+ exit (1);
+ }
+
+ rule.to_year = parse_year (data, data->fields[RULE_TO], TRUE,
+ rule.from_year);
+ if (rule.to_year == YEAR_MINIMUM) {
+ fprintf (stderr, "%s:%i: Invalid Rule TO value: %s\n",
+ data->filename, data->line_number, data->fields[RULE_TO]);
+ exit (1);
+ }
+
+ if (!strcmp (data->fields[RULE_TYPE], "-"))
+ rule.type = NULL;
+ else {
+ printf ("Type: %s\n", data->fields[RULE_TYPE]);
+ rule.type = g_strdup (data->fields[RULE_TYPE]);
+ }
+
+ rule.in_month = parse_month (data, data->fields[RULE_IN]);
+ rule.on_day_code = parse_day (data, data->fields[RULE_ON],
+ &rule.on_day_number, &rule.on_day_weekday);
+ rule.at_time_seconds = parse_time (data, data->fields[RULE_AT],
+ &rule.at_time_code);
+ rule.save_seconds = parse_time (data, data->fields[RULE_SAVE], &time_code);
+
+ if (!strcmp (data->fields[RULE_LETTER_S], "-")) {
+ rule.letter_s = NULL;
+ } else {
+ rule.letter_s = g_strdup (data->fields[RULE_LETTER_S]);
+ }
+
+ rule.is_shallow_copy = FALSE;
+
+ g_array_append_val (rule_array, rule);
+}
+
+
+static void
+parse_link_line (ParsingData *data)
+{
+ char *from, *to, *old_from;
+ GList *zone_list;
+
+ /* We must have 3 fields for a Link. */
+ if (data->num_fields != 3) {
+ fprintf (stderr, "%s:%i: Invalid Rule line - %i fields.\n%s\n",
+ data->filename, data->line_number, data->num_fields,
+ data->line);
+ exit (1);
+ }
+
+ from = data->fields[LINK_FROM];
+ to = data->fields[LINK_TO];
+
+#if 0
+ printf ("LINK FROM: %s\tTO: %s\n", from, to);
+#endif
+
+ if (g_hash_table_lookup_extended (data->link_data, from,
+ (gpointer) &old_from,
+ (gpointer) &zone_list)) {
+ from = old_from;
+ } else {
+ from = g_strdup (from);
+ zone_list = NULL;
+ }
+
+ zone_list = g_list_prepend (zone_list, g_strdup (to));
+
+ g_hash_table_insert (data->link_data, from, zone_list);
+}
+
+
+static int
+parse_year (ParsingData *data,
+ char *field,
+ gboolean accept_only,
+ int only_value)
+{
+ int len, year = 0;
+ char *p;
+
+ if (!field) {
+ fprintf (stderr, "%s:%i: Missing year.\n%s\n", data->filename,
+ data->line_number, data->line);
+ exit (1);
+ }
+
+ len = strlen (field);
+ if (accept_only && !strncmp (field, "only", len))
+ return only_value;
+ if (len >= 2) {
+ if (!strncmp (field, "maximum", len))
+ return YEAR_MAXIMUM;
+ else if (!strncmp (field, "minimum", len))
+ return YEAR_MINIMUM;
+ }
+
+ for (p = field; *p; p++) {
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid year: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ year = year * 10 + *p - '0';
+ }
+
+ if (year < 1000 || year > 2037) {
+ fprintf (stderr, "%s:%i: Strange year: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ return year;
+}
+
+
+/* Parses a month name, returning 0 (Jan) to 11 (Dec). */
+static int
+parse_month (ParsingData *data,
+ char *field)
+{
+ static char* months[] = { "january", "february", "march", "april", "may",
+ "june", "july", "august", "september", "october",
+ "november", "december" };
+ char *p;
+ int len, i;
+
+ /* If the field is missing, it must be the optional UNTIL month, so we return
+ 0 for January. */
+ if (!field)
+ return 0;
+
+ for (p = field, len = 0; *p; p++, len++) {
+ *p = tolower (*p);
+ }
+
+ for (i = 0; i < 12; i++) {
+ if (!strncmp (field, months[i], len))
+ return i;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid month: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a day specifier, returning a code representing the type of match
+ together with a day of the month and a weekday number (0=Sun). */
+static DayCode
+parse_day (ParsingData *data,
+ char *field,
+ int *day,
+ int *weekday)
+{
+ char *day_part, *p;
+ DayCode day_code;
+
+ if (!field) {
+ *day = 1;
+ return DAY_SIMPLE;
+ }
+
+ *day = *weekday = 0;
+
+ if (!strncmp (field, "last", 4)) {
+ *weekday = parse_weekday (data, field + 4);
+ /* We set the day to the end of the month to make sorting Rules easy. */
+ *day = 31;
+ return DAY_LAST_WEEKDAY;
+ }
+
+ day_part = field;
+ day_code = DAY_SIMPLE;
+
+ for (p = field; *p; p++) {
+ if (*p == '<' || *p == '>') {
+ if (*(p + 1) == '=') {
+ day_code = (*p == '<') ? DAY_WEEKDAY_ON_OR_BEFORE
+ : DAY_WEEKDAY_ON_OR_AFTER;
+ *p = '\0';
+ *weekday = parse_weekday (data, field);
+ day_part = p + 2;
+ break;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+ }
+
+ for (p = day_part; *p; p++) {
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ *day = *day * 10 + *p - '0';
+ }
+
+ if (*day < 1 || *day > 31) {
+ fprintf (stderr, "%s:%i: Invalid day: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ return day_code;
+}
+
+
+/* Parses a weekday name, returning 0 (Sun) to 6 (Sat). */
+static int
+parse_weekday (ParsingData *data,
+ char *field)
+{
+ static char* weekdays[] = { "sunday", "monday", "tuesday", "wednesday",
+ "thursday", "friday", "saturday" };
+ char *p;
+ int len, i;
+
+ for (p = field, len = 0; *p; p++, len++) {
+ *p = tolower (*p);
+ }
+
+ for (i = 0; i < 7; i++) {
+ if (!strncmp (field, weekdays[i], len))
+ return i;
+ }
+
+ fprintf (stderr, "%s:%i: Invalid weekday: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a time (hour + minute + second) and returns the result in seconds,
+ together with a time code specifying whether it is Wall clock time,
+ local standard time, or universal time.
+ The time can start with a '-' in which case it will be negative. */
+static int
+parse_time (ParsingData *data,
+ char *field,
+ TimeCode *time_code)
+{
+ char *p;
+ int hours = 0, minutes = 0, seconds = 0, result, negative = 0;
+
+ if (!field) {
+ *time_code = TIME_WALL;
+ return 0;
+ }
+
+ p = field;
+ if (*p == '-') {
+ p++;
+ negative = 1;
+ }
+
+ hours = parse_number (data, &p);
+
+ if (*p == ':') {
+ p++;
+ minutes = parse_number (data, &p);
+
+ if (*p == ':') {
+ p++;
+ seconds = parse_number (data, &p);
+ }
+ }
+
+ if (hours < 0 || hours > 24
+ || minutes < 0 || minutes > 59
+ || seconds < 0 || seconds > 59
+ || (hours == 24 && (minutes != 0 || seconds != 0))) {
+ fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+ }
+
+ if (hours == 24) {
+ hours = 23;
+ minutes = 59;
+ seconds = 59;
+ }
+
+#if 0
+ printf ("Time: %s -> %i:%02i:%02i\n", field, hours, minutes, seconds);
+#endif
+
+ result = hours * 3600 + minutes * 60 + seconds;
+ if (negative)
+ result = -result;
+
+ if (*p == '\0') {
+ *time_code = TIME_WALL;
+ return result;
+ }
+
+ if (*(p + 1) == '\0') {
+ if (*p == 'w') {
+ *time_code = TIME_WALL;
+ return result;
+ } else if (*p == 's') {
+ *time_code = TIME_STANDARD;
+ return result;
+ } else if (*p == 'u' || *p == 'g' || *p == 'z') {
+ *time_code = TIME_UNIVERSAL;
+ return result;
+ }
+ }
+
+ fprintf (stderr, "%s:%i: Invalid time: %s\n%s\n", data->filename,
+ data->line_number, field, data->line);
+ exit (1);
+}
+
+
+/* Parses a simple number and returns the result. The pointer argument
+ is moved to the first character after the number. */
+static int
+parse_number (ParsingData *data,
+ char **num)
+{
+ char *p;
+ int result;
+
+ p = *num;
+
+#if 0
+ printf ("In parse_number p:%s\n", p);
+#endif
+
+ if (*p < '0' || *p > '9') {
+ fprintf (stderr, "%s:%i: Invalid number: %s\n%s\n", data->filename,
+ data->line_number, *num, data->line);
+ exit (1);
+ }
+
+ result = *p++ - '0';
+
+ while (*p >= '0' && *p <= '9')
+ result = result * 10 + *p++ - '0';
+
+ *num = p;
+ return result;
+}
+
+
+static int
+parse_rules_save (ParsingData *data,
+ char *field,
+ char **rules)
+{
+ TimeCode time_code;
+
+ *rules = NULL;
+
+ /* Check for just "-". */
+ if (field[0] == '-' && field[1] == '\0')
+ return 0;
+
+ /* Check for a time to add to local standard time. We don't care about a
+ time code here, since it is just an offset. */
+ if (*field == '-' || (*field >= '0' && *field <= '9'))
+ return parse_time (data, field, &time_code);
+
+ /* It must be a rules name. */
+ *rules = g_strdup (field);
+ return 0;
+}
+
+
+
+
+
+GHashTable*
+parse_zone_tab (char *filename)
+{
+ GHashTable *zones_hash;
+ ZoneDescription *zone_desc;
+ FILE *fp;
+ char buf[4096];
+ gchar **fields, *zone_name, *latitude, *longitude, *p;
+
+
+ fp = fopen (filename, "r");
+ if (!fp) {
+ fprintf (stderr, "Couldn't open file: %s\n", filename);
+ exit (1);
+ }
+
+ zones_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ while (fgets (buf, sizeof(buf), fp)) {
+ if (*buf == '#') continue;
+
+ g_strchomp (buf);
+ fields = g_strsplit (buf,"\t", 4);
+
+ if (strlen (fields[0]) != 2) {
+ fprintf (stderr, "Invalid zone description line: %s\n", buf);
+ exit (1);
+ }
+
+ zone_name = g_strdup (fields[2]);
+
+ zone_desc = g_new (ZoneDescription, 1);
+ zone_desc->country_code[0] = fields[0][0];
+ zone_desc->country_code[1] = fields[0][1];
+ zone_desc->comment = (fields[3] && fields[3][0]) ? g_strdup (fields[3])
+ : NULL;
+
+ /* Now parse the latitude and longitude. */
+ latitude = fields[1];
+ longitude = latitude + 1;
+ while (*longitude != '+' && *longitude != '-')
+ longitude++;
+
+ parse_coord (latitude, longitude - latitude, zone_desc->latitude);
+ parse_coord (longitude, strlen (longitude), zone_desc->longitude);
+
+ g_hash_table_insert (zones_hash, zone_name, zone_desc);
+
+#if 0
+ g_print ("Found zone: %s %i %02i %02i,%i %02i %02i\n", zone_name,
+ zone_desc->latitude[0], zone_desc->latitude[1],
+ zone_desc->latitude[2],
+ zone_desc->longitude[0], zone_desc->longitude[1],
+ zone_desc->longitude[2]);
+#endif
+ }
+
+ fclose (fp);
+
+ return zones_hash;
+}
+
+
+static void
+parse_coord (char *coord,
+ int len,
+ int *result)
+{
+ int degrees = 0, minutes = 0, seconds = 0;
+
+ if (len == 5)
+ sscanf (coord + 1, "%2d%2d", &degrees, &minutes);
+ else if (len == 6)
+ sscanf (coord + 1, "%3d%2d", &degrees, &minutes);
+ else if (len == 7)
+ sscanf (coord + 1, "%2d%2d%2d", &degrees, &minutes, &seconds);
+ else if (len == 8)
+ sscanf (coord + 1, "%3d%2d%2d", &degrees, &minutes, &seconds);
+ else {
+ fprintf (stderr, "Invalid coordinate: %s\n", coord);
+ exit (1);
+ }
+
+ if (coord[0] == '-')
+ degrees = -degrees;
+
+ result[0] = degrees;
+ result[1] = minutes;
+ result[2] = seconds;
+}
+
diff --git a/libkcal/libical/vzic-1.3/vzic-parse.h b/libkcal/libical/vzic-1.3/vzic-parse.h
new file mode 100644
index 000000000..fde3b2c71
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-parse.h
@@ -0,0 +1,38 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+#ifndef _VZIC_PARSE_H_
+#define _VZIC_PARSE_H_
+
+#include <glib.h>
+
+void parse_olson_file (char *filename,
+ GArray **zone_data,
+ GHashTable **rule_data,
+ GHashTable **link_data,
+ int *max_until_year);
+
+GHashTable* parse_zone_tab (char *filename);
+
+#endif /* _VZIC_PARSE_H_ */
diff --git a/libkcal/libical/vzic-1.3/vzic-test.pl b/libkcal/libical/vzic-1.3/vzic-test.pl
new file mode 100755
index 000000000..cebac79fe
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic-test.pl
@@ -0,0 +1,164 @@
+#!/usr/bin/perl -w
+
+#
+# Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+# files compatible with the iCalendar specification (RFC2445).
+#
+# Copyright (C) 2001 Ximian, Inc.
+# Copyright (C) 2003 Damon Chaplin.
+#
+# Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+#
+
+#
+# This outputs an iCalendar file containing one event in each timezone,
+# as well as all the VTIMEZONEs. We use it for testing compatability with
+# other iCalendar apps like Outlook, by trying to import it there.
+#
+# Currently we have 377 timezones (with tzdata2001d).
+#
+
+# Set this to the toplevel directory of the VTIMEZONE files.
+$ZONEINFO_DIR = "/home/damon/src/zoneinfo";
+
+$output_file = "calendar.ics";
+
+
+# Save this so we can restore it later.
+$input_record_separator = $/;
+
+chdir $ZONEINFO_DIR
+ || die "Can't cd to $ZONEINFO_DIR";
+
+# Create the output file, to contain all the VEVENTs & VTIMEZONEs.
+open (OUTPUTFILE, ">$output_file")
+ || die "Can't create file: $output_file";
+
+# Output the standard header.
+ print OUTPUTFILE <<EOF;
+BEGIN:VCALENDAR
+PRODID:-//Ximian//NONSGML Vzic Test//EN
+VERSION:2.0
+METHOD:PUBLISH
+EOF
+
+$zone_num = 0;
+
+# 365 days in a non-leap year.
+@days_in_month = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
+
+foreach $file (`find -name "*.ics"`) {
+ # Get rid of './' at start and whitespace at end.
+ $file =~ s/^\.\///;
+ $file =~ s/\s+$//;
+
+ if ($file eq $output_file) {
+ next;
+ }
+
+# print "File: $file\n";
+
+ # Get the VTIMEZONE data.
+ open (ZONEFILE, "$file")
+ || die "Can't open file: $ZONEINFO_DIR/$file";
+ undef $/;
+ $vtimezone = <ZONEFILE>;
+ $/ = $input_record_separator;
+ close (ZONEFILE);
+
+ # Strip the stuff before and after the VTIMEZONE component
+ $vtimezone =~ s/^.*BEGIN:VTIMEZONE/BEGIN:VTIMEZONE/s;
+ $vtimezone =~ s/END:VTIMEZONE.*$/END:VTIMEZONE\n/s;
+
+ print OUTPUTFILE $vtimezone;
+
+ # Find the TZID.
+ $vtimezone =~ m/TZID:(.*)/;
+ $tzid = $1;
+# print "TZID: $tzid\n";
+
+ # Find the location.
+ $file =~ m/(.*)\.ics/;
+ $location = $1;
+# print "LOCATION: $location\n";
+
+ # Try to find the current UTC offset that Outlook will use.
+ # If there is an RRULE, we look for the first 2 TZOFFSETTO properties,
+ # else we just get the first one.
+ if ($vtimezone =~ m/RRULE/) {
+ $vtimezone =~ m/TZOFFSETTO:([+-]?\d+)/;
+ $tzoffsetto = $1;
+ $vtimezone =~ m/TZOFFSETFROM:([+-]?\d+)/;
+ $tzoffsetfrom = $1;
+ $tzoffset = "$tzoffsetfrom/$tzoffsetto";
+ } else {
+ $vtimezone =~ m/TZOFFSETTO:([+-]?\d+)/s;
+ $tzoffset = $1;
+ }
+# print "TZOFFSET: $tzoffset\n";
+
+ # We put each event on a separate day in 2001 and Jan 2002.
+ $day_num = $zone_num;
+ if ($day_num >= 365) {
+ $year = 2002;
+ $day_num -= 365;
+ } else {
+ $year = 2001;
+ }
+ $month = -1;
+ for ($i = 0; $i < 12; $i++) {
+ if ($day_num < $days_in_month[$i]) {
+ $month = $i;
+ last;
+ }
+ $day_num -= $days_in_month[$i]
+ }
+ if ($month == -1) {
+ die "month = -1";
+ }
+
+ $month++;
+ $day_num++;
+ $date = sprintf ("%i%02i%02i", $year, $month, $day_num);
+# print "Date: $date\n";
+
+ # Output a VEVENT using the timezone.
+ print OUTPUTFILE <<EOF;
+BEGIN:VEVENT
+UID:vzic-test-${zone_num}
+DTSTAMP:20010101T000000Z
+DTSTART;TZID=${tzid}:${date}T120000
+DTEND;TZID=${tzid}:${date}T130000
+RRULE:FREQ=MONTHLY;BYMONTHDAY=${day_num}
+SUMMARY:($tzoffset) ${location} 12:00-13:00 UTC
+SEQUENCE:1
+END:VEVENT
+EOF
+
+ $zone_num++;
+
+ # Use this to stop after a certain number.
+# last if ($zone_num == 100);
+}
+
+# Output the standard footer.
+ print OUTPUTFILE <<EOF;
+END:VCALENDAR
+EOF
+
+close (OUTPUTFILE);
+
diff --git a/libkcal/libical/vzic-1.3/vzic.c b/libkcal/libical/vzic-1.3/vzic.c
new file mode 100644
index 000000000..1108d8777
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic.c
@@ -0,0 +1,320 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "vzic.h"
+#include "vzic-parse.h"
+#include "vzic-dump.h"
+#include "vzic-output.h"
+
+
+/*
+ * Global command-line options.
+ */
+
+/* By default we output Outlook-compatible output. If --pure is used we
+ output pure output, with no changes to be compatible with Outlook. */
+gboolean VzicPureOutput = FALSE;
+
+gboolean VzicDumpOutput = FALSE;
+gboolean VzicDumpChanges = FALSE;
+gboolean VzicDumpZoneNamesAndCoords = TRUE;
+gboolean VzicDumpZoneTranslatableStrings= TRUE;
+gboolean VzicNoRRules = FALSE;
+gboolean VzicNoRDates = FALSE;
+char* VzicOutputDir = "zoneinfo";
+char* VzicUrlPrefix = NULL;
+char* VzicOlsonDir = OLSON_DIR;
+
+GList* VzicTimeZoneNames = NULL;
+
+static void convert_olson_file (char *olson_file);
+
+static void usage (void);
+
+static void free_zone_data (GArray *zone_data);
+static void free_rule_array (gpointer key,
+ gpointer value,
+ gpointer data);
+static void free_link_data (gpointer key,
+ gpointer value,
+ gpointer data);
+
+
+int
+main (int argc,
+ char *argv[])
+{
+ int i;
+ char directory[PATHNAME_BUFFER_SIZE];
+ char filename[PATHNAME_BUFFER_SIZE];
+ GHashTable *zones_hash;
+
+ /*
+ * Command-Line Option Parsing.
+ */
+ for (i = 1; i < argc; i++) {
+ /*
+ * User Options.
+ */
+
+ /* --pure: Output the perfect VCALENDAR data, which Outlook won't parse
+ as it has problems with certain iCalendar constructs. */
+ if (!strcmp (argv[i], "--pure"))
+ VzicPureOutput = TRUE;
+
+ /* --output-dir: specify where to output all the files beneath. The
+ default is the current directory. */
+ else if (argc > i + 1 && !strcmp (argv[i], "--output-dir"))
+ VzicOutputDir = argv[++i];
+
+ /* --url-prefix: Used as the base for the TZURL property in each
+ VTIMEZONE. The default is to not output TZURL properties. */
+ else if (argc > i + 1 && !strcmp (argv[i], "--url-prefix")) {
+ int length;
+ VzicUrlPrefix = argv[++i];
+ /* remove the trailing '/' if there is one */
+ length = strlen (VzicUrlPrefix);
+ if (VzicUrlPrefix[length - 1] == '/')
+ VzicUrlPrefix[length - 1] = '\0';
+ }
+
+ else if (argc > i + 1 && !strcmp (argv[i], "--olson-dir")) {
+ VzicOlsonDir = argv[++i];
+ }
+
+ /*
+ * Debugging Options.
+ */
+
+ /* --dump: Dump the Rule and Zone data that we parsed from the Olson
+ timezone files. This is used to test the parsing code. */
+ else if (!strcmp (argv[i], "--dump"))
+ VzicDumpOutput = TRUE;
+
+ /* --dump-changes: Dumps a list of times when each timezone changed,
+ and the new local time offset from UTC. */
+ else if (!strcmp (argv[i], "--dump-changes"))
+ VzicDumpChanges = TRUE;
+
+ /* --no-rrules: Don't output RRULE properties in the VTIMEZONEs. Instead
+ it will just output RDATEs for each year up to a certain year. */
+ else if (!strcmp (argv[i], "--no-rrules"))
+ VzicNoRRules = TRUE;
+
+ /* --no-rdates: Don't output multiple RDATEs in a single VTIMEZONE
+ component. Instead they will be output separately. */
+ else if (!strcmp (argv[i], "--no-rdates"))
+ VzicNoRDates = TRUE;
+
+ else
+ usage ();
+ }
+
+ /*
+ * Create any necessary directories.
+ */
+ ensure_directory_exists (VzicOutputDir);
+
+ if (VzicDumpOutput) {
+ /* Create the directories for the dump output, if they don't exist. */
+ sprintf (directory, "%s/ZonesVzic", VzicOutputDir);
+ ensure_directory_exists (directory);
+ sprintf (directory, "%s/RulesVzic", VzicOutputDir);
+ ensure_directory_exists (directory);
+ }
+
+ if (VzicDumpChanges) {
+ /* Create the directory for the changes output, if it doesn't exist. */
+ sprintf (directory, "%s/ChangesVzic", VzicOutputDir);
+ ensure_directory_exists (directory);
+ }
+
+ /*
+ * Convert the Olson timezone files.
+ */
+ convert_olson_file ("africa");
+ convert_olson_file ("antarctica");
+ convert_olson_file ("asia");
+ convert_olson_file ("australasia");
+ convert_olson_file ("europe");
+ convert_olson_file ("northamerica");
+ convert_olson_file ("southamerica");
+
+ /* These are backwards-compatability and weird stuff. */
+#if 0
+ convert_olson_file ("backward");
+ convert_olson_file ("etcetera");
+ convert_olson_file ("leapseconds");
+ convert_olson_file ("pacificnew");
+ convert_olson_file ("solar87");
+ convert_olson_file ("solar88");
+ convert_olson_file ("solar89");
+#endif
+
+ /* This doesn't really do anything and it messes up vzic-dump.pl so we
+ don't bother. */
+#if 0
+ convert_olson_file ("factory");
+#endif
+
+ /* This is old System V stuff, which we don't currently support since it
+ uses 'min' as a Rule FROM value which messes up our algorithm, making
+ it too slow and use too much memory. */
+#if 0
+ convert_olson_file ("systemv");
+#endif
+
+ /* Output the timezone names and coordinates in a zone.tab file, and
+ the translatable strings to feed to gettext. */
+ if (VzicDumpZoneNamesAndCoords) {
+ sprintf (filename, "%s/zone.tab", VzicOlsonDir);
+ zones_hash = parse_zone_tab (filename);
+
+ dump_time_zone_names (VzicTimeZoneNames, VzicOutputDir, zones_hash);
+ }
+
+ return 0;
+}
+
+
+static void
+convert_olson_file (char *olson_file)
+{
+ char input_filename[PATHNAME_BUFFER_SIZE];
+ GArray *zone_data;
+ GHashTable *rule_data, *link_data;
+ char dump_filename[PATHNAME_BUFFER_SIZE];
+ ZoneData *zone;
+ int i, max_until_year;
+
+ sprintf (input_filename, "%s/%s", VzicOlsonDir, olson_file);
+
+ parse_olson_file (input_filename, &zone_data, &rule_data, &link_data,
+ &max_until_year);
+
+ if (VzicDumpOutput) {
+ sprintf (dump_filename, "%s/ZonesVzic/%s", VzicOutputDir, olson_file);
+ dump_zone_data (zone_data, dump_filename);
+
+ sprintf (dump_filename, "%s/RulesVzic/%s", VzicOutputDir, olson_file);
+ dump_rule_data (rule_data, dump_filename);
+ }
+
+ output_vtimezone_files (VzicOutputDir, zone_data, rule_data, link_data,
+ max_until_year);
+
+ free_zone_data (zone_data);
+ g_hash_table_foreach (rule_data, free_rule_array, NULL);
+ g_hash_table_destroy (rule_data);
+ g_hash_table_foreach (link_data, free_link_data, NULL);
+ g_hash_table_destroy (link_data);
+}
+
+
+static void
+usage (void)
+{
+ fprintf (stderr, "Usage: vzic [--dump] [--dump-changes] [--no-rrules] [--no-rdates] [--pure] [--output-dir <directory>] [--url-prefix <url>] [--olson-dir <directory>]\n");
+
+ exit (1);
+}
+
+
+
+
+/*
+ * Functions to free the data structures.
+ */
+
+static void
+free_zone_data (GArray *zone_data)
+{
+ ZoneData *zone;
+ ZoneLineData *zone_line;
+ int i, j;
+
+ for (i = 0; i < zone_data->len; i++) {
+ zone = &g_array_index (zone_data, ZoneData, i);
+
+ g_free (zone->zone_name);
+
+ for (j = 0; j < zone->zone_line_data->len; j++) {
+ zone_line = &g_array_index (zone->zone_line_data, ZoneLineData, j);
+
+ g_free (zone_line->rules);
+ g_free (zone_line->format);
+ }
+
+ g_array_free (zone->zone_line_data, TRUE);
+ }
+
+ g_array_free (zone_data, TRUE);
+}
+
+
+static void
+free_rule_array (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ char *name = key;
+ GArray *rule_array = value;
+ RuleData *rule;
+ int i;
+
+ for (i = 0; i < rule_array->len; i++) {
+ rule = &g_array_index (rule_array, RuleData, i);
+
+ if (!rule->is_shallow_copy) {
+ g_free (rule->type);
+ g_free (rule->letter_s);
+ }
+ }
+
+ g_array_free (rule_array, TRUE);
+
+ g_free (name);
+}
+
+
+static void
+free_link_data (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GList *link = data;
+
+ g_free (key);
+
+ while (link) {
+ g_free (link->data);
+ link = link->next;
+ }
+
+ g_list_free (data);
+}
+
diff --git a/libkcal/libical/vzic-1.3/vzic.h b/libkcal/libical/vzic-1.3/vzic.h
new file mode 100644
index 000000000..afa84d67b
--- /dev/null
+++ b/libkcal/libical/vzic-1.3/vzic.h
@@ -0,0 +1,196 @@
+/*
+ * Vzic - a program to convert Olson timezone database files into VZTIMEZONE
+ * files compatible with the iCalendar specification (RFC2445).
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ * Copyright (C) 2003 Damon Chaplin.
+ *
+ * Author: Damon Chaplin <damon@gnome.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 02111-1307, USA.
+ */
+
+#ifndef _VZIC_H_
+#define _VZIC_H_
+
+#include <glib.h>
+
+
+/*
+ * Global command-line options.
+ */
+
+/* By default we output Outlook-compatible output. If --pure is used we output
+ pure output, with no changes to be compatible with Outlook. */
+extern gboolean VzicPureOutput;
+
+extern gboolean VzicDumpOutput;
+extern gboolean VzicDumpChanges;
+extern gboolean VzicDumpZoneNamesAndCoords;
+extern gboolean VzicDumpZoneTranslatableStrings;
+extern gboolean VzicNoRRules;
+extern gboolean VzicNoRDates;
+extern char* VzicUrlPrefix;
+
+extern GList* VzicTimeZoneNames;
+
+/* The minimum & maximum years we can use. */
+#define YEAR_MINIMUM G_MININT
+#define YEAR_MAXIMUM G_MAXINT
+
+/* The maximum size of any complete pathname. */
+#define PATHNAME_BUFFER_SIZE 1024
+
+/* Days can be expressed either as a simple month day number, 1-31, or a rule
+ such as the last Sunday, or the first Monday on or after the 8th. */
+typedef enum
+{
+ DAY_SIMPLE,
+ DAY_WEEKDAY_ON_OR_AFTER,
+ DAY_WEEKDAY_ON_OR_BEFORE,
+ DAY_LAST_WEEKDAY
+} DayCode;
+
+
+/* Times can be given either as universal time (UTC), local standard time
+ (without daylight-saving adjustments) or wall clock time (local standard
+ time plus daylight-saving adjustments, i.e. what you would see on a clock
+ on the wall!). */
+typedef enum
+{
+ TIME_WALL,
+ TIME_STANDARD,
+ TIME_UNIVERSAL
+} TimeCode;
+
+
+/* This represents one timezone, e.g. "Africa/Algiers".
+ It contains the timezone name, and an array of ZoneLineData structs which
+ hold data from each Zone line, including the continuation lines. */
+typedef struct _ZoneData ZoneData;
+struct _ZoneData
+{
+ char *zone_name;
+
+ /* An array of ZoneLineData, one for each Zone & Zone continuation line
+ read in. */
+ GArray *zone_line_data;
+};
+
+
+typedef struct _ZoneLineData ZoneLineData;
+struct _ZoneLineData
+{
+ /* The amount of time to add to UTC to get local standard time for the
+ current time range, in seconds. */
+ int stdoff_seconds;
+
+ /* Either rules is set to the name of a set of rules, or rules is NULL and
+ save is set to the time to add to local standard time to get wall time, in
+ seconds. If save is 0 as well, then standard time always applies. */
+ char *rules;
+ int save_seconds;
+
+ /* The format to use for the abbreviated timezone name, e.g. WE%sT.
+ The %s is replaced by variable part of the name. (See the letter_s field
+ in the RuleData struct below). */
+ char *format;
+
+ /* TRUE if an UNTIL time is given. */
+ gboolean until_set;
+
+ /* The UNTIL year, e.g. 2000. */
+ int until_year;
+
+ /* The UNTIL month 0 (Jan) to 11 (Dec). */
+ int until_month;
+
+ /* The UNTIL day, either a simple month day number, 1-31, or a rule such as
+ the last Sunday, or the first Monday on or after the 8th. */
+ DayCode until_day_code;
+ int until_day_number; /* 1 to 31. */
+ int until_day_weekday; /* 0 (Sun) to 6 (Sat). */
+
+ /* The UNTIL time, in seconds from midnight. The code specifies whether the
+ time is a wall clock time, local standard time, or universal time. */
+ int until_time_seconds;
+ TimeCode until_time_code;
+};
+
+
+typedef struct _RuleData RuleData;
+struct _RuleData
+{
+ /* The first year that the rule applies to, e.g. 1996.
+ Can also be YEAR_MINIMUM. */
+ int from_year;
+
+ /* The last year that the rule applies to, e.g. 1996.
+ Can also be YEAR_MAXIMUM. */
+ int to_year;
+
+ /* A string used to only match certain years between from and to.
+ The rule only applies to the years which match. If type is NULL the rule
+ applies to all years betweeen from and to.
+ zic uses an external program called yearistype to check the string.
+ Currently it is not used in the Olson database. */
+ char *type;
+
+ /* The month of the rule 0 (Jan) to 11 (Dec). */
+ int in_month;
+
+ /* The day, either a simple month day number, 1-31, or a rule such as
+ the last Sunday, or the first Monday on or after the 8th. */
+ DayCode on_day_code;
+ int on_day_number;
+ int on_day_weekday; /* 0 (Sun) to 6 (Sat). */
+
+ /* The time, in seconds from midnight. The code specifies whether the
+ time is a wall clock time, local standard time, or universal time. */
+ int at_time_seconds;
+ TimeCode at_time_code;
+
+ /* The amount of time to add to local standard time when the rule is in
+ effect, in seconds. If this is not 0 then it must be a daylight-saving
+ time. */
+ int save_seconds;
+
+ /* The letter(s) to use as the variable part in the abbreviated timezone
+ name. If this is NULL then no variable part is used. (See the format field
+ in the ZoneLineData struct above.) */
+ char *letter_s;
+
+
+ /* This is set to TRUE if this element is a shallow copy of another one,
+ in which case we don't free any of the fields. */
+ gboolean is_shallow_copy;
+};
+
+
+typedef struct _ZoneDescription ZoneDescription;
+struct _ZoneDescription
+{
+ /* 2-letter ISO 3166 country code. */
+ char country_code[2];
+
+ /* latitude and longitude in degrees, minutes & seconds. The degrees value
+ holds the sign of the entire latitude/longitude. */
+ int latitude[3];
+ int longitude[3];
+
+ char *comment;
+};
+
+#endif /* _VZIC_H_ */