1/*
2 This file is part of the kcal library.
3
4 Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
5 Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
6
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 This library 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 GNU
15 Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with this library; see the file COPYING.LIB. If not, write to
19 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22/**
23 @file
24 This file is part of the API for handling calendar data and
25 defines the FreeBusy class.
26
27 @brief
28 Provides information about the free/busy time of a calendar user.
29
30 @author Cornelius Schumacher \<schumacher@kde.org\>
31 @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
32*/
33
34#include "freebusy.h"
35#include "calendar.h"
36#include "event.h"
37
38#include <kdebug.h>
39#include <klocalizedstring.h>
40
41using namespace KCal;
42
43//@cond PRIVATE
44class KCal::FreeBusy::Private
45{
46 private:
47 FreeBusy *const q;
48 public:
49 Private( FreeBusy *qq ) : q( qq )
50 {}
51
52 Private( const KCal::FreeBusy::Private &other, FreeBusy *qq ) : q( qq )
53 { init( other ); }
54
55 Private( const FreeBusyPeriod::List &busyPeriods, FreeBusy *qq )
56 : q( qq ), mBusyPeriods( busyPeriods )
57 {}
58
59 void init( const KCal::FreeBusy::Private &other );
60 void init( const Event::List &events, const KDateTime &start, const KDateTime &end );
61
62 KDateTime mDtEnd; // end datetime
63 FreeBusyPeriod::List mBusyPeriods; // list of periods
64
65 // This is used for creating a freebusy object for the current user
66 bool addLocalPeriod( FreeBusy *fb, const KDateTime &start, const KDateTime &end );
67};
68
69void KCal::FreeBusy::Private::init( const KCal::FreeBusy::Private &other )
70{
71 mDtEnd = other.mDtEnd;
72 mBusyPeriods = other.mBusyPeriods;
73}
74//@endcond
75
76FreeBusy::FreeBusy()
77 : d( new KCal::FreeBusy::Private( this ) )
78{
79}
80
81FreeBusy::FreeBusy( const FreeBusy &other )
82 : IncidenceBase( other ),
83 d( new KCal::FreeBusy::Private( *other.d, this ) )
84{
85}
86
87FreeBusy::FreeBusy( const KDateTime &start, const KDateTime &end )
88 : d( new KCal::FreeBusy::Private( this ) )
89{
90 setDtStart( start );
91 setDtEnd( end );
92}
93
94FreeBusy::FreeBusy( const Event::List &events, const KDateTime &start, const KDateTime &end )
95 : d( new KCal::FreeBusy::Private( this ) )
96{
97 setDtStart( start );
98 setDtEnd( end );
99
100 d->init( events, start, end );
101}
102
103void FreeBusy::Private::init( const Event::List &eventList,
104 const KDateTime &start, const KDateTime &end )
105{
106 int extraDays, i, x, duration;
107 duration = start.daysTo( end );
108 QDate day;
109 KDateTime tmpStart;
110 KDateTime tmpEnd;
111
112 // Loops through every event in the calendar
113 Event::List::ConstIterator it;
114 for ( it = eventList.constBegin(); it != eventList.constEnd(); ++it ) {
115 Event *event = *it;
116
117 // If this event is transparent it shouldn't be in the freebusy list.
118 if ( event->transparency() == Event::Transparent ) {
119 continue;
120 }
121
122 // The code below can not handle all-day events. Fixing this resulted
123 // in a lot of duplicated code. Instead, make a copy of the event and
124 // set the period to the full day(s). This trick works for recurring,
125 // multiday, and single day all-day events.
126 Event *allDayEvent = 0;
127 if ( event->allDay() ) {
128 // addDay event. Do the hack
129 kDebug() << "All-day event";
130 allDayEvent = new Event( *event );
131
132 // Set the start and end times to be on midnight
133 KDateTime st = allDayEvent->dtStart();
134 st.setTime( QTime( 0, 0 ) );
135 KDateTime nd = allDayEvent->dtEnd();
136 nd.setTime( QTime( 23, 59, 59, 999 ) );
137 allDayEvent->setAllDay( false );
138 allDayEvent->setDtStart( st );
139 allDayEvent->setDtEnd( nd );
140
141 kDebug() << "Use:" << st.toString() << "to" << nd.toString();
142 // Finally, use this event for the setting below
143 event = allDayEvent;
144 }
145
146 // This whole for loop is for recurring events, it loops through
147 // each of the days of the freebusy request
148
149 for ( i = 0; i <= duration; ++i ) {
150 day = start.addDays(i).date();
151 tmpStart.setDate( day );
152 tmpEnd.setDate( day );
153
154 if ( event->recurs() ) {
155 if ( event->isMultiDay() ) {
156 // FIXME: This doesn't work for sub-daily recurrences or recurrences with
157 // a different time than the original event.
158 extraDays = event->dtStart().daysTo( event->dtEnd() );
159 for ( x = 0; x <= extraDays; ++x ) {
160 if ( event->recursOn( day.addDays(-x), start.timeSpec() ) ) {
161 tmpStart.setDate( day.addDays(-x) );
162 tmpStart.setTime( event->dtStart().time() );
163 tmpEnd = event->duration().end( tmpStart );
164
165 addLocalPeriod( q, tmpStart, tmpEnd );
166 break;
167 }
168 }
169 } else {
170 if ( event->recursOn( day, start.timeSpec() ) ) {
171 tmpStart.setTime( event->dtStart().time() );
172 tmpEnd.setTime( event->dtEnd().time() );
173
174 addLocalPeriod ( q, tmpStart, tmpEnd );
175 }
176 }
177 }
178
179 }
180 // Non-recurring events
181 addLocalPeriod( q, event->dtStart(), event->dtEnd() );
182
183 // Clean up
184 delete allDayEvent;
185 }
186
187 q->sortList();
188}
189
190FreeBusy::FreeBusy( Calendar *calendar, const KDateTime &start, const KDateTime &end )
191 : d( new KCal::FreeBusy::Private( this ) )
192{
193 kDebug();
194
195 setDtStart( start );
196 setDtEnd( end );
197
198 d->init( calendar ? calendar->rawEvents( start.date(), end.date() ) : Event::List(), start, end );
199}
200
201FreeBusy::FreeBusy( const Period::List &busyPeriods )
202 : d( new KCal::FreeBusy::Private( this ) )
203{
204 addPeriods(busyPeriods);
205}
206
207FreeBusy::FreeBusy( const FreeBusyPeriod::List &busyPeriods )
208 : d( new KCal::FreeBusy::Private( busyPeriods, this ) )
209{
210}
211
212FreeBusy::~FreeBusy()
213{
214 delete d;
215}
216
217QByteArray FreeBusy::type() const
218{
219 return "FreeBusy";
220}
221
222//KDE5:
223//QString FreeBusy::typeStr() const
224//{
225// return i18nc( "incidence type is freebusy", "free/busy" );
226//}
227
228void FreeBusy::setDtStart( const KDateTime &start )
229{
230 IncidenceBase::setDtStart( start.toUtc() );
231 updated();
232}
233
234void FreeBusy::setDtEnd( const KDateTime &end )
235{
236 d->mDtEnd = end;
237}
238
239KDateTime FreeBusy::dtEnd() const
240{
241 return d->mDtEnd;
242}
243
244Period::List FreeBusy::busyPeriods() const
245{
246 Period::List res;
247
248 foreach ( const FreeBusyPeriod &p, d->mBusyPeriods ) {
249 res << p;
250 }
251
252 return res;
253}
254
255FreeBusyPeriod::List FreeBusy::fullBusyPeriods() const
256{
257 return d->mBusyPeriods;
258}
259
260void FreeBusy::sortList()
261{
262 qSort( d->mBusyPeriods );
263 return;
264}
265
266void FreeBusy::addPeriods( const Period::List &list )
267{
268 foreach ( const Period &p, list ) {
269 d->mBusyPeriods << FreeBusyPeriod( p );
270 }
271 sortList();
272}
273
274void FreeBusy::addPeriods( const FreeBusyPeriod::List &list )
275{
276 d->mBusyPeriods += list;
277 sortList();
278}
279
280void FreeBusy::addPeriod( const KDateTime &start, const KDateTime &end )
281{
282 d->mBusyPeriods.append( FreeBusyPeriod( start, end ) );
283 sortList();
284}
285
286void FreeBusy::addPeriod( const KDateTime &start, const Duration &duration )
287{
288 d->mBusyPeriods.append( FreeBusyPeriod( start, duration ) );
289 sortList();
290}
291
292void FreeBusy::merge( FreeBusy *freeBusy )
293{
294 if ( freeBusy->dtStart() < dtStart() ) {
295 setDtStart( freeBusy->dtStart() );
296 }
297
298 if ( freeBusy->dtEnd() > dtEnd() ) {
299 setDtEnd( freeBusy->dtEnd() );
300 }
301
302 Period::List periods = freeBusy->busyPeriods();
303 Period::List::ConstIterator it;
304 for ( it = periods.constBegin(); it != periods.constEnd(); ++it ) {
305 d->mBusyPeriods.append( FreeBusyPeriod( (*it).start(), (*it).end() ) );
306 }
307 sortList();
308}
309
310void FreeBusy::shiftTimes( const KDateTime::Spec &oldSpec,
311 const KDateTime::Spec &newSpec )
312{
313 if ( oldSpec.isValid() && newSpec.isValid() && oldSpec != newSpec ) {
314 IncidenceBase::shiftTimes( oldSpec, newSpec );
315 d->mDtEnd = d->mDtEnd.toTimeSpec( oldSpec );
316 d->mDtEnd.setTimeSpec( newSpec );
317 foreach ( FreeBusyPeriod p, d->mBusyPeriods ) { //krazy:exclude=foreach
318 p.shiftTimes( oldSpec, newSpec );
319 }
320 }
321}
322
323FreeBusy &FreeBusy::operator=( const FreeBusy &other )
324{
325 // check for self assignment
326 if ( &other == this ) {
327 return *this;
328 }
329
330 IncidenceBase::operator=( other );
331 d->init( *other.d );
332 return *this;
333}
334
335bool FreeBusy::operator==( const FreeBusy &freebusy ) const
336{
337 return
338 IncidenceBase::operator==( freebusy ) &&
339 dtEnd() == freebusy.dtEnd() &&
340 d->mBusyPeriods == freebusy.d->mBusyPeriods;
341}
342
343//@cond PRIVATE
344bool FreeBusy::Private::addLocalPeriod( FreeBusy *fb,
345 const KDateTime &eventStart,
346 const KDateTime &eventEnd )
347{
348 KDateTime tmpStart;
349 KDateTime tmpEnd;
350
351 //Check to see if the start *or* end of the event is
352 //between the start and end of the freebusy dates.
353 KDateTime start = fb->dtStart();
354 if ( !( ( ( start.secsTo(eventStart) >= 0 ) &&
355 ( eventStart.secsTo(mDtEnd) >= 0 ) ) ||
356 ( ( start.secsTo(eventEnd) >= 0 ) &&
357 ( eventEnd.secsTo(mDtEnd) >= 0 ) ) ) ) {
358 return false;
359 }
360
361 if ( eventStart.secsTo( start ) >= 0 ) {
362 tmpStart = start;
363 } else {
364 tmpStart = eventStart;
365 }
366
367 if ( eventEnd.secsTo( mDtEnd ) <= 0 ) {
368 tmpEnd = mDtEnd;
369 } else {
370 tmpEnd = eventEnd;
371 }
372
373 FreeBusyPeriod p( tmpStart, tmpEnd );
374 mBusyPeriods.append( p );
375
376 return true;
377}
378//@endcond
379