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 )
51 return QDateTime( QDate( t.year, t.month, t.day ),
52 QTime( t.hour, t.minute, t.second ),
53 ( icaltime_is_utc( t ) ? Qt::UTC : Qt::LocalTime ) );
59 static QDateTime MAX_DATE()
62 if ( !dt.isValid() ) {
63 dt = QDateTime( QDate::currentDate().addYears( 20 ), QTime( 0, 0, 0 ) );
68 static icaltimetype writeLocalICalDateTime(
const QDateTime &utc,
int offset )
70 QDateTime local = utc.addSecs( offset );
71 icaltimetype t = icaltime_null_time();
72 t.year = local.date().year();
73 t.month = local.date().month();
74 t.day = local.date().day();
75 t.hour = local.time().hour();
76 t.minute = local.time().minute();
77 t.second = local.time().second();
88 class ICalTimeZonesPrivate
91 ICalTimeZonesPrivate() {}
92 ICalTimeZones::ZoneMap zones;
97 : d( new ICalTimeZonesPrivate )
113 if ( !
zone.isValid() ) {
116 if ( d->zones.find(
zone.name() ) != d->zones.end() ) {
120 d->zones.insert(
zone.name(),
zone );
126 if (
zone.isValid() ) {
127 for ( ZoneMap::Iterator it = d->zones.begin(), end = d->zones.end(); it != end; ++it ) {
128 if ( it.value() ==
zone ) {
129 d->zones.erase( it );
139 if ( !name.isEmpty() ) {
140 ZoneMap::Iterator it = d->zones.find( name );
141 if ( it != d->zones.end() ) {
157 if ( !name.isEmpty() ) {
158 ZoneMap::ConstIterator it = d->zones.constFind( name );
159 if ( it != d->zones.constEnd() ) {
174 const QString &countryCode,
175 float latitude,
float longitude,
176 const QString &comment )
177 : KTimeZoneBackend( source, name, countryCode, latitude, longitude, comment )
181 : KTimeZoneBackend( 0, tz.name(), tz.countryCode(), tz.latitude(), tz.longitude(), tz.comment() )
183 Q_UNUSED( earliest );
186 ICalTimeZoneBackend::~ICalTimeZoneBackend()
196 return "ICalTimeZone";
220 tz.latitude(), tz.longitude(),
223 const KTimeZoneData *data = tz.data(
true );
240 return dat ? dat->
city() : QString();
246 return dat ? dat->
url() : QByteArray();
258 return dat ? dat->
vtimezone() : QByteArray();
269 if ( !updateBase( other ) ) {
273 KTimeZoneData *otherData = other.data() ? other.data()->clone() : 0;
274 setData( otherData, other.source() );
281 if ( !utcZone.isValid() ) {
283 utcZone = tzs.
parse( icaltimezone_get_utc_timezone() );
291 class ICalTimeZoneDataPrivate
294 ICalTimeZoneDataPrivate() : icalComponent(0) {}
295 ~ICalTimeZoneDataPrivate()
297 if ( icalComponent ) {
298 icalcomponent_free( icalComponent );
301 icalcomponent *component()
const {
return icalComponent; }
302 void setComponent( icalcomponent *c )
304 if ( icalComponent ) {
305 icalcomponent_free( icalComponent );
311 QDateTime lastModified;
313 icalcomponent *icalComponent;
318 : d ( new ICalTimeZoneDataPrivate() )
323 : KTimeZoneData( rhs ),
324 d( new ICalTimeZoneDataPrivate() )
326 d->location = rhs.d->location;
328 d->lastModified = rhs.d->lastModified;
329 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
333 const KTimeZone &tz,
const QDate &earliest )
334 : KTimeZoneData( rhs ),
335 d( new ICalTimeZoneDataPrivate() )
340 WEEKDAY_OF_MONTH = 0x02,
341 LAST_WEEKDAY_OF_MONTH = 0x04
344 if ( tz.type() ==
"KSystemTimeZone" ) {
348 icalcomponent *c = 0;
349 KTimeZone ktz = KSystemTimeZones::readZone( tz.name() );
350 if ( ktz.isValid() ) {
351 if ( ktz.data(
true) ) {
354 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
355 icaltimezone_free( itz, 1 );
360 icaltimezone *itz = icaltimezone_get_builtin_timezone( tz.name().toUtf8() );
361 c = icalcomponent_new_clone( icaltimezone_get_component( itz ) );
367 icalproperty *prop = icalcomponent_get_first_property( c, ICAL_TZID_PROPERTY );
369 icalvalue *value = icalproperty_get_value( prop );
370 const char *tzid = icalvalue_get_text( value );
372 int len = icalprefix.size();
373 if ( !strncmp( icalprefix, tzid, len ) ) {
374 const char *s = strchr( tzid + len,
'/' );
376 QByteArray tzidShort( s + 1 );
377 icalvalue_set_text( value, tzidShort );
380 prop = icalcomponent_get_first_property( c, ICAL_X_PROPERTY );
381 const char *xname = icalproperty_get_x_name( prop );
382 if ( xname && !strcmp( xname,
"X-LIC-LOCATION" ) ) {
383 icalcomponent_remove_property( c, prop );
389 d->setComponent( c );
392 icalcomponent *tzcomp = icalcomponent_new(ICAL_VTIMEZONE_COMPONENT);
393 icalcomponent_add_property( tzcomp, icalproperty_new_tzid( tz.name().toUtf8() ) );
398 QList<KTimeZone::Transition> transits = transitions();
399 if ( earliest.isValid() ) {
401 for (
int i = 0, end = transits.count(); i < end; ++i ) {
402 if ( transits[i].time().date() >= earliest ) {
404 transits.erase( transits.begin(), transits.begin() + i );
410 int trcount = transits.count();
411 QVector<bool> transitionsDone(trcount);
412 transitionsDone.fill(
false);
416 icaldatetimeperiodtype dtperiod;
417 dtperiod.period = icalperiodtype_null_period();
420 for ( ; i < trcount && transitionsDone[i]; ++i ) {
423 if ( i >= trcount ) {
427 int preOffset = ( i > 0 ) ? transits[i - 1].phase().utcOffset() : rhs.previousUtcOffset();
428 KTimeZone::Phase phase = transits[i].phase();
429 if ( phase.utcOffset() == preOffset ) {
430 transitionsDone[i] =
true;
431 while ( ++i < trcount ) {
432 if ( transitionsDone[i] ||
433 transits[i].phase() != phase ||
434 transits[i - 1].phase().utcOffset() != preOffset ) {
437 transitionsDone[i] =
true;
441 icalcomponent *phaseComp =
442 icalcomponent_new( phase.isDst() ? ICAL_XDAYLIGHT_COMPONENT : ICAL_XSTANDARD_COMPONENT );
443 QList<QByteArray> abbrevs = phase.abbreviations();
444 for (
int a = 0, aend = abbrevs.count(); a < aend; ++a ) {
445 icalcomponent_add_property( phaseComp,
446 icalproperty_new_tzname(
447 static_cast<const char*
>( abbrevs[a]) ) );
449 if ( !phase.comment().isEmpty() ) {
450 icalcomponent_add_property( phaseComp,
451 icalproperty_new_comment( phase.comment().toUtf8() ) );
453 icalcomponent_add_property( phaseComp,
454 icalproperty_new_tzoffsetfrom( preOffset ) );
455 icalcomponent_add_property( phaseComp,
456 icalproperty_new_tzoffsetto( phase.utcOffset() ) );
458 icalcomponent *phaseComp1 = icalcomponent_new_clone( phaseComp );
459 icalcomponent_add_property( phaseComp1,
460 icalproperty_new_dtstart(
461 writeLocalICalDateTime( transits[i].time(), preOffset ) ) );
462 bool useNewRRULE =
false;
468 int year = 0, month = 0, daysInMonth = 0, dayOfMonth = 0;
470 int nthFromStart = 0;
474 QList<QDateTime> rdates;
475 QList<QDateTime> times;
476 QDateTime qdt = transits[i].time();
478 transitionsDone[i] =
true;
482 rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH;
486 month = date.month();
487 daysInMonth = date.daysInMonth();
488 dayOfWeek = date.dayOfWeek();
489 dayOfMonth = date.day();
490 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1;
491 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1;
493 if ( ++i >= trcount ) {
495 times += QDateTime();
497 if ( transitionsDone[i] ||
498 transits[i].phase() != phase ||
499 transits[i - 1].phase().utcOffset() != preOffset ) {
502 transitionsDone[i] =
true;
503 qdt = transits[i].time();
504 if ( !qdt.isValid() ) {
510 if ( qdt.time() != time ||
511 date.month() != month ||
512 date.year() != ++year ) {
515 int day = date.day();
516 if ( ( newRule & DAY_OF_MONTH ) && day != dayOfMonth ) {
517 newRule &= ~DAY_OF_MONTH;
519 if ( newRule & ( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH ) ) {
520 if ( date.dayOfWeek() != dayOfWeek ) {
521 newRule &= ~( WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH );
523 if ( ( newRule & WEEKDAY_OF_MONTH ) &&
524 ( day - 1 ) / 7 + 1 != nthFromStart ) {
525 newRule &= ~WEEKDAY_OF_MONTH;
527 if ( ( newRule & LAST_WEEKDAY_OF_MONTH ) &&
528 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) {
529 newRule &= ~LAST_WEEKDAY_OF_MONTH;
539 int yr = times[0].date().year();
540 while ( !rdates.isEmpty() ) {
543 if ( qdt.time() != time ||
544 date.month() != month ||
545 date.year() != --yr ) {
548 int day = date.day();
549 if ( rule & DAY_OF_MONTH ) {
550 if ( day != dayOfMonth ) {
554 if ( date.dayOfWeek() != dayOfWeek ||
555 ( ( rule & WEEKDAY_OF_MONTH ) &&
556 ( day - 1 ) / 7 + 1 != nthFromStart ) ||
557 ( ( rule & LAST_WEEKDAY_OF_MONTH ) &&
558 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) {
562 times.prepend( qdt );
565 if ( times.count() > ( useNewRRULE ? minPhaseCount : minRuleCount ) ) {
567 icalrecurrencetype r;
568 icalrecurrencetype_clear( &r );
569 r.freq = ICAL_YEARLY_RECURRENCE;
570 r.count = ( year >= 2030 ) ? 0 : times.count() - 1;
571 r.by_month[0] = month;
572 if ( rule & DAY_OF_MONTH ) {
573 r.by_month_day[0] = dayOfMonth;
574 }
else if ( rule & WEEKDAY_OF_MONTH ) {
575 r.by_day[0] = ( dayOfWeek % 7 + 1 ) + ( nthFromStart * 8 );
576 }
else if ( rule & LAST_WEEKDAY_OF_MONTH ) {
577 r.by_day[0] = -( dayOfWeek % 7 + 1 ) - ( nthFromEnd * 8 );
579 icalproperty *prop = icalproperty_new_rrule( r );
583 icalcomponent *c = icalcomponent_new_clone( phaseComp );
584 icalcomponent_add_property(
585 c, icalproperty_new_dtstart( writeLocalICalDateTime( times[0], preOffset ) ) );
586 icalcomponent_add_property( c, prop );
587 icalcomponent_add_component( tzcomp, c );
589 icalcomponent_add_property( phaseComp1, prop );
593 for (
int t = 0, tend = times.count() - 1; t < tend; ++t ) {
605 }
while ( i < trcount );
608 for (
int rd = 0, rdend = rdates.count(); rd < rdend; ++rd ) {
609 dtperiod.time = writeLocalICalDateTime( rdates[rd], preOffset );
610 icalcomponent_add_property( phaseComp1, icalproperty_new_rdate( dtperiod ) );
612 icalcomponent_add_component( tzcomp, phaseComp1 );
613 icalcomponent_free( phaseComp );
616 d->setComponent( tzcomp );
628 if ( &rhs ==
this ) {
632 KTimeZoneData::operator=( rhs );
633 d->location = rhs.d->location;
635 d->lastModified = rhs.d->lastModified;
636 d->setComponent( icalcomponent_new_clone( rhs.d->component() ) );
657 return d->lastModified;
662 QByteArray result( icalcomponent_as_ical_string( d->component() ) );
663 icalmemory_free_ring();
669 icaltimezone *icaltz = icaltimezone_new();
673 icalcomponent *c = icalcomponent_new_clone( d->component() );
674 if ( !icaltimezone_set_component( icaltz, c ) ) {
675 icalcomponent_free( c );
676 icaltimezone_free( icaltz, 1 );
690 class ICalTimeZoneSourcePrivate
693 static QList<QDateTime> parsePhase( icalcomponent *,
bool daylight,
694 int &prevOffset, KTimeZone::Phase & );
695 static QByteArray icalTzidPrefix;
698 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
702 : KTimeZoneSource( false ),
713 QFile file( fileName );
714 if ( !file.open( QIODevice::ReadOnly ) ) {
717 QTextStream ts( &file );
718 ts.setCodec(
"ISO 8859-1" );
719 QByteArray text = ts.readAll().trimmed().toLatin1();
723 icalcomponent *calendar = icalcomponent_new_from_string( text.data() );
725 if ( icalcomponent_isa( calendar ) == ICAL_VCALENDAR_COMPONENT ) {
726 result =
parse( calendar, zones );
728 icalcomponent_free( calendar );
735 for ( icalcomponent *c = icalcomponent_get_first_component( calendar, ICAL_VTIMEZONE_COMPONENT );
736 c; c = icalcomponent_get_next_component( calendar, ICAL_VTIMEZONE_COMPONENT ) ) {
738 if ( !zone.isValid() ) {
742 if ( oldzone.isValid() ) {
746 }
else if ( !zones.
add( zone ) ) {
760 icalproperty *p = icalcomponent_get_first_property( vtimezone, ICAL_ANY_PROPERTY );
762 icalproperty_kind kind = icalproperty_isa( p );
765 case ICAL_TZID_PROPERTY:
766 name = QString::fromUtf8( icalproperty_get_tzid( p ) );
769 case ICAL_TZURL_PROPERTY:
770 data->d->url = icalproperty_get_tzurl( p );
773 case ICAL_LOCATION_PROPERTY:
775 data->d->location = QString::fromUtf8( icalproperty_get_location( p ) );
778 case ICAL_X_PROPERTY:
780 const char *xname = icalproperty_get_x_name( p );
781 if ( xname && !strcmp( xname,
"X-LIC-LOCATION" ) ) {
782 xlocation = QString::fromUtf8( icalproperty_get_x( p ) );
786 case ICAL_LASTMODIFIED_PROPERTY:
788 icaltimetype t = icalproperty_get_lastmodified(p);
789 if ( icaltime_is_utc( t ) ) {
790 data->d->lastModified = toQDateTime( t );
792 kDebug() <<
"LAST-MODIFIED not UTC";
799 p = icalcomponent_get_next_property( vtimezone, ICAL_ANY_PROPERTY );
802 if ( name.isEmpty() ) {
803 kDebug() <<
"TZID missing";
807 if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) {
808 data->d->location = xlocation;
811 if ( name.startsWith( prefix ) ) {
813 int i = name.indexOf(
'/', prefix.length() );
815 name = name.mid( i + 1 );
825 QList<KTimeZone::Transition> transitions;
827 QList<KTimeZone::Phase> phases;
828 for ( icalcomponent *c = icalcomponent_get_first_component( vtimezone, ICAL_ANY_COMPONENT );
829 c; c = icalcomponent_get_next_component( vtimezone, ICAL_ANY_COMPONENT ) )
832 KTimeZone::Phase phase;
833 QList<QDateTime> times;
834 icalcomponent_kind kind = icalcomponent_isa( c );
837 case ICAL_XSTANDARD_COMPONENT:
839 times = ICalTimeZoneSourcePrivate::parsePhase( c,
false, prevoff, phase );
842 case ICAL_XDAYLIGHT_COMPONENT:
844 times = ICalTimeZoneSourcePrivate::parsePhase( c,
true, prevoff, phase );
848 kDebug() <<
"Unknown component:" << kind;
851 int tcount = times.count();
854 for (
int t = 0; t < tcount; ++t ) {
855 transitions += KTimeZone::Transition( times[t], phase );
857 if ( !earliest.isValid() || times[0] < earliest ) {
858 prevOffset = prevoff;
863 data->setPhases( phases, prevOffset );
866 qSort( transitions );
867 for (
int t = 1, tend = transitions.count(); t < tend; ) {
868 if ( transitions[t].phase() == transitions[t - 1].phase() ) {
869 transitions.removeAt( t );
875 data->setTransitions( transitions );
877 data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
878 kDebug() <<
"VTIMEZONE" << name;
892 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c,
895 KTimeZone::Phase &phase )
897 QList<QDateTime> transitions;
900 QList<QByteArray> abbrevs;
905 bool found_dtstart =
false;
906 bool found_tzoffsetfrom =
false;
907 bool found_tzoffsetto =
false;
908 icaltimetype dtstart = icaltime_null_time();
911 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
913 icalproperty_kind kind = icalproperty_isa( p );
916 case ICAL_TZNAME_PROPERTY:
922 QByteArray tzname = icalproperty_get_tzname( p );
925 if ( ( !daylight && tzname ==
"Standard Time" ) ||
926 ( daylight && tzname ==
"Daylight Time" ) ) {
929 if ( !abbrevs.contains( tzname ) ) {
934 case ICAL_DTSTART_PROPERTY:
935 dtstart = icalproperty_get_dtstart( p );
936 found_dtstart =
true;
939 case ICAL_TZOFFSETFROM_PROPERTY:
940 prevOffset = icalproperty_get_tzoffsetfrom( p );
941 found_tzoffsetfrom =
true;
944 case ICAL_TZOFFSETTO_PROPERTY:
945 utcOffset = icalproperty_get_tzoffsetto( p );
946 found_tzoffsetto =
true;
949 case ICAL_COMMENT_PROPERTY:
950 comment = QString::fromUtf8( icalproperty_get_comment( p ) );
953 case ICAL_RDATE_PROPERTY:
954 case ICAL_RRULE_PROPERTY:
959 kDebug() <<
"Unknown property:" << kind;
962 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
966 if ( !found_dtstart || !found_tzoffsetfrom || !found_tzoffsetto ) {
967 kDebug() <<
"DTSTART/TZOFFSETFROM/TZOFFSETTO missing";
972 QDateTime localStart = toQDateTime( dtstart );
973 dtstart.second -= prevOffset;
974 dtstart.zone = icaltimezone_get_utc_timezone();
975 QDateTime utcStart = toQDateTime( icaltime_normalize( dtstart ) );
977 transitions += utcStart;
984 KDateTime klocalStart( localStart, KDateTime::Spec::ClockTime() );
985 KDateTime maxTime( MAX_DATE(), KDateTime::Spec::ClockTime() );
987 icalproperty *p = icalcomponent_get_first_property( c, ICAL_ANY_PROPERTY );
989 icalproperty_kind kind = icalproperty_isa( p );
992 case ICAL_RDATE_PROPERTY:
994 icaltimetype t = icalproperty_get_rdate(p).time;
995 if ( icaltime_is_date( t ) ) {
997 t.hour = dtstart.hour;
998 t.minute = dtstart.minute;
999 t.second = dtstart.second;
1005 if ( !icaltime_is_utc( t ) ) {
1006 t.second -= prevOffset;
1007 t.zone = icaltimezone_get_utc_timezone();
1008 t = icaltime_normalize( t );
1010 transitions += toQDateTime( t );
1013 case ICAL_RRULE_PROPERTY:
1018 impl.readRecurrence( icalproperty_get_rrule( p ), &r );
1023 KDateTime end( r.
endDt() );
1024 if ( end.timeSpec() == KDateTime::Spec::UTC() ) {
1025 end.setTimeSpec( KDateTime::Spec::ClockTime() );
1026 r.
setEndDt( end.addSecs( prevOffset ) );
1030 for (
int i = 0, end = dts.count(); i < end; ++i ) {
1031 QDateTime utc = dts[i].dateTime();
1032 utc.setTimeSpec( Qt::UTC );
1033 transitions += utc.addSecs( -prevOffset );
1040 p = icalcomponent_get_next_property( c, ICAL_ANY_PROPERTY );
1042 qSortUnique( transitions );
1045 phase = KTimeZone::Phase( utcOffset, abbrevs, daylight, comment );
1052 if ( !icalBuiltIn ) {
1056 QString tzid = zone;
1058 if ( zone.startsWith( prefix ) ) {
1059 int i = zone.indexOf(
'/', prefix.length() );
1061 tzid = zone.mid( i + 1 );
1064 KTimeZone ktz = KSystemTimeZones::readZone( tzid );
1065 if ( ktz.isValid() ) {
1066 if ( ktz.data(
true ) ) {
1075 QByteArray zoneName = zone.toUtf8();
1076 icaltimezone *icaltz = icaltimezone_get_builtin_timezone( zoneName );
1079 icaltz = icaltimezone_get_builtin_timezone_from_tzid( zoneName );
1084 return parse( icaltz );
1089 if ( ICalTimeZoneSourcePrivate::icalTzidPrefix.isEmpty() ) {
1090 icaltimezone *icaltz = icaltimezone_get_builtin_timezone(
"Europe/London" );
1091 QByteArray tzid = icaltimezone_get_tzid( icaltz );
1092 if ( tzid.right( 13 ) ==
"Europe/London" ) {
1093 int i = tzid.indexOf(
'/', 1 );
1095 ICalTimeZoneSourcePrivate::icalTzidPrefix = tzid.left( i + 1 );
1096 return ICalTimeZoneSourcePrivate::icalTzidPrefix;
1099 kError() <<
"failed to get libical TZID prefix";
1101 return ICalTimeZoneSourcePrivate::icalTzidPrefix;