22 #include "icaltimezones.h"
27 #include <libical/ical.h>
28 #include <libical/icaltimezone.h>
30 #include <ksystemtimezone.h>
31 #include <kdatetime.h>
34 #include <QtCore/QDateTime>
35 #include <QtCore/QString>
36 #include <QtCore/QList>
37 #include <QtCore/QVector>
38 #include <QtCore/QSet>
39 #include <QtCore/QFile>
40 #include <QtCore/QTextStream>
45 static const int minRuleCount = 5;
46 static const int minPhaseCount = 8;
49 static QDateTime toQDateTime(
const icaltimetype &t )
52 QTime( t.hour, t.minute, t.second ),
53 ( t.is_utc ? Qt::UTC : Qt::LocalTime ) );
68 static icaltimetype writeLocalICalDateTime(
const QDateTime &utc,
int offset )
71 icaltimetype t = icaltime_null_time();
89 class ICalTimeZonesPrivate
92 ICalTimeZonesPrivate() {}
98 : d( new ICalTimeZonesPrivate )
114 if ( !zone.isValid() ) {
117 if ( d->zones.find( zone.name() ) != d->zones.end() ) {
121 d->zones.insert( zone.name(),
zone );
127 if ( zone.isValid() ) {
128 for (
ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) {
129 if ( it.value() ==
zone ) {
130 d->zones.erase( it );
142 if ( it != d->zones.end() ) {
160 if ( it != d->zones.constEnd() ) {
176 float latitude,
float longitude,
178 : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment )
182 : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() )
184 Q_UNUSED( earliest );
187 ICalTimeZoneBackend::~ICalTimeZoneBackend()
197 return "ICalTimeZone";
221 tz.latitude(), tz.longitude(),
224 const KTimeZoneData *data = tz.data(
true );
270 if ( !updateBase( other ) ) {
274 KTimeZoneData *otherData = other.data() ? other.data()->clone() : 0;
275 setData( otherData, other.source() );
282 if ( !utcZone.isValid() ) {
284 utcZone = tzs.
parse( icaltimezone_get_utc_timezone() );
292 class ICalTimeZoneDataPrivate
295 ICalTimeZoneDataPrivate() : icalComponent(0) {}
296 ~ICalTimeZoneDataPrivate()
298 if ( icalComponent ) {
299 icalcomponent_free( icalComponent );
302 icalcomponent *component()
const {
return icalComponent; }
303 void setComponent( icalcomponent *c )
305 if ( icalComponent ) {
306 icalcomponent_free( icalComponent );
314 icalcomponent *icalComponent;
319 : d ( new ICalTimeZoneDataPrivate() )
324 : KTimeZoneData( rhs ),
325 d( new ICalTimeZoneDataPrivate() )
327 d->location = rhs.d->location;
329 d->lastModified = rhs.d->lastModified;
330 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
334 const KTimeZone &tz,
const QDate &earliest )
335 : KTimeZoneData( rhs ),
336 d( new ICalTimeZoneDataPrivate() )
341 WEEKDAY_OF_MONTH = 0x02,
342 LAST_WEEKDAY_OF_MONTH = 0x04
345 if ( tz.type() ==
"KSystemTimeZone" ) {
349 icalcomponent *c = 0;
350 KTimeZone ktz = KSystemTimeZones::readZone( tz.name() );
351 if ( ktz.isValid() ) {
352 if ( ktz.data(
true) ) {
355 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
356 icaltimezone_free( itz, 1 );
361 icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() );
362 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
368 icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY );
370 icalvalue *value = icalproperty_get_value( prop );
371 const char *tzid = icalvalue_get_text( value );
373 int len = icalprefix.
size();
374 if ( !strncmp( icalprefix, tzid, len ) ) {
375 const char *s = strchr( tzid + len,
'/' );
378 icalvalue_set_text( value, tzidShort );
381 prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY );
382 const char *xname = icalproperty_get_x_name( prop );
383 if ( xname && !strcmp( xname,
"X-LIC-LOCATION" ) ) {
384 icalcomponent_remove_property( c, prop );
390 d->setComponent( c );
393 icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
394 icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) );
402 for (
int i = 0, end = transits.
count(); i < end; ++i ) {
403 if ( transits[i].time().date() >= earliest ) {
411 int trcount = transits.
count();
413 transitionsDone.
fill(
false);
417 icaldatetimeperiodtype dtperiod;
418 dtperiod.period = icalperiodtype_null_period();
421 for ( ; i < trcount && transitionsDone[i]; ++i ) {
424 if ( i >= trcount ) {
428 int preOffset = ( i > 0 ) ? transits[i - 1].phase().utcOffset() : rhs.previousUtcOffset();
429 KTimeZone::Phase phase = transits[i].phase();
430 if ( phase.utcOffset() == preOffset ) {
431 transitionsDone[i] =
true;
432 while ( ++i < trcount ) {
433 if ( transitionsDone[i] ||
434 transits[i].phase() != phase ||
435 transits[i - 1].phase().utcOffset() != preOffset ) {
438 transitionsDone[i] =
true;
442 icalcomponent *phaseComp =
443 icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT );
445 for (
int a = 0, aend = abbrevs.count(); a < aend; ++a ) {
446 icalcomponent_add_property( phaseComp,
447 icalproperty_new_tzname(
448 static_cast<const char*>( abbrevs[a]) ) );
450 if ( !phase.comment().isEmpty() ) {
451 icalcomponent_add_property( phaseComp,
452 icalproperty_new_comment( phase.comment().toUtf8() ) );
454 icalcomponent_add_property( phaseComp,
455 icalproperty_new_tzoffsetfrom( preOffset ) );
456 icalcomponent_add_property( phaseComp,
457 icalproperty_new_tzoffsetto( phase.utcOffset() ) );
459 icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp );
460 icalcomponent_add_property( phaseComp1,
461 icalproperty_new_dtstart(
462 writeLocalICalDateTime( transits[i].time(), preOffset ) ) );
463 bool useNewRRULE =
false;
469 int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0;
471 int nthFromStart = 0;
479 transitionsDone[i] =
true;
483 rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH;
487 month = date.
month();
490 dayOfMonth = date.
day();
491 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1;
492 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1;
494 if ( ++i >= trcount ) {
498 if ( transitionsDone[i] ||
499 transits[i].phase() != phase ||
500 transits[i - 1].phase().utcOffset() != preOffset ) {
503 transitionsDone[i] =
true;
504 qdt = transits[i].time();
511 if ( qdt.
time() != time ||
512 date.
month() != month ||
513 date.
year() != ++year ) {
516 int day = date.
day();
517 if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) {
518 newRule &= ~DAY_OF_MONTH;
520 if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) {
522 newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH );
524 if ( ( newRule & WEEKDAY_OF_MONTH ) &&
525 ( day - 1 ) / 7 + 1 != nthFromStart ) {
526 newRule &= ~WEEKDAY_OF_MONTH;
528 if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) &&
529 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) {
530 newRule &= ~LAST_WEEKDAY_OF_MONTH;
540 int yr = times[0].date().year();
544 if ( qdt.
time() != time ||
545 date.
month() != month ||
546 date.
year() != --yr ) {
549 int day = date.
day();
550 if ( rule & DAY_OF_MONTH ) {
551 if ( day != dayOfMonth ) {
556 ( ( rule & WEEKDAY_OF_MONTH ) &&
557 ( day - 1 ) / 7 + 1 != nthFromStart ) ||
558 ( ( rule & LAST_WEEKDAY_OF_MONTH ) &&
559 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) {
566 if ( times.
count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) {
568 icalrecurrencetype r;
569 icalrecurrencetype_clear( &r );
570 r.freq = ICAL_YEARLY_RECURRENCE;
571 r.count = ( year >= 2030 ) ? 0 : times.
count() - 1;
572 r.by_month[0] = month;
573 if ( rule & DAY_OF_MONTH ) {
574 r.by_month_day[0] = dayOfMonth;
575 }
else if ( rule & WEEKDAY_OF_MONTH ) {
576 r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 );
577 }
else if ( rule & LAST_WEEKDAY_OF_MONTH ) {
578 r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 );
580 icalproperty *prop = icalproperty_new_rrule( r );
584 icalcomponent *c = icalcomponent_new_clone( phaseComp );
585 icalcomponent_add_property(
586 c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) );
587 icalcomponent_add_property( c, prop );
588 icalcomponent_add_component( tzcomp, c );
590 icalcomponent_add_property( phaseComp1, prop );
594 for (
int t = 0, tend = times.
count() - 1; t < tend; ++t ) {
606 }
while ( i < trcount );
609 for (
int rd = 0, rdend = rdates.
count(); rd < rdend; ++rd ) {
610 dtperiod.
time = writeLocalICalDateTime( rdates[rd], preOffset );
611 icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) );
613 icalcomponent_add_component( tzcomp, phaseComp1 );
614 icalcomponent_free( phaseComp );
617 d->setComponent( tzcomp );
629 if ( &rhs ==
this ) {
633 KTimeZoneData::operator=( rhs );
634 d->location = rhs.d->location;
636 d->lastModified = rhs.d->lastModified;
637 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
658 return d->lastModified;
663 QByteArray result( icalcomponent_as_ical_string( d->component() ) );
664 icalmemory_free_ring();
670 icaltimezone *icaltz = icaltimezone_new();
674 icalcomponent *c = icalcomponent_new_clone( d->component() );
675 if ( !icaltimezone_set_component( icaltz, c ) ) {
676 icalcomponent_free( c );
677 icaltimezone_free( icaltz, 1 );
691 class ICalTimeZoneSourcePrivate
695 int &prevOffset, KTimeZone::Phase & );
699 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
703 : KTimeZoneSource( false ),
714 QFile file( fileName );
715 if ( !file.
open( QIODevice::ReadOnly ) ) {
724 icalcomponent *calendar = icalcomponent_new_from_string( text.
data() );
726 if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) {
727 result =
parse( calendar, zones );
729 icalcomponent_free( calendar );
736 for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );
737 c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) {
739 if ( !zone.isValid() ) {
743 if ( oldzone.isValid() ) {
747 }
else if ( !zones.
add( zone ) ) {
761 icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY );
763 icalproperty_kind kind = icalproperty_isa( p );
766 case ICAL_TZID_PROPERTY:
770 case ICAL_TZURL_PROPERTY:
771 data->d->url = icalproperty_get_tzurl( p );
774 case ICAL_LOCATION_PROPERTY:
779 case ICAL_X_PROPERTY:
781 const char *xname = icalproperty_get_x_name( p );
782 if ( xname && !strcmp( xname,
"X-LIC-LOCATION" ) ) {
787 case ICAL_LASTMODIFIED_PROPERTY:
789 icaltimetype t = icalproperty_get_lastmodified(p);
791 data->d->lastModified = toQDateTime( t );
793 kDebug() <<
"LAST-MODIFIED not UTC";
800 p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY );
804 kDebug() <<
"TZID missing";
808 if ( data->d->location.isEmpty() && !xlocation.
isEmpty() ) {
809 data->d->location = xlocation;
816 name = name.
mid( i + 1 );
829 for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT );
830 c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) )
833 KTimeZone::Phase phase;
835 icalcomponent_kind kind = icalcomponent_isa( c );
838 case ICAL_XSTANDARD_COMPONENT:
840 times = ICalTimeZoneSourcePrivate::parsePhase( c,
false, prevoff, phase );
843 case ICAL_XDAYLIGHT_COMPONENT:
845 times = ICalTimeZoneSourcePrivate::parsePhase( c,
true, prevoff, phase );
849 kDebug() <<
"Unknown component:" << kind;
852 int tcount = times.
count();
855 for (
int t = 0; t < tcount; ++t ) {
856 transitions += KTimeZone::Transition( times[t], phase );
858 if ( !earliest.
isValid() || times[0] < earliest ) {
859 prevOffset = prevoff;
864 data->setPhases( phases, prevOffset );
867 qSort( transitions );
868 for (
int t = 1, tend = transitions.
count(); t < tend; ) {
869 if ( transitions[t].phase() == transitions[t - 1].phase() ) {
876 data->setTransitions( transitions );
878 data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
879 kDebug() <<
"VTIMEZONE" << name;
896 KTimeZone::Phase &phase )
906 bool found_dtstart =
false;
907 bool found_tzoffsetfrom =
false;
908 bool found_tzoffsetto =
false;
909 icaltimetype dtstart = icaltime_null_time();
912 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
914 icalproperty_kind kind = icalproperty_isa( p );
917 case ICAL_TZNAME_PROPERTY:
923 QByteArray tzname = icalproperty_get_tzname( p );
926 if ( ( !daylight && tzname ==
"Standard Time" ) ||
927 ( daylight && tzname ==
"Daylight Time" ) ) {
930 if ( !abbrevs.
contains( tzname ) ) {
935 case ICAL_DTSTART_PROPERTY:
936 dtstart = icalproperty_get_dtstart( p );
937 found_dtstart =
true;
940 case ICAL_TZOFFSETFROM_PROPERTY:
941 prevOffset = icalproperty_get_tzoffsetfrom( p );
942 found_tzoffsetfrom =
true;
945 case ICAL_TZOFFSETTO_PROPERTY:
946 utcOffset = icalproperty_get_tzoffsetto( p );
947 found_tzoffsetto =
true;
950 case ICAL_COMMENT_PROPERTY:
954 case ICAL_RDATE_PROPERTY:
955 case ICAL_RRULE_PROPERTY:
960 kDebug() <<
"Unknown property:" << kind;
963 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
967 if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) {
968 kDebug() <<
"DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
973 QDateTime localStart = toQDateTime( dtstart );
974 dtstart.second -= prevOffset;
976 QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) );
978 transitions += utcStart;
985 KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() );
986 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
988 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
990 icalproperty_kind kind = icalproperty_isa( p );
993 case ICAL_RDATE_PROPERTY:
995 icaltimetype t = icalproperty_get_rdate(p).time;
996 if ( icaltime_is_date( t ) ) {
998 t.hour = dtstart.hour;
999 t.minute = dtstart.minute;
1000 t.second = dtstart.second;
1007 t.second -= prevOffset;
1009 t = icaltime_normalize( t );
1011 transitions += toQDateTime( t );
1014 case ICAL_RRULE_PROPERTY:
1019 impl.readRecurrence( icalproperty_get_rrule( p ), &r );
1024 KDateTime end( r.
endDt() );
1025 if ( end.timeSpec() == KDateTime::Spec::UTC() ) {
1026 end.setTimeSpec( KDateTime::Spec::ClockTime() );
1027 r.
setEndDt( end.addSecs( prevOffset ) );
1031 for (
int i = 0, end = dts.
count(); i < end; ++i ) {
1034 transitions += utc.
addSecs( -prevOffset );
1041 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1043 qSortUnique( transitions );
1046 phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment );
1053 if ( !icalBuiltIn ) {
1062 tzid = zone.
mid( i + 1 );
1065 KTimeZone ktz = KSystemTimeZones::readZone( tzid );
1066 if ( ktz.isValid() ) {
1067 if ( ktz.data(
true ) ) {
1077 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
1080 icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName );
1085 return parse( icaltz );
1090 if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) {
1091 icaltimezone *icaltz = icaltimezone_get_builtin_timezone(
"Europe/London" );
1092 QByteArray tzid = icaltimezone_get_tzid( icaltz );
1093 if ( tzid.
right( 13 ) ==
"Europe/London" ) {
1094 int i = tzid.
indexOf(
'/', 1 );
1096 ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.
left( i + 1 );
1097 return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1100 kError() <<
"failed to get libical TZID prefix";
1102 return ICalTimeZoneSourcePrivate::icalTzidPrefix;
void setCodec(QTextCodec *codec)
icaltimezone * icalTimezone() const
Returns the ICal timezone structure which represents this time zone.
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
virtual KTimeZoneBackend * clone() const
Creates a copy of this instance.
QByteArray url() const
Returns the URL of the published VTIMEZONE definition, if any.
virtual ~ICalTimeZoneData()
Destructor.
ICalTimeZone()
Constructs a null time zone.
QVector< T > & fill(const T &value, int size)
ICalTimeZoneBackend()
Implements ICalTimeZone::ICalTimeZone().
QByteArray url() const
Returns the URL of the published VTIMEZONE definition, if any.
virtual QByteArray type() const
Returns the class name of the data represented by this instance.
ICalTimeZone parse(icalcomponent *vtimezone)
Creates an ICalTimeZone instance containing the detailed information parsed from a VTIMEZONE componen...
iterator erase(iterator pos)
bool update(const ICalTimeZone &other)
Update the definition of the time zone to be identical to another ICalTimeZone instance.
static QByteArray icalTzidPrefix()
Returns the prefix string used in the TZID field in built-in libical time zones.
icaltimezone * icalTimezone() const
Returns the ICal timezone structure which represents this time zone.
ICalTimeZoneSource()
Constructs an iCalendar time zone source.
virtual ~ICalTimeZone()
Destructor.
virtual KTimeZoneData * clone() const
Creates a new copy of this object.
void setTimeSpec(Qt::TimeSpec spec)
int indexOf(char ch, int from) const
ICalTimeZone zone(const QString &name) const
Returns the time zone with the given name.
int count(const T &value) const
QString fromUtf8(const char *str, int size)
~ICalTimeZones()
Destructor.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
QByteArray right(int len) const
bool add(const ICalTimeZone &zone)
Adds a time zone to the collection.
void clear()
Clears the collection.
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
const ZoneMap zones() const
Returns all the time zones defined in this collection.
QDateTime lastModified() const
Returns the LAST-MODIFIED time of the VTIMEZONE, if any.
ICalTimeZones()
Constructs an empty time zone collection.
Backend class for KICalTimeZone class.
bool contains(const T &value) const
virtual ~ICalTimeZoneSource()
Destructor.
The ICalTimeZones class represents a time zone database which consists of a collection of individual ...
DateTimeList timesInInterval(const KDateTime &start, const KDateTime &end) const
Returns a list of all the times at which the recurrence will occur between two specified times...
ICalTimeZone remove(const ICalTimeZone &zone)
Removes a time zone from the collection.
virtual bool hasTransitions() const
Return whether daylight saving transitions are available for the time zone.
QByteArray left(int len) const
ICalTimeZoneData()
Default constructor.
QByteArray toLatin1() const
A QList which can be sorted.
QString mid(int position, int n) const
A class which reads and parses iCalendar VTIMEZONE components, and accesses libical time zone data...
ICalTimeZoneData & operator=(const ICalTimeZoneData &rhs)
Assignment operator.
virtual bool hasTransitions(const KTimeZone *caller) const
Implements ICalTimeZone::hasTransitions().
Parsed iCalendar VTIMEZONE data.
KDateTime endDt(bool *result=0) const
Returns the date and time of the last recurrence.
QString city() const
Returns the name of the city for this time zone, if any.
void prepend(const T &value)
QByteArray vtimezone() const
Returns the VTIMEZONE string which represents this time zone.
void setEndDt(const KDateTime &endDateTime)
Sets the date and time of the last recurrence.
QDateTime addSecs(int s) const
static ICalTimeZone utc()
Returns a standard UTC time zone, with name "UTC".
QByteArray vtimezone() const
Returns the VTIMEZONE string which represents this time zone.
int duration() const
Returns -1 if the event recurs infinitely, 0 if the end date is set, otherwise the total number of re...
QString city() const
Returns the name of the city for this time zone, if any.
QDateTime lastModified() const
Returns the LAST-MODIFIED time of the VTIMEZONE, if any.
This class represents a recurrence rule for a calendar incidence.
ICalTimeZone standardZone(const QString &zone, bool icalBuiltIn=false)
Creates an ICalTimeZone instance for a standard time zone.
The ICalTimeZone class represents an iCalendar VTIMEZONE component.
void setStartDt(const KDateTime &start)
Sets the recurrence start date/time.
This class represents a recurrence rule for a calendar incidence.
QByteArray toUtf8() const