diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 (patch) | |
tree | 67208f7c145782a7e90b123b982ca78d88cc2c87 /libkcal/libical/vzic-1.3 | |
download | tdepim-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/ChangeLog | 57 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/Makefile | 83 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/Makefile.org | 89 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/README | 202 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/test-vzic.c | 422 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-dump.c | 409 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-dump.h | 58 | ||||
-rwxr-xr-x | libkcal/libical/vzic-1.3/vzic-dump.pl | 222 | ||||
-rwxr-xr-x | libkcal/libical/vzic-1.3/vzic-merge.pl | 127 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-output.c | 2325 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-output.h | 38 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-parse.c | 901 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic-parse.h | 38 | ||||
-rwxr-xr-x | libkcal/libical/vzic-1.3/vzic-test.pl | 164 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic.c | 320 | ||||
-rw-r--r-- | libkcal/libical/vzic-1.3/vzic.h | 196 |
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", °rees, &minutes); + else if (len == 6) + sscanf (coord + 1, "%3d%2d", °rees, &minutes); + else if (len == 7) + sscanf (coord + 1, "%2d%2d%2d", °rees, &minutes, &seconds); + else if (len == 8) + sscanf (coord + 1, "%3d%2d%2d", °rees, &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_ */ |