Imported Upstream version 1.4.1
[platform/upstream/syncevolution.git] / src / syncevo / icaltz-util.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * Authors : 
4  *  Chenthill Palanisamy <pchenthill@novell.com>
5  *
6  * Copyright 2007, Novell, Inc.
7  *
8  * This program is free software; you can redistribute it and/or 
9  * modify it under the terms of version 2 of the GNU Lesser General Public 
10  * License as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 
20  * Boston, MA 02110-1301, USA.
21  */
22
23 /*
24  * Compile with
25  * gcc -c -Wall -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY  -DICALTZ_UTIL_MAIN -DHAVE_UNISTD_H  -DHAVE_ENDIAN_H -DHAVE_BYTESWAP_H $(pkg-config --cflags --libs libical) -o icaltz-util icaltz-util.c
26  * to get an utility which will print the VTIMEZONE definition of
27  * a certain location, for example "Europe/Berlin".
28  *
29  * With -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY the
30  * icaltzutil_get_zone_directory() from libical will be used.
31  */
32
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 #include <string.h>
37
38 #ifdef ICALTZ_UTIL_MAIN
39 #include <stdio.h>
40 #endif
41
42 #ifdef EVOLUTION_ICAL_COMPATIBILITY
43 # include "eds_abi_wrapper.h"
44 # else
45 # include <libical/icaltimezone.h>
46 #endif
47
48 #ifdef HAVE_STDINT_H
49 #include <stdint.h>
50 #endif
51
52 #ifdef HAVE_UNISTD_H
53 #include <unistd.h>
54 #endif
55
56 #if defined(sun) && defined(__SVR4)
57 #include <sys/types.h>
58 #include <sys/byteorder.h>
59 #else
60 # ifdef HAVE_BYTESWAP_H
61 #  include <byteswap.h>
62 # endif
63 # ifdef HAVE_ENDIAN_H
64 #  include <endian.h>
65 # else
66 # ifdef HAVE_SYS_ENDIAN_H
67 #  include <sys/endian.h>
68 #  ifdef bswap32
69 #   define bswap_32 bswap32
70 #  else
71 #   define bswap_32 swap32
72 #  endif
73 # endif
74 # endif
75 #endif
76
77 #ifdef _MSC_VER
78 #if !defined(HAVE_BYTESWAP_H) && !defined(HAVE_SYS_ENDIAN_H) && !defined(HAVE_ENDIAN_H)
79 #define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff)
80 #define bswap_32(x) (((x) << 24) & 0xff000000)  \
81                     | (((x) << 8) & 0xff0000)   \
82                     | (((x) >> 8) & 0xff00)     \
83                     | (((x) >> 24) & 0xff )
84 #define bswap_64(x) ((((x) & 0xff00000000000000ull) >> 56) \
85                     | (((x) & 0x00ff000000000000ull) >> 40) \
86                     | (((x) & 0x0000ff0000000000ull) >> 24) \
87                     | (((x) & 0x000000ff00000000ull) >> 8) \
88                     | (((x) & 0x00000000ff000000ull) << 8) \
89                     | (((x) & 0x0000000000ff0000ull) << 24) \
90                     | (((x) & 0x000000000000ff00ull) << 40) \
91                     | (((x) & 0x00000000000000ffull) << 56))
92 #endif
93 #include <io.h>
94 #endif
95
96 #if defined(__APPLE__)
97 #define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff)
98 #define bswap_32 __builtin_bswap32
99 #define bswap_64 __builtin_bswap64
100 #endif
101
102 #ifndef PATH_MAX
103 #define PATH_MAX 512
104 #endif
105
106 #ifndef F_OK
107 #define F_OK 0
108 #endif
109
110 #ifndef R_OK
111 #define R_OK 4
112 #endif
113
114 #include <limits.h>
115 #include <time.h>
116 #include <stdlib.h>
117 #ifdef HAVE_UNISTD_H
118 #include <unistd.h>
119 #endif
120 #include <libical/icalerror.h>
121 #include "icaltz-util.h"
122
123 typedef struct 
124 {
125         char    ttisgmtcnt [4]; 
126         char    ttisstdcnt[4];  
127         char    leapcnt[4];             
128         char    timecnt[4];     
129         char    typecnt[4];
130         char    charcnt[4];                     
131 } tzinfo; 
132
133 static int r_pos [] = {1, 2, 3, -2, -1};
134
135 #ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY
136 static char *search_paths [] = {"/usr/share/zoneinfo","/usr/lib/zoneinfo","/etc/zoneinfo","/usr/share/lib/zoneinfo"};
137 static char *zdir = NULL;
138 #endif
139
140 #define NUM_SEARCH_PATHS (sizeof (search_paths)/ sizeof (search_paths [0]))
141 #define EFREAD(buf,size,num,fs) \
142         if (fread (buf, size, num, fs) == 0  && ferror (fs)) {\
143                 icalerror_set_errno (ICAL_FILE_ERROR);          \
144                 goto error;                                     \
145         }                                                       \
146
147 typedef struct
148 {
149         long int gmtoff;
150         unsigned char isdst;
151         unsigned int abbr;      
152         unsigned char isstd;
153         unsigned char isgmt;
154         char *zname;
155
156 } ttinfo;
157
158 typedef struct
159 {
160         time_t transition;
161         long int change;
162 } leap;
163
164 #ifndef EVOLUTION_ICAL_COMPATIBILITY
165 extern const char *ical_tzid_prefix;
166 #endif
167
168 static int
169 decode (const void *ptr)
170 {
171 #if defined(sun) && defined(__SVR4)
172     if (sizeof (int) == 4)
173 #ifdef _BIG_ENDIAN
174         return *(const int *) ptr;
175 #else
176         return BSWAP_32 (*(const int *) ptr);
177 #endif
178 #else
179     if ((BYTE_ORDER == BIG_ENDIAN) && sizeof (int) == 4)
180         return *(const int *) ptr;
181     else if (BYTE_ORDER == LITTLE_ENDIAN && sizeof (int) == 4)
182         return bswap_32 (*(const int *) ptr);
183 #endif
184         else
185         {
186                 const unsigned char *p = ptr;
187                 int result = *p & (1 << (CHAR_BIT - 1)) ? ~0 : 0;
188
189                 result = (result << 8) | *p++;
190                 result = (result << 8) | *p++;
191                 result = (result << 8) | *p++;
192                 result = (result << 8) | *p++;
193
194                 return result;
195         }
196 }
197
198 static char *
199 zname_from_stridx (char *str, long int idx) 
200 {
201         int i = 0;
202         char *ret;
203         size_t size;
204
205         i = idx;
206
207         while (str [i] != '\0') 
208                 i++;
209
210         size = i - idx;
211         str += idx;
212         ret = (char *) malloc (size + 1);
213         ret = strncpy (ret, str, size);
214         ret [size] = '\0';
215
216         return ret;
217 }
218
219 static void 
220 find_transidx (time_t *transitions, ttinfo *types, int *trans_idx, long int num_trans, int *stdidx, int *dstidx) 
221 {
222         time_t now, year_start;
223         int i, found = 0;
224         struct icaltimetype itime;
225
226         now = time (NULL);
227         itime = icaltime_from_timet (now, 0);
228         itime.month = itime.day = 1;
229         itime.hour = itime.minute = itime.second = 0;
230         year_start = icaltime_as_timet(itime);
231
232         /* Set this by default */
233         *stdidx = (num_trans - 1);
234
235         for (i = (num_trans - 1); i >= 0; --i)
236                 if (year_start < transitions [i]) {
237                         int idx;
238                         found = 1;
239                         idx = trans_idx [i];
240                         (types [idx].isdst) ? (*dstidx = i) : (*stdidx = i);
241                 }
242
243         /* If the transition found is the last among the list, prepare to use the last two transtions. 
244          * Using this will most likely throw the DTSTART of the resulting component off by 1 or 2 days
245          * but it would set right by the adjustment made. 
246          * NOTE: We need to use the last two transitions only because there is no data for the future 
247          * transitions. 
248          */
249         if (found && (*dstidx == -1)) {
250                 *dstidx = ((*stdidx) - 1);
251         }
252
253         return;
254 }
255
256 #ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY
257 static void
258 set_zonedir (void)
259 {
260         char file_path[PATH_MAX];
261         const char *fname = ZONES_TAB_SYSTEM_FILENAME;
262         int i;  
263
264         for (i = 0;i < NUM_SEARCH_PATHS; i++) {
265                 sprintf (file_path, "%s/%s", search_paths [i], fname);
266                 if (!access (file_path, F_OK|R_OK)) {
267                         zdir = search_paths [i];
268                         break;
269                 }
270         }
271 }
272
273
274 const char *
275 icaltzutil_get_zone_directory (void)
276 {
277         if (!zdir)
278                 set_zonedir ();
279
280         return zdir;
281 }
282 #endif
283
284 /* Calculate the relative position of the week in a month from a date */
285 static int
286 calculate_pos (icaltimetype icaltime)
287 {
288         int pos;
289
290         pos = (icaltime.day -1) / 7;
291
292         /* Check if pos 3 is the last occurence of the week day in the month */ 
293         if (pos == 3 && ((icaltime.day + 7) > icaltime_days_in_month (icaltime.month, icaltime.year))) 
294                 pos = 4;
295
296         return r_pos [pos];
297 }
298
299 static void
300 adjust_dtstart_day_to_rrule (icalcomponent *comp, struct icalrecurrencetype rule)
301 {
302         time_t now, year_start;
303         struct icaltimetype start, comp_start, iter_start, itime;
304         icalrecur_iterator *iter;
305
306         now = time (NULL);
307         itime = icaltime_from_timet (now, 0);
308         itime.month = itime.day = 1;
309         itime.hour = itime.minute = itime.second = 0;
310         year_start = icaltime_as_timet(itime);
311
312         comp_start = icalcomponent_get_dtstart (comp);
313         start = icaltime_from_timet (year_start, 0);
314
315         iter = icalrecur_iterator_new (rule, start);
316         iter_start = icalrecur_iterator_next (iter);
317         icalrecur_iterator_free (iter);
318
319         if (iter_start.day != comp_start.day) {
320                 comp_start.day = iter_start.day;
321                 icalcomponent_set_dtstart (comp, comp_start);
322         }
323 }
324
325 icalcomponent*
326 icaltzutil_fetch_timezone (const char *location)
327 {
328         int ret = 0;
329         FILE *f;
330         tzinfo type_cnts;
331         unsigned int i, num_trans, num_types = 0, num_chars, num_leaps, num_isstd, num_isgmt;
332         time_t *transitions = NULL;
333         time_t trans;
334         int *trans_idx = NULL, dstidx = -1, stdidx = -1, pos, sign, zidx, zp_idx;
335         ttinfo *types = NULL;
336         char *znames = NULL, *full_path, *tzid, *r_trans, *temp = NULL;
337         leap *leaps = NULL;
338         icalcomponent *tz_comp = NULL, *dst_comp = NULL, *std_comp = NULL;
339         icalproperty *icalprop;
340         icaltimetype dtstart, icaltime;
341         struct icalrecurrencetype ical_recur;
342         const char *basedir;
343
344         /* if (!location) return NULL; */
345
346         basedir = icaltzutil_get_zone_directory();
347         if (!basedir) {
348                 icalerror_set_errno (ICAL_FILE_ERROR);
349                 return NULL;
350         }
351
352         full_path = (char *) malloc (strlen (basedir) + strlen (location) + 2);
353         sprintf (full_path,"%s/%s",basedir, location);
354
355         if ((f = fopen (full_path, "rb")) == 0) {
356                 icalerror_set_errno (ICAL_FILE_ERROR);
357                 free (full_path);
358                 return NULL;
359         }
360
361         if ((ret = fseek (f, 20, SEEK_SET)) != 0) {
362                 icalerror_set_errno (ICAL_FILE_ERROR);
363                 goto error;     
364         }
365
366         EFREAD(&type_cnts, 24, 1, f);
367
368         num_isgmt = decode (type_cnts.ttisgmtcnt);
369         num_leaps = decode (type_cnts.leapcnt);
370         num_chars = decode (type_cnts.charcnt);
371         num_trans = decode (type_cnts.timecnt);
372         num_isstd = decode (type_cnts.ttisstdcnt);
373         num_types = decode (type_cnts.typecnt);
374
375         transitions = calloc (num_trans, sizeof (time_t));
376         temp = calloc (num_trans, 4);
377         r_trans = temp;
378         EFREAD(r_trans, 4, num_trans, f);
379
380         if (num_trans) {
381                 trans_idx = calloc (num_trans, sizeof (int));
382                 for (i = 0; i < num_trans; i++) {
383                         trans_idx [i] = fgetc (f);
384                         transitions [i] = decode (r_trans);
385                         r_trans += 4;
386                 }
387         }
388         
389         free (temp);
390         temp = NULL;
391
392         types = calloc (num_types, sizeof (ttinfo));
393         for (i = 0; i < num_types; i++) {
394                 unsigned char a [4];
395                 int c;
396
397                 EFREAD(a, 4, 1, f);
398                 c = fgetc (f);
399                 types [i].isdst = c;
400                 if((c = fgetc (f)) < 0) {
401                    c = 0;
402                    break;
403                 }
404                 types [i].abbr = c;
405                 types [i].gmtoff = decode (a);
406         }
407
408         znames = (char *) malloc (num_chars);
409         EFREAD(znames, num_chars, 1, f);
410
411         /* We got all the information which we need */
412
413         leaps = calloc (num_leaps, sizeof (leap));
414         for (i = 0; i < num_leaps; i++) {
415                 char c [4];
416
417                 EFREAD (c, 4, 1, f);
418                 leaps [i].transition = decode (c);
419
420                 EFREAD (c, 4, 1, f);
421                 leaps [i].change = decode (c);
422         }
423
424         for (i = 0; i < num_isstd; ++i) {
425                 int c = getc (f);
426                 types [i].isstd = c != 0;
427         }
428
429         while (i < num_types)
430                 types [i++].isstd = 0;
431
432         for (i = 0; i <  num_isgmt; ++i) {
433                 int c = getc (f);
434                 types [i].isgmt = c != 0;
435         }
436
437         while (i < num_types)
438                 types [i++].isgmt = 0;
439
440         /* Read all the contents now */
441
442         for (i = 0; i < num_types; i++) 
443                 types [i].zname = zname_from_stridx (znames, types [i].abbr);
444
445         if (num_trans != 0)
446                 find_transidx (transitions, types, trans_idx, num_trans, &stdidx, &dstidx);
447         else
448                 stdidx = 0;
449
450         tz_comp = icalcomponent_new (ICAL_VTIMEZONE_COMPONENT);
451
452         /* Add tzid property */
453         tzid = (char *) malloc (strlen (ical_tzid_prefix) + strlen (location) + 8);
454         sprintf (tzid, "%sTzfile/%s", ical_tzid_prefix, location);
455         icalprop = icalproperty_new_tzid (tzid);
456         icalcomponent_add_property (tz_comp, icalprop);
457         free (tzid);
458
459         icalprop = icalproperty_new_x (location);
460         icalproperty_set_x_name (icalprop, "X-LIC-LOCATION");
461         icalcomponent_add_property (tz_comp, icalprop);
462         
463         if (stdidx != -1) {
464                 if (num_trans != 0)
465                         zidx = trans_idx [stdidx];
466                 else 
467                         zidx = 0;
468
469                 std_comp = icalcomponent_new (ICAL_XSTANDARD_COMPONENT);
470                 icalprop = icalproperty_new_tzname (types [zidx].zname);
471                 icalcomponent_add_property (std_comp, icalprop);
472
473                 if (dstidx != -1)
474                         zp_idx = trans_idx [stdidx-1]; 
475                 else
476                         zp_idx = zidx;
477                 /* DTSTART localtime uses TZOFFSETFROM UTC offset */
478                 if (num_trans != 0)
479                         trans = transitions [stdidx] + types [zp_idx].gmtoff;
480                 else
481                         trans = types [zp_idx].gmtoff;
482                 icaltime = icaltime_from_timet (trans, 0);
483                 dtstart = icaltime;
484                 dtstart.year = 1970;
485                 dtstart.minute = dtstart.second = 0;
486                 icalprop = icalproperty_new_dtstart (dtstart);
487                 icalcomponent_add_property (std_comp, icalprop);
488
489                 /* If DST changes are present use RRULE */
490                 if (dstidx != -1) {
491                         icalrecurrencetype_clear (&ical_recur);
492                         ical_recur.freq = ICAL_YEARLY_RECURRENCE;
493                         ical_recur.by_month [0] = icaltime.month;
494                         pos = calculate_pos (icaltime);
495                         pos < 0 ? (sign = -1): (sign = 1);
496                         ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime));
497                         icalprop = icalproperty_new_rrule (ical_recur);
498                         icalcomponent_add_property (std_comp, icalprop);
499
500                         adjust_dtstart_day_to_rrule (std_comp, ical_recur);
501                 }
502         icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff);
503         icalcomponent_add_property (std_comp, icalprop);
504
505                 icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff);
506                 icalcomponent_add_property (std_comp, icalprop);
507
508                 icalcomponent_add_component (tz_comp, std_comp);
509         } else 
510                 icalerror_set_errno (ICAL_MALFORMEDDATA_ERROR);
511         
512         if (dstidx != -1) {
513                 zidx = trans_idx [dstidx];
514                 zp_idx = trans_idx [dstidx-1];
515                 dst_comp = icalcomponent_new (ICAL_XDAYLIGHT_COMPONENT);
516                 icalprop = icalproperty_new_tzname (types [zidx].zname);
517                 icalcomponent_add_property (dst_comp, icalprop);
518
519                 /* DTSTART localtime uses TZOFFSETFROM UTC offset */
520                 if (num_trans != 0)
521                         trans = transitions [dstidx] + types [zp_idx].gmtoff;
522                 else
523                         trans = types [zp_idx].gmtoff;
524                 icaltime = icaltime_from_timet (trans, 0);
525                 dtstart = icaltime;
526                 dtstart.year = 1970;
527                 dtstart.minute = dtstart.second = 0;
528                 icalprop = icalproperty_new_dtstart (dtstart);
529                 icalcomponent_add_property (dst_comp, icalprop);
530
531                 icalrecurrencetype_clear (&ical_recur);
532                 ical_recur.freq = ICAL_YEARLY_RECURRENCE;
533                 ical_recur.by_month [0] = icaltime.month;
534                 pos = calculate_pos (icaltime);
535                 pos < 0 ? (sign = -1): (sign = 1);
536                 ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime));
537                 icalprop = icalproperty_new_rrule (ical_recur);
538                 icalcomponent_add_property (dst_comp, icalprop);
539
540                 adjust_dtstart_day_to_rrule (dst_comp, ical_recur);
541
542                 icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff);
543                 icalcomponent_add_property (dst_comp, icalprop);
544
545                 icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff);
546                 icalcomponent_add_property (dst_comp, icalprop);
547
548                 icalcomponent_add_component (tz_comp, dst_comp);
549         }
550
551 error:
552         if (f)
553                 fclose  (f);
554
555         if (transitions)
556                 free (transitions);
557         if (trans_idx)
558                 free (trans_idx);
559         if (types) {
560                 for (i = 0; i < num_types; i++) 
561                         if (types [i].zname)
562                                 free (types [i].zname);
563                 free (types);
564         }
565         if (znames)
566                 free (znames);
567         free (full_path);
568         if (leaps)
569                 free (leaps);
570         if (temp)
571                 free (temp);
572
573         return tz_comp;
574 }
575
576 /*
577  * What follows is copied and slightly simplified (not thread-safe!)
578  * code from icaltimezone.c.
579  *
580  * This is necessary because otherwise, when libsynthesis.so calls
581  * icaltimezone_get_component(), libical.1.so uses its own builtin
582  * icaltzutil_fetch_timezone().  Apparently it exports that without
583  * looking it up via the dynamic linker itself. Therefore we have to
584  * redirect the original icaltimezone_get_component() call.
585  */
586
587 static void
588 icaltimezone_load_builtin_timezone      (icaltimezone *zone)
589 {
590     icalcomponent *subcomp;
591     const char *location;
592
593     /* If the location isn't set, it isn't a builtin timezone. */
594     location = icaltimezone_get_location(zone);
595     if (!location || !location[0])
596         return;
597
598     subcomp = icaltzutil_fetch_timezone (location);
599
600     if (!subcomp) {
601         icalerror_set_errno(ICAL_PARSE_ERROR);
602         return;
603     }
604
605     icaltimezone_set_component(zone, subcomp);
606 }
607
608 #undef icaltimezone_get_component
609 icalcomponent *icaltimezone_get_component(icaltimezone *zone)
610 {
611         icalcomponent *comp;
612         /* If this is a floating time, without a timezone, return NULL. */
613         if (!zone)
614                 return NULL;
615
616         /*
617          * Without this check, icaltimezone_set_component() in
618          * icaltimezone_load_builtin_timezone() will discard the
619          * already loaded component of builtin timezones and replace
620          * it with the new one, so there is no leak. It's just
621          * inefficient.
622          *
623          * However, this method also gets called for non-internal
624          * timezones which were created from a VTIMEZONE and in
625          * that case not using the existing component is wrong.
626          *
627          * Hack: duplicate the internal _icaltimezone struct (from
628          * icaltimezoneimpl.h).
629          */
630         struct _my_icaltimezone {
631                 char *tzid;
632                 char *location;
633                 char *tznames;
634                 double latitude;
635                 double longitude;
636                 icalcomponent *component;
637         };
638         comp = ((struct _my_icaltimezone *)zone)->component;
639         if (!comp) {
640                 icaltimezone_load_builtin_timezone (zone);
641                 comp = ((struct _my_icaltimezone *)zone)->component;
642         }
643
644         return comp;
645 }
646
647 /*
648  * For including the .o file in binaries via -Wl,-usyncevo_fetch_timezone.
649  * We cannot use -Wl,-uicaltzutil_fetch_timezone because that gets satisfied by
650  * libical itself.
651  */
652 int syncevo_fetch_timezone;
653
654 /*
655  * Avoid lazy resolution of the methods that we export. client-test otherwise
656  * ends up calling the libical version of the methods despite having its own
657  * copy compiled into the executable, at least on Ubuntu Saucy and Trusty.
658  */
659 /* void *syncevo_fetch_timezone_p = &icaltzutil_fetch_timezone; */
660 /* void *syncevo_get_component_p = &icaltimezone_get_component; */
661
662 #ifdef ICALTZ_UTIL_MAIN
663 int 
664 main (int argc, char *argv [])
665 {
666         icalcomponent *comp = icaltzutil_fetch_timezone (argv [1]);
667         const char *str = comp ? icalcomponent_as_ical_string(comp) : "no such zone";
668         puts(str);
669         return 0;
670 }
671 #endif