1 /* This file is part of the KDE project
2    Copyright (C) 2006-2007 Dag Andersen <danders@get2net.dk>
3    Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
4 
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public
7    License as published by the Free Software Foundation; either
8    version 2 of the License, or (at your option) any later version.
9 
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public License
16    along with this library; see the file COPYING.LIB.  If not, write to
17    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301, USA.
19 */
20 // clazy:excludeall=qstring-arg
21 #include "CalendarTester.h"
22 #include "DateTimeTester.h"
23 #include <kptcalendar.h>
24 #include <kptdatetime.h>
25 #include <kptduration.h>
26 #include <kptmap.h>
27 #include "kptappointment.h"
28 
29 #include <QTimeZone>
30 #include <QDateTime>
31 
32 #include "debug.cpp"
33 
34 
35 namespace KPlato
36 {
37 
createTimeZoneWithOffsetFromSystem(int hours,const QString & name,int * shiftDays)38 QTimeZone createTimeZoneWithOffsetFromSystem(int hours, const QString & name, int *shiftDays)
39 {
40     QTimeZone systemTimeZone = QTimeZone::systemTimeZone();
41     int systemOffsetSeconds = systemTimeZone.standardTimeOffset(QDateTime(QDate(1980, 1, 1), QTime(), Qt::UTC));
42     int offsetSeconds = systemOffsetSeconds + 3600 * hours;
43     if (offsetSeconds >= (12*3600)) {
44         qDebug() << "reducing offset by 24h";
45         offsetSeconds -= (24*3600);
46         *shiftDays = -1;
47     } else if (offsetSeconds <= -(12*3600)) {
48         qDebug() << "increasing offset by 24h";
49         offsetSeconds += (24*3600);
50         *shiftDays = 1;
51     } else {
52         *shiftDays = 0;
53     }
54     qDebug() << "creating timezone for offset" << hours << offsetSeconds << "systemoffset" << systemOffsetSeconds
55              << "shiftDays" << *shiftDays;
56     return QTimeZone(name.toLatin1(), offsetSeconds, name, name);
57 }
58 
testSingleDay()59 void CalendarTester::testSingleDay() {
60     Calendar t("Test");
61     QDate wdate(2006,1,2);
62     DateTime before = DateTime(wdate.addDays(-1), QTime());
63     DateTime after = DateTime(wdate.addDays(1), QTime());
64     QTime t1(8,0,0);
65     QTime t2(10,0,0);
66     DateTime wdt1(wdate, t1);
67     DateTime wdt2(wdate, t2);
68     int length = t1.msecsTo(t2);
69     CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
70     day->addInterval(TimeInterval(t1, length));
71     t.addDay(day);
72     QVERIFY(t.findDay(wdate) == day);
73 
74     QVERIFY(t.hasInterval(after, DateTime(after.addDays(1))) == false);
75     QVERIFY(t.hasInterval(before, DateTime(before.addDays(-1))) == false);
76 
77     QVERIFY(t.hasInterval(after, before) == false);
78     QVERIFY(t.hasInterval(before, after));
79 
80     QVERIFY((t.firstAvailableAfter(after, DateTime(after.addDays(10)))).isValid() == false);
81     QVERIFY((t.firstAvailableBefore(before, DateTime(before.addDays(-10)))).isValid() == false);
82 
83     QCOMPARE(t.firstAvailableAfter(before,after), wdt1);
84     QCOMPARE(t.firstAvailableBefore(after, before), wdt2);
85 
86     Duration e(0, 2, 0);
87     QCOMPARE((t.effort(before, after)), e);
88 }
89 
testWeekdays()90 void CalendarTester::testWeekdays() {
91     Calendar t("Test");
92     QDate wdate(2006,1,4); // wednesday
93     DateTime before = DateTime(wdate.addDays(-2), QTime());
94     DateTime after = DateTime(wdate.addDays(2), QTime());
95     QTime t1(8,0,0);
96     QTime t2(10,0,0);
97     int length = t1.msecsTo(t2);
98 
99     CalendarDay *wd1 = t.weekday(Qt::Wednesday);
100     QVERIFY(wd1 != 0);
101 
102     wd1->setState(CalendarDay::Working);
103     wd1->addInterval(TimeInterval(t1, length));
104 
105     QCOMPARE(t.firstAvailableAfter(before, after), DateTime(QDate(2006, 1, 4), QTime(8,0,0)));
106     QCOMPARE((t.firstAvailableBefore(after, before)), DateTime(QDate(2006, 1, 4), QTime(10,0,0)));
107 
108     QCOMPARE(t.firstAvailableAfter(after, DateTime(QDate(QDate(2006,1,14)), QTime())), DateTime(QDate(2006, 1, 11), QTime(8,0,0)));
109     QCOMPARE(t.firstAvailableBefore(before, DateTime(QDate(2005,12,25), QTime())), DateTime(QDate(2005, 12, 28), QTime(10,0,0)));
110 }
111 
testCalendarWithParent()112 void CalendarTester::testCalendarWithParent() {
113     Calendar p("Test 3 parent");
114     Calendar t("Test 3");
115     t.setParentCal(&p);
116     QDate wdate(2006,1,2);
117     DateTime before = DateTime(wdate.addDays(-1), QTime());
118     DateTime after = DateTime(wdate.addDays(1), QTime());
119     QTime t1(8,0,0);
120     QTime t2(10,0,0);
121     int length = t1.msecsTo(t2);
122     DateTime wdt1(wdate, t1);
123     DateTime wdt2(wdate, t2);
124 
125     CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
126     day->addInterval(TimeInterval(t1, length));
127     p.addDay(day);
128     QVERIFY(p.findDay(wdate) == day);
129 
130     //same tests as in testSingleDay()
131     QVERIFY(t.hasInterval(after, DateTime(after.addDays(1))) == false);
132     QVERIFY(t.hasInterval(before, DateTime(before.addDays(-1))) == false);
133 
134     QVERIFY(t.hasInterval(after, before) == false);
135     QVERIFY(t.hasInterval(before, after));
136 
137     QVERIFY((t.firstAvailableAfter(after, DateTime(after.addDays(10)))).isValid() == false);
138     QVERIFY((t.firstAvailableBefore(before, DateTime(before.addDays(-10)))).isValid() == false);
139 
140     QVERIFY(t.firstAvailableAfter(before, after).isValid());
141     QVERIFY(t.firstAvailableBefore(after, before).isValid());
142 
143     QCOMPARE(t.firstAvailableAfter(before,after), wdt1);
144     QCOMPARE(t.firstAvailableBefore(after, before), wdt2);
145 
146     Duration e(0, 2, 0);
147     QCOMPARE((t.effort(before, after)), e);
148 }
149 
testTimezone()150 void CalendarTester::testTimezone()
151 {
152     Calendar t("Test");
153     QDate wdate(2006,1,2);
154     DateTime before = DateTime(wdate.addDays(-1), QTime());
155     DateTime after = DateTime(wdate.addDays(1), QTime());
156     QTime t1(8,0,0);
157     QTime t2(10,0,0);
158     DateTime wdt1(wdate, t1);
159     DateTime wdt2(wdate, t2);
160     int length = t1.msecsTo(t2);
161     CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
162     day->addInterval(TimeInterval(t1, length));
163     t.addDay(day);
164     Debug::print(&t, "Time zone testing");
165     QVERIFY(t.findDay(wdate) == day);
166 
167     // local zone: Europe/Berlin (1 hours from London)
168     int loShiftDays;
169     QTimeZone lo = createTimeZoneWithOffsetFromSystem(-1, "DummyLondon", &loShiftDays);
170     QVERIFY(lo.isValid());
171     QDateTime dt1 = QDateTime(wdate, t1, lo).addDays(loShiftDays).addSecs(-2 * 3600);
172     QDateTime dt2 = QDateTime(wdate, t2, lo).addDays(loShiftDays).addSecs(0 * 3600);
173 
174     qDebug()<<QDateTime(wdt1)<<QDateTime(wdt2);
175     qDebug()<<dt1<<dt2<<"("<<dt1.toLocalTime()<<dt2.toLocalTime()<<")";
176     QCOMPARE(t.firstAvailableAfter(DateTime(dt1), after), wdt1);
177     QCOMPARE(t.firstAvailableBefore(DateTime(dt2), before), wdt2);
178 
179     Duration e(0, 2, 0);
180     QCOMPARE(t.effort(DateTime(dt1), DateTime(dt2)), e);
181 
182     // local zone: Europe/Berlin (9 hours from America/Los_Angeles)
183     int laShiftDays;
184     QTimeZone la = createTimeZoneWithOffsetFromSystem(-9, "DummyLos_Angeles", &laShiftDays);
185     QVERIFY(la.isValid());
186     QDateTime dt3 = QDateTime(wdate, t1, la).addDays(laShiftDays).addSecs(-10 * 3600);
187     QDateTime dt4 = QDateTime(wdate, t2, la).addDays(laShiftDays).addSecs(-8 * 3600);
188 
189     qDebug()<<QDateTime(wdt1)<<QDateTime(wdt2);
190     qDebug()<<dt3<<dt4<<"("<<dt3.toLocalTime()<<dt4.toLocalTime()<<")";
191     QCOMPARE(t.firstAvailableAfter(DateTime(dt3), after), wdt1);
192     QCOMPARE(t.firstAvailableBefore(DateTime(dt4), before), wdt2);
193 
194     QCOMPARE(t.effort(DateTime(dt3), DateTime(dt4)), e);
195 
196     QString s = "Test Cairo:";
197     qDebug()<<s;
198     // local zone: Europe/Berlin (1 hour from cairo)
199     int caShiftDays;
200     QTimeZone ca = createTimeZoneWithOffsetFromSystem(1, "DummyCairo", &caShiftDays);
201     QDateTime dt5 = QDateTime(wdate, t1, ca).addDays(caShiftDays).addSecs(0 * 3600);
202     QDateTime dt6 = QDateTime(wdate, t2, ca).addDays(caShiftDays).addSecs(2 * 3600);
203 
204     qDebug()<<QDateTime(wdt1)<<QDateTime(wdt2);
205     qDebug()<<dt5<<dt6<<"("<<dt5.toLocalTime()<<dt6.toLocalTime()<<")";
206     QCOMPARE(t.firstAvailableAfter(DateTime(dt5), after), wdt1);
207     QCOMPARE(t.firstAvailableBefore(DateTime(dt6), before), wdt2);
208 
209     QCOMPARE(t.effort(DateTime(dt5), DateTime(dt6)), e);
210 }
211 
workIntervals()212 void CalendarTester::workIntervals()
213 {
214     Calendar t("Test");
215     QDate wdate(2006,1,2);
216     DateTime before = DateTime(wdate.addDays(-1), QTime());
217     DateTime after = DateTime(wdate.addDays(1), QTime());
218     QTime t1(8,0,0);
219     QTime t2(10,0,0);
220     DateTime wdt1(wdate, t1);
221     DateTime wdt2(wdate, t2);
222     int length = t1.msecsTo(t2);
223     CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
224     day->addInterval(TimeInterval(t1, length));
225     t.addDay(day);
226     QVERIFY(t.findDay(wdate) == day);
227 
228     AppointmentIntervalList lst = t.workIntervals(before, after, 100.);
229     QCOMPARE(lst.map().count(), 1);
230     QCOMPARE(wdate, lst.map().values().first().startTime().date());
231     QCOMPARE(t1, lst.map().values().first().startTime().time());
232     QCOMPARE(wdate, lst.map().values().first().endTime().date());
233     QCOMPARE(t2, lst.map().values().first().endTime().time());
234     QCOMPARE(100., lst.map().values().first().load());
235 
236     QTime t3(12, 0, 0);
237     day->addInterval(TimeInterval(t3, length));
238 
239     lst = t.workIntervals(before, after, 100.);
240     Debug::print(lst);
241     QCOMPARE(lst.map().count(), 2);
242     QCOMPARE(wdate, lst.map().values().first().startTime().date());
243     QCOMPARE(t1, lst.map().values().first().startTime().time());
244     QCOMPARE(wdate, lst.map().values().first().endTime().date());
245     QCOMPARE(t2, lst.map().values().first().endTime().time());
246     QCOMPARE(100., lst.map().values().first().load());
247 
248     QCOMPARE(wdate, lst.map().values().at(1).startTime().date());
249     QCOMPARE(t3, lst.map().values().at(1).startTime().time());
250     QCOMPARE(wdate, lst.map().values().at(1).endTime().date());
251     QCOMPARE(t3.addMSecs(length), lst.map().values().at(1).endTime().time());
252     QCOMPARE(100., lst.map().values().at(1).load());
253 
254     // add interval before the existing
255     QTime t4(5, 30, 0);
256     day->addInterval(TimeInterval(t4, length));
257 
258     lst = t.workIntervals(before, after, 100.);
259     Debug::print(lst);
260     QCOMPARE(lst.map().count(), 3);
261     QCOMPARE(wdate, lst.map().values().first().startTime().date());
262     QCOMPARE(t4, lst.map().values().first().startTime().time());
263     QCOMPARE(wdate, lst.map().values().first().endTime().date());
264     QCOMPARE(t4.addMSecs(length), lst.map().values().first().endTime().time());
265     QCOMPARE(100., lst.map().values().first().load());
266 
267     QCOMPARE(wdate, lst.map().values().at(1).startTime().date());
268     QCOMPARE(t1, lst.map().values().at(1).startTime().time());
269     QCOMPARE(wdate, lst.map().values().at(1).endTime().date());
270     QCOMPARE(t2, lst.map().values().at(1).endTime().time());
271     QCOMPARE(100., lst.map().values().at(1).load());
272 
273     QCOMPARE(wdate, lst.map().values().at(2).startTime().date());
274     QCOMPARE(t3, lst.map().values().at(2).startTime().time());
275     QCOMPARE(wdate, lst.map().values().at(2).endTime().date());
276     QCOMPARE(t3.addMSecs(length), lst.map().values().at(2).endTime().time());
277     QCOMPARE(100., lst.map().values().at(2).load());
278 }
279 
workIntervalsFullDays()280 void CalendarTester::workIntervalsFullDays()
281 {
282     Calendar t("Test");
283     QDate wdate(2006,1,2);
284     DateTime before = DateTime(wdate.addDays(-1), QTime());
285     DateTime after = DateTime(wdate.addDays(10), QTime());
286 
287     CalendarDay *day = new CalendarDay(wdate, CalendarDay::Working);
288     day->addInterval(TimeInterval(QTime(0, 0, 0), 24*60*60*1000));
289     t.addDay(day);
290 
291     QCOMPARE(day->numIntervals(), 1);
292     QVERIFY(day->timeIntervals().constFirst()->endsMidnight());
293 
294     DateTime start = day->start();
295     DateTime end = day->end();
296 
297     QCOMPARE(t.workIntervals(start, end, 100.).map().count(), 1);
298     QCOMPARE(t.workIntervals(before, after, 100.).map().count(), 1);
299 
300     day = new CalendarDay(wdate.addDays(1), CalendarDay::Working);
301     day->addInterval(TimeInterval(QTime(0, 0, 0), 24*60*60*1000));
302     t.addDay(day);
303 
304     end = day->end();
305 
306     QCOMPARE(t.workIntervals(start, end, 100.).map().count(), 2);
307     QCOMPARE(t.workIntervals(before, after, 100.).map().count(), 2);
308 
309     day = new CalendarDay(wdate.addDays(2), CalendarDay::Working);
310     day->addInterval(TimeInterval(QTime(0, 0, 0), 24*60*60*1000));
311     t.addDay(day);
312 
313     end = day->end();
314 
315     QCOMPARE(t.workIntervals(start, end, 100.).map().count(), 3);
316     QCOMPARE(t.workIntervals(before, after, 100.).map().count(), 3);
317 
318 }
319 
dstSpring()320 void CalendarTester::dstSpring()
321 {
322     QByteArray tz("TZ=Europe/Copenhagen");
323     putenv(tz.data());
324 
325     Calendar t("DST");
326     QDate wdate(2016,3,27);
327 
328     DateTime before = DateTime(wdate.addDays(-1), QTime());
329     DateTime after = DateTime(wdate.addDays(1), QTime());
330     QTime t1(0,0,0);
331     int length = 24*60*60*1000;
332 
333     CalendarDay *wd1 = t.weekday(Qt::Sunday);
334     QVERIFY(wd1 != 0);
335 
336     wd1->setState(CalendarDay::Working);
337     wd1->addInterval(TimeInterval(t1, length));
338     AppointmentIntervalList lst = t.workIntervals(before, after, 100.);
339     qDebug()<<lst;
340     QCOMPARE(lst.map().count(), 1);
341     QCOMPARE(lst.map().first().effort().toHours(), 23.); // clazy:exclude=detaching-temporary
342 
343     wd1->clearIntervals();
344     qDebug()<<"clear list";
345     wd1->addInterval(TimeInterval(QTime(0,0,0), Duration(2., Duration::Unit_h).milliseconds()));
346     wd1->addInterval(TimeInterval(QTime(2,0,0), Duration(2., Duration::Unit_h).milliseconds()));
347 
348     lst = t.workIntervals(before, after, 100.);
349     qDebug()<<"DST?"<<DateTime(wdate, QTime(2,0,0))<<endl<<lst;
350     QCOMPARE(lst.map().count(), 2);
351 
352     AppointmentInterval ai = lst.map().values().value(0);
353     QCOMPARE(ai.startTime(),  DateTime(wdate, QTime()));
354     QCOMPARE(ai.endTime(),  DateTime(wdate, QTime(2,0,0)));
355     QCOMPARE(ai.effort().toHours(),  2.);
356 
357     Debug::print(lst);
358     ai = lst.map().values().value(1);
359     QCOMPARE(ai.startTime(),  DateTime(wdate, QTime(2,0,0)));
360     QCOMPARE(ai.endTime(),  DateTime(wdate, QTime(4,0,0)));
361     QCOMPARE(ai.effort().toHours(),  1.); // Missing DST hour is skipped
362 
363     unsetenv("TZ");
364 }
365 
366 } //namespace KPlato
367 
368 QTEST_GUILESS_MAIN(KPlato::CalendarTester)
369