diff options
Diffstat (limited to 'src/libical/icaltime.c')
-rw-r--r-- | src/libical/icaltime.c | 1238 |
1 files changed, 1238 insertions, 0 deletions
diff --git a/src/libical/icaltime.c b/src/libical/icaltime.c new file mode 100644 index 0000000..c1c4f35 --- /dev/null +++ b/src/libical/icaltime.c @@ -0,0 +1,1238 @@ +/* -*- Mode: C -*- + ====================================================================== + FILE: icaltime.c + CREATOR: eric 02 June 2000 + + $Id: icaltime.c,v 1.71 2008-01-29 18:31:48 dothebart Exp $ + $Locker: $ + + (C) COPYRIGHT 2000, Eric Busboom <eric@softwarestudio.org> + http://www.softwarestudio.org + + This program is free software; you can redistribute it and/or modify + it under the terms of either: + + The LGPL as published by the Free Software Foundation, version + 2.1, available at: http://www.fsf.org/copyleft/lesser.html + + Or: + + The Mozilla Public License Version 1.0. You may obtain a copy of + the License at http://www.mozilla.org/MPL/ + + The Original Code is eric. The Initial Developer of the Original + Code is Eric Busboom + + + ======================================================================*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "icaltime.h" +#include <assert.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <time.h> + +#include "astime.h" /* Julian data handling routines */ + +#include "icalerror.h" +#include "icalmemory.h" + +#include "icaltimezone.h" +#include "icalvalue.h" + +#ifdef WIN32 +#include <windows.h> + +#define snprintf _snprintf +#define strcasecmp stricmp +#endif + +#ifdef WIN32 +/* Undef the similar macro from pthread.h, it doesn't check if + * gmtime() returns NULL. + */ +#undef gmtime_r + +/* The gmtime() in Microsoft's C library is MT-safe */ +#define gmtime_r(tp,tmp) (gmtime(tp)?(*(tmp)=*gmtime(tp),(tmp)):0) +#endif + +#ifdef HAVE_PTHREAD + #include <pthread.h> + static pthread_mutex_t tzid_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +/* + * Function to convert a struct tm time specification + * to an ANSI time_t using the specified time zone. + * This is different from the standard mktime() function + * in that we don't want the automatic adjustments for + * local daylight savings time applied to the result. + * This function expects well-formed input. + */ +static time_t make_time(struct tm *tm, int tzm) +{ + time_t tim; + + static int days[] = { -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 }; + + /* check that year specification within range */ + + if (tm->tm_year < 70 || tm->tm_year > 138) + return((time_t) -1); + + /* check that month specification within range */ + + if (tm->tm_mon < 0 || tm->tm_mon > 11) + return((time_t) -1); + + /* check for upper bound of Jan 17, 2038 (to avoid possibility of + 32-bit arithmetic overflow) */ + + if (tm->tm_year == 138) { + if (tm->tm_mon > 0) + return((time_t) -1); + else if (tm->tm_mday > 17) + return((time_t) -1); + } + + /* + * calculate elapsed days since start of the epoch (midnight Jan + * 1st, 1970 UTC) 17 = number of leap years between 1900 and 1970 + * (number of leap days to subtract) + */ + + tim = (tm->tm_year - 70) * 365 + ((tm->tm_year - 1) / 4) - 17; + + /* add number of days elapsed in the current year */ + + tim += days[tm->tm_mon]; + + /* check and adjust for leap years (the leap year check only valid + during the 32-bit era */ + + if ((tm->tm_year & 3) == 0 && tm->tm_mon > 1) + tim += 1; + + /* elapsed days to current date */ + + tim += tm->tm_mday; + + + /* calculate elapsed hours since start of the epoch */ + + tim = tim * 24 + tm->tm_hour; + + /* calculate elapsed minutes since start of the epoch */ + + tim = tim * 60 + tm->tm_min; + + /* adjust per time zone specification */ + + tim -= tzm; + + /* calculate elapsed seconds since start of the epoch */ + + tim = tim * 60 + tm->tm_sec; + + /* return number of seconds since start of the epoch */ + + return(tim); +} + +/** @brief Constructor (deprecated). + * + * Convert seconds past UNIX epoch to a timetype. + * + * @deprecated This constructor is deprecated and shouldn't be used in + * new software. Use icaltime_from_timet_with_zone(time_t, int, + * icaltimezone *) instead. In the meantime, calls to this method + * return a floating time, which can always be converted to a local + * time with an appropriate call to icaltime_convert_to_zone(). + */ + +struct icaltimetype +icaltime_from_timet(const time_t tm, const int is_date) +{ +#ifndef NO_WARN_DEPRECATED + icalerror_warn("icaltime_from_timet() is DEPRECATED, use icaltime_from_timet_with_zone() instead"); +#endif + + return icaltime_from_timet_with_zone(tm, is_date, 0); +} + + +/** @brief Constructor. + * + * @param tm The time + * @param is_date Boolean: 1 means we should treat tm as a DATE + * @param zone The timezone tm is in, NULL means to treat tm as a + * floating time + * + * Return a new icaltime instance, initialized to the given time + * expressed as seconds past UNIX epoch, optionally using the given + * timezone. + * + * If the caller specifies the is_date param as TRUE, the returned + * object is of DATE type, otherwise the input is meant to be of + * DATE-TIME type. + * If the zone is not specified (NULL zone param) the time is taken + * to be floating, that is, valid in any timezone. Note that, in + * addition to the uses specified in [RFC2445], this can be used + * when doing simple math on couples of times. + * If the zone is specified (UTC or otherwise), it's stored in the + * object and it's used as the native timezone for this object. + * This means that the caller can convert this time to a different + * target timezone with no need to store the source timezone. + * + */ +struct icaltimetype +icaltime_from_timet_with_zone(const time_t tm, const int is_date, + const icaltimezone *zone) +{ + struct icaltimetype tt; + struct tm t; + icaltimezone *utc_zone; + + utc_zone = icaltimezone_get_utc_timezone (); + + /* Convert the time_t to a struct tm in UTC time. We can trust gmtime + for this. */ +#ifdef HAVE_PTHREAD + gmtime_r (&tm, &t); +#else + t = *(gmtime (&tm)); +#endif + + tt.year = t.tm_year + 1900; + tt.month = t.tm_mon + 1; + tt.day = t.tm_mday; + tt.hour = t.tm_hour; + tt.minute = t.tm_min; + tt.second = t.tm_sec; + tt.is_date = 0; + tt.is_utc = (zone == utc_zone) ? 1 : 0; + tt.is_daylight = 0; + tt.zone = NULL; + + /* Use our timezone functions to convert to the required timezone. */ + icaltimezone_convert_time (&tt, utc_zone, (icaltimezone *)zone); + + tt.is_date = is_date; + + /* If it is a DATE value, make sure hour, minute & second are 0. */ + if (is_date) { + tt.hour = 0; + tt.minute = 0; + tt.second = 0; + } + + return tt; +} + +/** @brief Convenience constructor. + * + * Returns the current time in the given timezone, as an icaltimetype. + */ +struct icaltimetype icaltime_current_time_with_zone(const icaltimezone *zone) +{ + return icaltime_from_timet_with_zone (time (NULL), 0, zone); +} + +/** @brief Convenience constructor. + * + * Returns the current day as an icaltimetype, with is_date set. + */ +struct icaltimetype icaltime_today(void) +{ + return icaltime_from_timet_with_zone (time (NULL), 1, NULL); +} + +/** @brief Return the time as seconds past the UNIX epoch + * + * While this function is not currently deprecated, it probably won't do + * what you expect, unless you know what you're doing. In particular, you + * should only pass an icaltime in UTC, since no conversion is done. Even + * in that case, it's probably better to just use + * icaltime_as_timet_with_zone(). + */ +time_t icaltime_as_timet(const struct icaltimetype tt) +{ + struct tm stm; + time_t t; + + /* If the time is the special null time, return 0. */ + if (icaltime_is_null_time(tt)) { + return 0; + } + + /* Copy the icaltimetype to a struct tm. */ + memset (&stm, 0, sizeof (struct tm)); + + if (icaltime_is_date(tt)) { + stm.tm_sec = stm.tm_min = stm.tm_hour = 0; + } else { + stm.tm_sec = tt.second; + stm.tm_min = tt.minute; + stm.tm_hour = tt.hour; + } + + stm.tm_mday = tt.day; + stm.tm_mon = tt.month-1; + stm.tm_year = tt.year-1900; + stm.tm_isdst = -1; + + t = make_time(&stm, 0); + + return t; + +} + + +/* Structure used by set_tz to hold an old value of TZ, and the new + value, which is in memory we will have to free in unset_tz */ +/* This will hold the last "TZ=XXX" string we used with putenv(). After we + call putenv() again to set a new TZ string, we can free the previous one. + As far as I know, no libc implementations actually free the memory used in + the environment variables (how could they know if it is a static string or + a malloc'ed string?), so we have to free it ourselves. */ +static char* saved_tz = NULL; + +/* If you use set_tz(), you must call unset_tz() some time later to restore the + original TZ. Pass unset_tz() the string that set_tz() returns. Call both the functions + locking the tzid mutex as in icaltime_as_timet_with_zone */ +char* set_tz(const char* tzid) +{ + char *old_tz, *old_tz_copy = NULL, *new_tz; + + /* Get the old TZ setting and save a copy of it to return. */ + old_tz = getenv("TZ"); + if(old_tz){ + old_tz_copy = (char*)malloc(strlen (old_tz) + 4); + + if(old_tz_copy == 0){ + icalerror_set_errno(ICAL_NEWFAILED_ERROR); + return 0; + } + + strcpy (old_tz_copy, "TZ="); + strcpy (old_tz_copy + 3, old_tz); + } + + /* Create the new TZ string. */ + new_tz = (char*)malloc(strlen (tzid) + 4); + + if(new_tz == 0){ + icalerror_set_errno(ICAL_NEWFAILED_ERROR); + return 0; + } + + strcpy (new_tz, "TZ="); + strcpy (new_tz + 3, tzid); + + /* Add the new TZ to the environment. */ + putenv(new_tz); + + /* Free any previous TZ environment string we have used in a synchronized manner. */ + + free (saved_tz); + + /* Save a pointer to the TZ string we just set, so we can free it later. */ + saved_tz = new_tz; + + return old_tz_copy; /* This will be zero if the TZ env var was not set */ +} + +void unset_tz(char *tzstr) +{ + /* restore the original environment */ + + if(tzstr!=0){ + putenv(tzstr); + } else { + /* Delete from environment. We prefer unsetenv(3) over putenv(3) + because the former is POSIX and behaves consistently. The later + does not unset the variable in some systems (like NetBSD), leaving + it with an empty value. This causes problems later because further + calls to time related functions in libc will treat times in UTC. */ +#ifdef HAVE_UNSETENV + unsetenv("TZ"); +#else + putenv("TZ"); +#endif + } + + /* Free any previous TZ environment string we have used in a synchronized manner */ + free (saved_tz); + + /* Save a pointer to the TZ string we just set, so we can free it later. + (This can possibly be NULL if there was no TZ to restore.) */ + saved_tz = tzstr; +} + +/** Return the time as seconds past the UNIX epoch, using the + * given timezone. + * + * This convenience method combines a call to icaltime_convert_to_zone() + * with a call to icaltime_as_timet(). + * If the input timezone is null, no conversion is done; that is, the + * time is simply returned as time_t in its native timezone. + */ +time_t icaltime_as_timet_with_zone(const struct icaltimetype tt, + const icaltimezone *zone) +{ + icaltimezone *utc_zone; + struct tm stm; + time_t t; + char *old_tz; + struct icaltimetype local_tt; + + utc_zone = icaltimezone_get_utc_timezone (); + + /* If the time is the special null time, return 0. */ + if (icaltime_is_null_time(tt)) { + return 0; + } + + local_tt = tt; + + /* Clear the is_date flag, so we can convert the time. */ + local_tt.is_date = 0; + + /* Use our timezone functions to convert to UTC. */ + icaltimezone_convert_time (&local_tt, (icaltimezone *)zone, utc_zone); + + /* Copy the icaltimetype to a struct tm. */ + memset (&stm, 0, sizeof (struct tm)); + + stm.tm_sec = local_tt.second; + stm.tm_min = local_tt.minute; + stm.tm_hour = local_tt.hour; + stm.tm_mday = local_tt.day; + stm.tm_mon = local_tt.month-1; + stm.tm_year = local_tt.year-1900; + stm.tm_isdst = -1; +/* The functions putenv and mktime are not thread safe, inserting a lock +to prevent any crashes */ + +#ifdef HAVE_PTHREAD + pthread_mutex_lock (&tzid_mutex); +#endif + + /* Set TZ to UTC and use mktime to convert to a time_t. */ + old_tz = set_tz ("UTC"); +#ifdef WIN32 + tzset (); +#endif + + t = mktime (&stm); + unset_tz (old_tz); +#ifdef WIN32 + tzset (); +#endif + +#ifdef HAVE_PTHREAD + pthread_mutex_unlock (&tzid_mutex); +#endif + return t; +} + +const char* icaltime_as_ical_string(const struct icaltimetype tt) +{ + char *buf; + buf = icaltime_as_ical_string_r(tt); + icalmemory_add_tmp_buffer(buf); + return buf; +} + + +/** + * Return a string represention of the time, in RFC2445 format. The + * string is owned by libical + */ +char* icaltime_as_ical_string_r(const struct icaltimetype tt) +{ + size_t size = 17; + char* buf = icalmemory_new_buffer(size); + + if(tt.is_date){ + snprintf(buf, size,"%04d%02d%02d",tt.year,tt.month,tt.day); + } else { + const char* fmt; + if(tt.is_utc){ + fmt = "%04d%02d%02dT%02d%02d%02dZ"; + } else { + fmt = "%04d%02d%02dT%02d%02d%02d"; + } + snprintf(buf, size,fmt,tt.year,tt.month,tt.day, + tt.hour,tt.minute,tt.second); + } + + return buf; +} + + +/** + * Reset all of the time components to be in their normal ranges. For + * instance, given a time with minutes=70, the minutes will be reduces + * to 10, and the hour incremented. This allows the caller to do + * arithmetic on times without worrying about overflow or + * underflow. + * + * Implementation note: we call icaltime_adjust() with no adjustment. + */ +struct icaltimetype icaltime_normalize(const struct icaltimetype tt) +{ + struct icaltimetype ret = tt; + icaltime_adjust(&ret, 0, 0, 0, 0); + return ret; +} + + + +/** @brief Contructor. + * + * Create a time from an ISO format string. + * + * @todo If the given string specifies a DATE-TIME not in UTC, there + * is no way to know if this is a floating time or really refers to a + * timezone. We should probably add a new constructor: + * icaltime_from_string_with_zone() + */ +struct icaltimetype icaltime_from_string(const char* str) +{ + struct icaltimetype tt = icaltime_null_time(); + int size; + + icalerror_check_arg_re(str!=0,"str",icaltime_null_time()); + + size = strlen(str); + + if ((size == 15) || (size == 19)) { /* floating time with/without separators*/ + tt.is_utc = 0; + tt.is_date = 0; + } else if ((size == 16) || (size == 20)) { /* UTC time, ends in 'Z'*/ + if ((str[15] != 'Z') && (str[19] != 'Z')) + goto FAIL; + + tt.is_utc = 1; + tt.zone = icaltimezone_get_utc_timezone(); + tt.is_date = 0; + } else if ((size == 8) || (size == 10)) { /* A DATE */ + tt.is_utc = 0; + tt.is_date = 1; + } else { /* error */ + goto FAIL; + } + + if (tt.is_date == 1){ + if (size == 10) { + char dsep1, dsep2; + if (sscanf(str,"%04d%c%02d%c%02d",&tt.year,&dsep1,&tt.month,&dsep2,&tt.day) < 5) + goto FAIL; + if ((dsep1 != '-') || (dsep2 != '-')) + goto FAIL; + } else if (sscanf(str,"%04d%02d%02d",&tt.year,&tt.month,&tt.day) < 3) { + goto FAIL; + } + } else { + if (size > 16 ) { + char dsep1, dsep2, tsep, tsep1, tsep2; + if (sscanf(str,"%04d%c%02d%c%02d%c%02d%c%02d%c%02d",&tt.year,&dsep1,&tt.month,&dsep2, + &tt.day,&tsep,&tt.hour,&tsep1,&tt.minute,&tsep2,&tt.second) < 11) + goto FAIL; + + if((tsep != 'T') || (dsep1 != '-') || (dsep2 != '-') || (tsep1 != ':') || (tsep2 != ':')) + goto FAIL; + + } else { + char tsep; + if (sscanf(str,"%04d%02d%02d%c%02d%02d%02d",&tt.year,&tt.month,&tt.day, + &tsep,&tt.hour,&tt.minute,&tt.second) < 7) + goto FAIL; + + if(tsep != 'T') + goto FAIL; + } + } + + return tt; + +FAIL: + icalerror_set_errno(ICAL_MALFORMEDDATA_ERROR); + return icaltime_null_time(); +} + + +/* Returns whether the specified year is a leap year. Year is the normal year, + e.g. 2001. */ +int +icaltime_is_leap_year (const int year) +{ + + if (year <= 1752) + return (year % 4 == 0); + else + return ( (year % 4==0) && (year % 100 !=0 )) || (year % 400 == 0); +} + + +int +ycaltime_days_in_year (const int year) +{ + if (icaltime_is_leap_year (year)) + return 366; + else return 365; +} + +static int _days_in_month[] = {0,31,28,31,30,31,30,31,31,30,31,30,31}; + +int icaltime_days_in_month(const int month, const int year) +{ + + int days = _days_in_month[month]; + +/* The old code aborting if it was passed a parameter like BYMONTH=0 + * Unfortunately it's not practical right now to pass an error all + * the way up the stack, so instead of aborting we're going to apply + * the GIGO principle and simply return '30 days' if we get an + * invalid month. Modern applications cannot tolerate crashing. + * assert(month > 0); + * assert(month <= 12); + */ + if ((month < 1) || (month > 12)) { + return 30; + } + + if( month == 2){ + days += icaltime_is_leap_year(year); + } + + return days; +} + +/* 1-> Sunday, 7->Saturday */ +int icaltime_day_of_week(const struct icaltimetype t){ + UTinstant jt; + + memset(&jt,0,sizeof(UTinstant)); + + jt.year = t.year; + jt.month = t.month; + jt.day = t.day; + jt.i_hour = 0; + jt.i_minute = 0; + jt.i_second = 0; + + juldat(&jt); + + return jt.weekday + 1; +} + +/** Day of the year that the first day of the week (Sunday) is on. + */ +int icaltime_start_doy_week(const struct icaltimetype t, int fdow){ + UTinstant jt; + int delta; + + memset(&jt,0,sizeof(UTinstant)); + + jt.year = t.year; + jt.month = t.month; + jt.day = t.day; + jt.i_hour = 0; + jt.i_minute = 0; + jt.i_second = 0; + + juldat(&jt); + caldat(&jt); + + delta = jt.weekday - (fdow - 1); + if (delta < 0) delta += 7; + return jt.day_of_year - delta; +} + +/** Day of the year that the first day of the week (Sunday) is on. + * + * @deprecated Doesn't take into account different week start days. + */ +int icaltime_start_doy_of_week(const struct icaltimetype t){ + +#ifndef NO_WARN_DEPRECATED + icalerror_warn("icaltime_start_doy_of_week() is DEPRECATED, use\ + icaltime_start_doy_week() instead"); +#endif + + return icaltime_start_doy_week(t, 1); +} + +/** + * @todo Doesn't take into account the start day of the + * week. strftime assumes that weeks start on Monday. + */ +int icaltime_week_number(const struct icaltimetype ictt) +{ + UTinstant jt; + + memset(&jt,0,sizeof(UTinstant)); + + jt.year = ictt.year; + jt.month = ictt.month; + jt.day = ictt.day; + jt.i_hour = 0; + jt.i_minute = 0; + jt.i_second = 0; + + juldat(&jt); + caldat(&jt); + + return (jt.day_of_year - jt.weekday) / 7; +} + +/* The first array is for non-leap years, the second for leap years*/ +static const int days_in_year_passed_month[2][13] = +{ /* jan feb mar apr may jun jul aug sep oct nov dec */ + { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, + { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } +}; + +/** + * Returns the day of the year, counting from 1 (Jan 1st). + */ +int icaltime_day_of_year(const struct icaltimetype t){ + int is_leap = icaltime_is_leap_year (t.year); + + return days_in_year_passed_month[is_leap][t.month - 1] + t.day; +} + +/** @brief Contructor. + * + * Create a new time, given a day of year and a year. + */ +/* Jan 1 is day #1, not 0 */ +struct icaltimetype icaltime_from_day_of_year(const int _doy, const int _year) +{ + struct icaltimetype tt = icaltime_null_date(); + int is_leap; + int month; + int doy = _doy; + int year = _year; + + is_leap = icaltime_is_leap_year(year); + + /* Zero and neg numbers represent days of the previous year */ + if(doy <1){ + year--; + is_leap = icaltime_is_leap_year(year); + doy += days_in_year_passed_month[is_leap][12]; + } else if(doy > days_in_year_passed_month[is_leap][12]){ + /* Move on to the next year*/ + is_leap = icaltime_is_leap_year(year); + doy -= days_in_year_passed_month[is_leap][12]; + year++; + } + + tt.year = year; + + for (month = 11; month >= 0; month--) { + if (doy > days_in_year_passed_month[is_leap][month]) { + tt.month = month + 1; + tt.day = doy - days_in_year_passed_month[is_leap][month]; + break; + } + } + + return tt; +} + +/** @brief Constructor. + * + * Return a null time, which indicates no time has been set. + * This time represents the beginning of the epoch. + */ +struct icaltimetype icaltime_null_time(void) +{ + struct icaltimetype t; + memset(&t,0,sizeof(struct icaltimetype)); + + return t; +} + +/** @brief Constructor. + * + * Return a null date, which indicates no time has been set. + */ +struct icaltimetype icaltime_null_date(void) +{ + struct icaltimetype t; + memset(&t,0,sizeof(struct icaltimetype)); + + t.is_date = 1; + + /* + * Init to -1 to match what icalyacc.y used to do. + * Does anything depend on this? + */ + t.hour = -1; + t.minute = -1; + t.second = -1; + + return t; +} + + +/** + * Returns false if the time is clearly invalid, but is not null. This + * is usually the result of creating a new time type buy not clearing + * it, or setting one of the flags to an illegal value. + */ +int icaltime_is_valid_time(const struct icaltimetype t){ + if(t.is_utc > 1 || t.is_utc < 0 || + t.year < 0 || t.year > 3000 || + t.is_date > 1 || t.is_date < 0){ + return 0; + } else { + return 1; + } + +} + +/** @brief Returns true if time is a DATE + */ +int icaltime_is_date(const struct icaltimetype t) { + + return t.is_date; +} + +/** @brief Returns true if time is relative to UTC zone + * + * @todo We should only check the zone + */ +int icaltime_is_utc(const struct icaltimetype t) { + + return t.is_utc; +} + +/** + * Return true if the time is null. + */ +int icaltime_is_null_time(const struct icaltimetype t) +{ + if (t.second +t.minute+t.hour+t.day+t.month+t.year == 0){ + return 1; + } + + return 0; + +} + +/** + * Return -1, 0, or 1 to indicate that a<b, a==b, or a>b. + * This calls icaltime_compare function after converting them to the utc + * timezone. + */ + +int icaltime_compare(const struct icaltimetype a_in, const struct icaltimetype b_in) +{ + struct icaltimetype a, b; + + a = icaltime_convert_to_zone(a_in, icaltimezone_get_utc_timezone()); + b = icaltime_convert_to_zone(b_in, icaltimezone_get_utc_timezone()); + + if (a.year > b.year) + return 1; + else if (a.year < b.year) + return -1; + + else if (a.month > b.month) + return 1; + else if (a.month < b.month) + return -1; + + else if (a.day > b.day) + return 1; + else if (a.day < b.day) + return -1; + + /* if both are dates, we are done */ + if (a.is_date && b.is_date) + return 0; + + /* else, if only one is a date (and we already know the date part is equal), + then the other is greater */ + else if (b.is_date) + return 1; + else if (a.is_date) + return -1; + + else if (a.hour > b.hour) + return 1; + else if (a.hour < b.hour) + return -1; + + else if (a.minute > b.minute) + return 1; + else if (a.minute < b.minute) + return -1; + + else if (a.second > b.second) + return 1; + else if (a.second < b.second) + return -1; + + return 0; +} + +/** + * like icaltime_compare, but only use the date parts. + */ + +int +icaltime_compare_date_only(const struct icaltimetype a_in, const struct icaltimetype b_in) +{ + struct icaltimetype a, b; + icaltimezone *tz = icaltimezone_get_utc_timezone(); + + a = icaltime_convert_to_zone(a_in, tz); + b = icaltime_convert_to_zone(b_in, tz); + + if (a.year > b.year) + return 1; + else if (a.year < b.year) + return -1; + + if (a.month > b.month) + return 1; + else if (a.month < b.month) + return -1; + + if (a.day > b.day) + return 1; + else if (a.day < b.day) + return -1; + + return 0; +} + +/** + * like icaltime_compare, but only use the date parts; accepts timezone. + */ + +int +icaltime_compare_date_only_tz(const struct icaltimetype a_in, const struct icaltimetype b_in, icaltimezone *tz) +{ + struct icaltimetype a, b; + + a = icaltime_convert_to_zone(a_in, tz); + b = icaltime_convert_to_zone(b_in, tz); + + if (a.year > b.year) + return 1; + else if (a.year < b.year) + return -1; + + if (a.month > b.month) + return 1; + else if (a.month < b.month) + return -1; + + if (a.day > b.day) + return 1; + else if (a.day < b.day) + return -1; + + return 0; +} + +/* These are defined in icalduration.c: +struct icaltimetype icaltime_add(struct icaltimetype t, + struct icaldurationtype d) +struct icaldurationtype icaltime_subtract(struct icaltimetype t1, + struct icaltimetype t2) +*/ + + + +/** @brief Internal, shouldn't be part of the public API + * + * Adds (or subtracts) a time from a icaltimetype. + * NOTE: This function is exactly the same as icaltimezone_adjust_change() + * except for the type of the first parameter. + */ +void +icaltime_adjust(struct icaltimetype *tt, const int days, const int hours, + const int minutes, const int seconds) { + + int second, minute, hour, day; + int minutes_overflow, hours_overflow, days_overflow = 0, years_overflow; + int days_in_month; + + /* If we are passed a date make sure to ignore hour minute and second */ + if (tt->is_date) + goto IS_DATE; + + /* Add on the seconds. */ + second = tt->second + seconds; + tt->second = second % 60; + minutes_overflow = second / 60; + if (tt->second < 0) { + tt->second += 60; + minutes_overflow--; + } + + /* Add on the minutes. */ + minute = tt->minute + minutes + minutes_overflow; + tt->minute = minute % 60; + hours_overflow = minute / 60; + if (tt->minute < 0) { + tt->minute += 60; + hours_overflow--; + } + + /* Add on the hours. */ + hour = tt->hour + hours + hours_overflow; + tt->hour = hour % 24; + days_overflow = hour / 24; + if (tt->hour < 0) { + tt->hour += 24; + days_overflow--; + } + +IS_DATE: + /* Normalize the month. We do this before handling the day since we may + need to know what month it is to get the number of days in it. + Note that months are 1 to 12, so we have to be a bit careful. */ + if (tt->month >= 13) { + years_overflow = (tt->month - 1) / 12; + tt->year += years_overflow; + tt->month -= years_overflow * 12; + } else if (tt->month <= 0) { + /* 0 to -11 is -1 year out, -12 to -23 is -2 years. */ + years_overflow = (tt->month / 12) - 1; + tt->year += years_overflow; + tt->month -= years_overflow * 12; + } + + /* Add on the days. */ + day = tt->day + days + days_overflow; + if (day > 0) { + for (;;) { + days_in_month = icaltime_days_in_month (tt->month, tt->year); + if (day <= days_in_month) + break; + + tt->month++; + if (tt->month >= 13) { + tt->year++; + tt->month = 1; + } + + day -= days_in_month; + } + } else { + while (day <= 0) { + if (tt->month == 1) { + tt->year--; + tt->month = 12; + } else { + tt->month--; + } + + day += icaltime_days_in_month (tt->month, tt->year); + } + } + tt->day = day; +} + +/** @brief Convert time to a given timezone + * + * Convert a time from its native timezone to a given timezone. + * + * If tt is a date, the returned time is an exact + * copy of the input. If it's a floating time, the returned object + * represents the same time translated to the given timezone. + * Otherwise the time will be converted to the new + * time zone, and its native timezone set to the right timezone. + */ +struct icaltimetype icaltime_convert_to_zone(const struct icaltimetype tt, + icaltimezone *zone) { + + struct icaltimetype ret = tt; + + /* If it's a date do nothing */ + if (tt.is_date) { + return ret; + } + + if (tt.zone == zone) { + return ret; + } + + /* If it's a floating time we don't want to adjust the time */ + if (tt.zone != NULL) { + icaltimezone_convert_time(&ret, (icaltimezone *)tt.zone, zone); + } + + ret.zone = zone; + if (zone == icaltimezone_get_utc_timezone()) { + ret.is_utc = 1; + } else { + ret.is_utc = 0; + } + + return ret; +} + +const icaltimezone * +icaltime_get_timezone(const struct icaltimetype t) { + + return t.zone; +} + +const char * +icaltime_get_tzid(const struct icaltimetype t) { + + if (t.zone != NULL) { + return icaltimezone_get_tzid((icaltimezone *)t.zone); + } else { + return NULL; + } +} + +/** @brief Set the timezone + * + * Force the icaltime to be interpreted relative to another timezone. + * If you need to do timezone conversion, applying offset adjustments, + * then you should use icaltime_convert_to_timezone instead. + */ +struct icaltimetype +icaltime_set_timezone(struct icaltimetype *t, const icaltimezone *zone) { + + /* If it's a date do nothing */ + if (t->is_date) { + return *t; + } + + if (t->zone == zone) { + return *t; + } + + t->zone = zone; + if (zone == icaltimezone_get_utc_timezone()) { + t->is_utc = 1; + } else { + t->is_utc = 0; + } + + return *t; +} + + +/** + * @brief builds an icaltimespan given a start time, end time and busy value. + * + * @param dtstart The beginning time of the span, can be a date-time + * or just a date. + * @param dtend The end time of the span. + * @param is_busy A boolean value, 0/1. + * @return A span using the supplied values. + * + * returned span contains times specified in UTC. + */ + +icaltime_span icaltime_span_new(struct icaltimetype dtstart, + struct icaltimetype dtend, + int is_busy) +{ + icaltime_span span; + + span.is_busy = is_busy; + + span.start = icaltime_as_timet_with_zone(dtstart, + dtstart.zone ? dtstart.zone : icaltimezone_get_utc_timezone()); + + if (icaltime_is_null_time(dtend)) { + if (!icaltime_is_date(dtstart)) { + /* If dtstart is a DATE-TIME and there is no DTEND nor DURATION + it takes no time */ + span.end = span.start; + return span; + } else { + dtend = dtstart; + } + } + + span.end = icaltime_as_timet_with_zone(dtend, + dtend.zone ? dtend.zone : icaltimezone_get_utc_timezone()); + + if (icaltime_is_date(dtstart)) { + /* no time specified, go until the end of the day..*/ + span.end += 60*60*24 - 1; + } + return span; +} + + +/** @brief Returns true if the two spans overlap + * + * @param s1 1st span to test + * @param s2 2nd span to test + * @return boolean value + * + * The result is calculated by testing if the start time of s1 is contained + * by the s2 span, or if the end time of s1 is contained by the s2 span. + * + * Also returns true if the spans are equal. + * + * Note, this will return false if the spans are adjacent. + */ + +int icaltime_span_overlaps(icaltime_span *s1, + icaltime_span *s2) +{ + /* s1->start in s2 */ + if (s1->start > s2->start && s1->start < s2->end) + return 1; + + /* s1->end in s2 */ + if (s1->end > s2->start && s1->end < s2->end) + return 1; + + /* s2->start in s1 */ + if (s2->start > s1->start && s2->start < s1->end) + return 1; + + /* s2->end in s1 */ + if (s2->end > s1->start && s2->end < s1->end) + return 1; + + if (s1->start == s2->start && s1->end == s2->end) + return 1; + + return 0; +} + +/** @brief Returns true if the span is totally within the containing + * span + * + * @param s The span to test for. + * @param container The span to test against. + * @return boolean value. + * + */ + +int icaltime_span_contains(icaltime_span *s, + icaltime_span *container) +{ + + if ((s->start >= container->start && s->start < container->end) && + (s->end <= container->end && s->end > container->start)) + return 1; + + return 0; +} |