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 ( t.is_utc ? 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();
89 class ICalTimeZonesPrivate
92 ICalTimeZonesPrivate() {}
93 ICalTimeZones::ZoneMap zones;
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 );
140 if ( !name.isEmpty() ) {
141 ZoneMap::Iterator it = d->zones.find( name );
142 if ( it != d->zones.end() ) {
158 if ( !name.isEmpty() ) {
159 ZoneMap::ConstIterator it = d->zones.constFind( name );
160 if ( it != d->zones.constEnd() ) {
175 const QString &countryCode,
176 float latitude,
float longitude,
177 const QString &comment )
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 );
241 return dat ? dat->
city() : QString();
247 return dat ? dat->
url() : QByteArray();
259 return dat ? dat->
vtimezone() : QByteArray();
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 );
312 QDateTime lastModified;
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,
'/' );
377 QByteArray tzidShort( s + 1 );
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() ) );
399 QList<KTimeZone::Transition> transits = transitions();
400 if ( earliest.isValid() ) {
402 for (
int i = 0, end = transits.count(); i < end; ++i ) {
403 if ( transits[i].time().date() >= earliest ) {
405 transits.erase( transits.begin(), transits.begin() + i );
411 int trcount = transits.count();
412 QVector<bool> transitionsDone(trcount);
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 );
444 QList<QByteArray> abbrevs = phase.abbreviations();
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;
475 QList<QDateTime> rdates;
476 QList<QDateTime> times;
477 QDateTime qdt = transits[i].time();
479 transitionsDone[i] =
true;
483 rule = DAY_OF_MONTH | WEEKDAY_OF_MONTH | LAST_WEEKDAY_OF_MONTH;
487 month = date.month();
488 daysInMonth = date.daysInMonth();
489 dayOfWeek = date.dayOfWeek();
490 dayOfMonth = date.day();
491 nthFromStart = ( dayOfMonth - 1 ) / 7 + 1;
492 nthFromEnd = ( daysInMonth - dayOfMonth ) / 7 + 1;
494 if ( ++i >= trcount ) {
496 times += QDateTime();
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();
505 if ( !qdt.isValid() ) {
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 ) ) {
521 if ( date.dayOfWeek() != dayOfWeek ) {
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();
541 while ( !rdates.isEmpty() ) {
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 ) {
555 if ( date.dayOfWeek() != dayOfWeek ||
556 ( ( rule & WEEKDAY_OF_MONTH ) &&
557 ( day - 1 ) / 7 + 1 != nthFromStart ) ||
558 ( ( rule & LAST_WEEKDAY_OF_MONTH ) &&
559 ( daysInMonth - day ) / 7 + 1 != nthFromEnd ) ) {
563 times.prepend( qdt );
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
694 static QList<QDateTime> parsePhase( icalcomponent *,
bool daylight,
695 int &prevOffset, KTimeZone::Phase & );
696 static QByteArray icalTzidPrefix;
699 QByteArray ICalTimeZoneSourcePrivate::icalTzidPrefix;
703 : KTimeZoneSource( false ),
714 QFile file( fileName );
715 if ( !file.open( QIODevice::ReadOnly ) ) {
718 QTextStream ts( &file );
719 ts.setCodec(
"ISO 8859-1" );
720 QByteArray text = ts.readAll().trimmed().toLatin1();
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:
767 name = QString::fromUtf8( icalproperty_get_tzid( p ) );
770 case ICAL_TZURL_PROPERTY:
771 data->d->url = icalproperty_get_tzurl( p );
774 case ICAL_LOCATION_PROPERTY:
776 data->d->location = QString::fromUtf8( icalproperty_get_location( p ) );
779 case ICAL_X_PROPERTY:
781 const char *xname = icalproperty_get_x_name( p );
782 if ( xname && !strcmp( xname,
"X-LIC-LOCATION" ) ) {
783 xlocation = QString::fromUtf8( icalproperty_get_x( p ) );
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 );
803 if ( name.isEmpty() ) {
804 kDebug() <<
"TZID missing";
808 if ( data->d->location.isEmpty() && !xlocation.isEmpty() ) {
809 data->d->location = xlocation;
812 if ( name.startsWith( prefix ) ) {
814 int i = name.indexOf(
'/', prefix.length() );
816 name = name.mid( i + 1 );
826 QList<KTimeZone::Transition> transitions;
828 QList<KTimeZone::Phase> phases;
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;
834 QList<QDateTime> times;
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() ) {
870 transitions.removeAt( t );
876 data->setTransitions( transitions );
878 data->d->setComponent( icalcomponent_new_clone( vtimezone ) );
879 kDebug() <<
"VTIMEZONE" << name;
893 QList<QDateTime> ICalTimeZoneSourcePrivate::parsePhase( icalcomponent *c,
896 KTimeZone::Phase &phase )
898 QList<QDateTime> transitions;
901 QList<QByteArray> abbrevs;
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:
951 comment = QString::fromUtf8( icalproperty_get_comment( p ) );
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 ) {
1032 QDateTime utc = dts[i].dateTime();
1033 utc.setTimeSpec( Qt::UTC );
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 ) {
1057 QString tzid = zone;
1059 if ( zone.startsWith( prefix ) ) {
1060 int i = zone.indexOf(
'/', prefix.length() );
1062 tzid = zone.mid( i + 1 );
1065 KTimeZone ktz = KSystemTimeZones::readZone( tzid );
1066 if ( ktz.isValid() ) {
1067 if ( ktz.data(
true ) ) {
1076 QByteArray zoneName = zone.toUtf8();
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;