/*
   This file is part of kalarmcal library, which provides access to KAlarm
   calendar data.

   SPDX-FileCopyrightText: 2018-2026 David Jarvie <djarvie@kde.org>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "kaeventtest.h"

#include "kaevent.h"
#include "holidays.h"
using namespace KAlarmCal;

#include <KCalendarCore/Event>
#include <KCalendarCore/Alarm>
using namespace KCalendarCore;
#include <KHolidays/HolidayRegion>
#include <KLocalizedString>

#include <QTest>
#include <QFileInfo>
#include <QTextStream>

QTEST_GUILESS_MAIN(KAEventTest)

namespace
{
const QString SC = QStringLiteral(";");
}

//////////////////////////////////////////////////////
// Constructors and basic property information methods
//////////////////////////////////////////////////////

void KAEventTest::constructors()
{
    const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London"));
    const QString name(QStringLiteral("name"));
    const QString text(QStringLiteral("message"));
    const QColor fgColour(130, 110, 240);
    const QColor bgColour(20, 70, 140);
    const QFont  font(QStringLiteral("Helvetica"), 10, QFont::Bold, true);
    const KAEvent::Flags flags(KAEvent::ConfirmAck | KAEvent::AutoClose);
    {
        // Display alarm
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QCOMPARE(event.message(), text);
        QCOMPARE(event.displayMessage(), text);
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.bgColour(), bgColour);
        QCOMPARE(event.fgColour(), fgColour);
        QCOMPARE(event.font(), font);
        QCOMPARE(event.flags(), flags);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Display);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Message);
        QCOMPARE(event.lateCancel(), 3);
        QVERIFY(!event.isReadOnly());
    }
    {
        // Display file alarm
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::File, 3, flags);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QCOMPARE(event.fileName(), text);
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.bgColour(), bgColour);
        QCOMPARE(event.fgColour(), fgColour);
        QCOMPARE(event.font(), font);
        QCOMPARE(event.flags(), flags);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Display);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::File);
        QCOMPARE(event.lateCancel(), 3);
        QVERIFY(!event.isReadOnly());
    }
    {
        // Command alarm
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Command, 3, flags);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QCOMPARE(event.command(), text);
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.flags(), flags);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Command);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Command);
        QCOMPARE(event.lateCancel(), 3);
        QVERIFY(!event.isReadOnly());
    }
    {
        // Email alarm
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Email, 3, flags);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QCOMPARE(event.message(), text);
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QCOMPARE(event.emailMessage(), text);
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.flags(), flags);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Email);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Email);
        QCOMPARE(event.lateCancel(), 3);
        QVERIFY(!event.isReadOnly());
    }
    {
        // Audio alarm
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Audio, 3, flags);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QVERIFY(event.cleanText().isEmpty());
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QCOMPARE(event.audioFile(), text);
        QCOMPARE(event.flags(), flags);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Audio);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Audio);
        QCOMPARE(event.lateCancel(), 3);
        QVERIFY(!event.isReadOnly());
    }

    // This tests the basic KCalendarCore::Event properties.
    // Custom properties are tested later.
    const QDateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC"));
    const QString uid(QStringLiteral("fd45-77398a2"));
    {
        // Display alarm
        Event::Ptr kcalevent(new Event);
        kcalevent->setCreated(createdDt);
        kcalevent->setDtStart(dt.qDateTime());
        kcalevent->setSummary(name);
        kcalevent->setDescription(text);
        kcalevent->setUid(uid);
        kcalevent->setRevision(12);
        kcalevent->setReadOnly(false);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setDisplayAlarm(text);

        KAEvent event(kcalevent);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QCOMPARE(event.message(), text);
        QCOMPARE(event.displayMessage(), text);
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.actionTypes(), KAEvent::Action::Display);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Message);
        QVERIFY(!event.isReadOnly());
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 12);
    }
    {
        // Display file alarm
        Event::Ptr kcalevent(new Event);
        kcalevent->setCreated(createdDt);
        kcalevent->setDtStart(dt.qDateTime());
        kcalevent->setSummary(name);
        kcalevent->setDescription(text);
        kcalevent->setUid(uid);
        kcalevent->setRevision(12);
        kcalevent->setReadOnly(false);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setDisplayAlarm(text);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("FILE"));

        KAEvent event(kcalevent);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QCOMPARE(event.fileName(), text);
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.actionTypes(), KAEvent::Action::Display);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::File);
        QVERIFY(!event.isReadOnly());
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 12);
    }
    {
        // Command alarm
        const QString args(QStringLiteral("-x anargument"));
        const QString cmdline(text + QStringLiteral(" ") + args);
        Event::Ptr kcalevent(new Event);
        kcalevent->setCreated(createdDt);
        kcalevent->setDtStart(dt.qDateTime());
        kcalevent->setSummary(name);
        kcalevent->setDescription(text);
        kcalevent->setUid(uid);
        kcalevent->setRevision(12);
        kcalevent->setReadOnly(false);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(text, args);

        KAEvent event(kcalevent);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), cmdline);
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QCOMPARE(event.command(), cmdline);
        QVERIFY(event.emailMessage().isEmpty());
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.actionTypes(), KAEvent::Action::Command);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Command);
        QVERIFY(!event.isReadOnly());
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 12);
    }
    {
        // Email alarm
        const QString subject(QStringLiteral("Subject 1"));
        const Person addressee{QStringLiteral("Fred"), QStringLiteral("fred@freddy.com")};
        const Person::List addressees{addressee};
        const QStringList attachments{QStringLiteral("/tmp/xyz"), QStringLiteral("/home/fred/attch.p")};
        Event::Ptr kcalevent(new Event);
        kcalevent->setCreated(createdDt);
        kcalevent->setDtStart(dt.qDateTime());
        kcalevent->setSummary(name);
        kcalevent->setDescription(text);
        kcalevent->setUid(uid);
        kcalevent->setRevision(12);
        kcalevent->setReadOnly(false);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setEmailAlarm(subject, text, addressees, attachments);

        KAEvent event(kcalevent);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QCOMPARE(event.message(), text);
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QCOMPARE(event.emailMessage(), text);
        QVERIFY(event.audioFile().isEmpty());
        QCOMPARE(event.actionTypes(), KAEvent::Action::Email);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Email);
        QVERIFY(!event.isReadOnly());
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 12);
        QCOMPARE(event.emailAddresses(), QStringList{QStringLiteral("Fred <fred@freddy.com>")});
        QCOMPARE(event.emailSubject(), subject);
        QCOMPARE(event.emailAttachments(), attachments);
    }
    {
        // Audio alarm
        Event::Ptr kcalevent(new Event);
        kcalevent->setCreated(createdDt);
        kcalevent->setDtStart(dt.qDateTime());
        kcalevent->setSummary(name);
        kcalevent->setUid(uid);
        kcalevent->setRevision(12);
        kcalevent->setReadOnly(true);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setAudioAlarm(text);

        KAEvent event(kcalevent);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.startDateTime(), DateTime(dt));
        QCOMPARE(event.cleanText(), text);
        QCOMPARE(event.name(), name);
        QVERIFY(event.message().isEmpty());
        QVERIFY(event.displayMessage().isEmpty());
        QVERIFY(event.fileName().isEmpty());
        QVERIFY(event.command().isEmpty());
        QVERIFY(event.emailMessage().isEmpty());
        QCOMPARE(event.audioFile(), text);
        QCOMPARE(event.actionTypes(), KAEvent::Action::Audio);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Audio);
        QVERIFY(event.isReadOnly());
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 12);
    }
}

void KAEventTest::flags()
{
    const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London"));
    const QString name(QStringLiteral("name"));
    const QString text(QStringLiteral("message"));
    const QColor fgColour(130, 110, 240);
    const QColor bgColour(20, 70, 140);
    const QFont  font(QStringLiteral("Helvetica"), 10, QFont::Bold, true);
    {
        const KAEvent::Flags flags(KAEvent::Beep | KAEvent::DefaultFont);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(event.beep());
        QVERIFY(event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::RepeatAtLogin | KAEvent::Disabled);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(event.repeatAtLogin());
        QVERIFY(!event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KADateTime dtDateOnly(QDate(2010,5,13), QTimeZone("Europe/London"));
        const KAEvent::Flags flags(KAEvent::RepeatAtLogin | KAEvent::Disabled);
        KAEvent event(dtDateOnly, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QVERIFY(event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags | KAEvent::AnyTime);
        QVERIFY(event.repeatAtLogin());
        QVERIFY(!event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::RepeatAtLogin | KAEvent::Disabled);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags | KAEvent::AnyTime);
        QVERIFY(event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags | KAEvent::AnyTime);
        QVERIFY(event.repeatAtLogin());
        QVERIFY(!event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::ConfirmAck | KAEvent::Speak | KAEvent::ExcludeHolidays);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(event.confirmAck());
        QVERIFY(event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        static Holidays holidays;
        KAEvent::setHolidays(holidays);
        const KAEvent::Flags flags(KAEvent::AutoClose | KAEvent::ExcludeHolidays | KAEvent::RepeatSound);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(event.autoClose());
        QVERIFY(event.holidaysExcluded());
        QVERIFY(event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
        KAEvent::setHolidays();
    }
    {
        const KAEvent::Flags flags(KAEvent::CopyKOrganizer | KAEvent::WorkTimeOnly);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(event.copyToKOrganizer());
        QVERIFY(event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::Script | KAEvent::ExecInXterm);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Command, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(event.commandScript());
        QVERIFY(event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::DisplayCommand | KAEvent::ReminderOnce);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Command, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(event.commandDisplay());
        QVERIFY(event.reminderOnceOnly());
        QVERIFY(!event.emailBcc());
    }
    {
        const KAEvent::Flags flags(KAEvent::EmailBcc);
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Email, 3, flags);
        QVERIFY(!event.startDateTime().isDateOnly());
        QCOMPARE(event.flags(), flags);
        QVERIFY(!event.repeatAtLogin());
        QVERIFY(event.enabled());
        QVERIFY(!event.beep());
        QVERIFY(!event.useDefaultFont());
        QVERIFY(!event.confirmAck());
        QVERIFY(!event.speak());
        QVERIFY(!event.autoClose());
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(!event.repeatSound());
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(!event.workTimeOnly());
        QVERIFY(!event.commandScript());
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(!event.reminderOnceOnly());
        QVERIFY(event.emailBcc());
    }
}

namespace
{

Event::Ptr createKcalEvent(const QDateTime& start, const QDateTime& created, Alarm::Ptr& kcalalarm, Alarm::Type type = Alarm::Display)
{
    Event::Ptr kcalevent(new Event);
    kcalevent->setCreated(created);
    kcalevent->setDtStart(start);
    kcalalarm = kcalevent->newAlarm();
    kcalalarm->setType(type);
    switch (type) {
        case Alarm::Display:
            kcalalarm->setText(QStringLiteral("message"));
            break;
        case Alarm::Procedure:
            kcalalarm->setProgramFile(QStringLiteral("/tmp/cmd.sh"));
            kcalalarm->setProgramArguments(QStringLiteral("-a PERM"));
            break;
        case Alarm::Email: {
            Person addr(QStringLiteral("Cliff Edge"), QStringLiteral("cliff@edge.com"));
            kcalalarm->setMailSubject(QStringLiteral("Subject"));
            kcalalarm->setMailText(QStringLiteral("message"));
            kcalalarm->setMailAddress(addr);
            kcalalarm->setMailAttachment(QStringLiteral("/tmp/secret.txt"));
            break;
        }
        case Alarm::Audio:
            kcalalarm->setAudioFile(QStringLiteral("/tmp/sample.ogg"));
            break;
        default:
            break;
    }
    return kcalevent;
}

Event::Ptr createKcalEvent(const QDateTime& start, const QDateTime& created, Alarm::Type type = Alarm::Display)
{
    Alarm::Ptr kcalalarm;
    return createKcalEvent(start, created, kcalalarm, type);
}

Alarm::Ptr copyKcalAlarm(Event::Ptr& kcalevent, Alarm::Ptr& kcalalarm)
{
    Alarm::Ptr newAlarm = kcalevent->newAlarm();
    *newAlarm.data() = *kcalalarm.data();
    return newAlarm;
}

}

void KAEventTest::fromKCalEvent()
{
    // Check KCalendarCore::Event custom properties.
    const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London"));
    const QDateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC"));

    KLocalizedString::setApplicationDomain(QByteArrayLiteral("kaeventtest"));

    // Event category, UID, revision, start time, created time
    {
        const QString uid = QStringLiteral("fa74ec931");
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ACTIVE"));
        kcalevent->setUid(uid);
        kcalevent->setRevision(273);
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::ACTIVE);
        QCOMPARE(event.startDateTime().kDateTime(), dt);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
        QCOMPARE(event.id(), uid);
        QCOMPARE(event.revision(), 273);
    }
    {
        // Start time using LocalZone
        const KADateTime dtLocal(dt.date(), dt.time(), KADateTime::LocalZone);
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ACTIVE"));
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LOCAL"));
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::ACTIVE);
        QCOMPARE(event.startDateTime().kDateTime(), dtLocal);
        QCOMPARE(event.createdDateTime().qDateTime(), createdDt);
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("TEMPLATE"));
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::TEMPLATE);
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("ARCHIVED"));
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::ARCHIVED);
    }
    {
        bool showEdit = false;
        bool showDefer = false;
        ResourceId collectionId = -1;
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        Alarm::Ptr kcalalarmDisp = copyKcalAlarm(kcalevent, kcalalarm);
        {
            kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING"));
            kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING"));
            KAEvent event(kcalevent);
            QCOMPARE(event.category(), CalEvent::DISPLAYING);
            KAEvent event2;
            event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer);
            QCOMPARE(event2.category(), CalEvent::ACTIVE);
            QVERIFY(!event2.repeatAtLogin());
            QCOMPARE(collectionId, -1);
            QVERIFY(!showEdit);
            QVERIFY(!showDefer);
        }
        {
            kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;7;EDIT"));
            KAEvent event(kcalevent);
            QCOMPARE(event.category(), CalEvent::DISPLAYING);
            KAEvent event2;
            event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer);
            QCOMPARE(event2.category(), CalEvent::ACTIVE);
            QVERIFY(!event2.deferred());
            QCOMPARE(collectionId, 7);
            QVERIFY(showEdit);
            QVERIFY(!showDefer);
        }
        {
            kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;-1;DEFER"));
            kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL"));
            kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING,DEFERRAL"));
            KAEvent event(kcalevent);
            QCOMPARE(event.category(), CalEvent::DISPLAYING);
            KAEvent event2;
            event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer);
            QCOMPARE(event2.category(), CalEvent::ACTIVE);
            QVERIFY(event2.deferred());
            QVERIFY(!event2.deferDateTime().isDateOnly());
            QCOMPARE(collectionId, -1);
            QVERIFY(!showEdit);
            QVERIFY(showDefer);
        }
        {
            kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;634;DEFER;EDIT"));
            kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DATE_DEFERRAL"));
            kcalalarmDisp->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING,DATE_DEFERRAL"));
            KAEvent event(kcalevent);
            QCOMPARE(event.category(), CalEvent::DISPLAYING);
            KAEvent event2;
            event2.reinstateFromDisplaying(kcalevent, collectionId, showEdit, showDefer);
            QCOMPARE(event2.category(), CalEvent::ACTIVE);
            QVERIFY(event2.deferred());
            QVERIFY(event2.deferDateTime().isDateOnly());
            QCOMPARE(collectionId, 634);
            QVERIFY(showEdit);
            QVERIFY(showDefer);
        }
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;DEFER"));
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::DISPLAYING);
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "TYPE", QStringLiteral("DISPLAYING;EDIT"));
        KAEvent event(kcalevent);
        QCOMPARE(event.category(), CalEvent::DISPLAYING);
    }

    // Event flags
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE"));
        KAEvent event(kcalevent);
        QVERIFY(event.startDateTime().isDateOnly());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ACKCONF"));
        KAEvent event(kcalevent);
        QVERIFY(!event.startDateTime().isDateOnly());
        QVERIFY(event.confirmAck());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KORG"));
        KAEvent event(kcalevent);
        QVERIFY(!event.confirmAck());
        QVERIFY(event.copyToKOrganizer());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EXHOLIDAYS"));
        KAEvent event(kcalevent);
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(event.holidaysExcluded());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("WORKTIME"));
        KAEvent event(kcalevent);
        QVERIFY(!event.holidaysExcluded());
        QVERIFY(event.workTimeOnly());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LATECANCEL;4"));
        KAEvent event(kcalevent);
        QVERIFY(!event.reminderOnceOnly());
        QCOMPARE(event.lateCancel(), 4);
        QVERIFY(!event.autoClose());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LOGIN"));
        KAEvent event(kcalevent);
        QCOMPARE(event.lateCancel(), 0);
        QVERIFY(event.repeatAtLogin(true));
        QVERIFY(!event.repeatAtLogin(false));
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("LATECLOSE;16"));
        KAEvent event(kcalevent);
        QVERIFY(!event.repeatAtLogin());
        QCOMPARE(event.lateCancel(), 16);
        QVERIFY(event.autoClose());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KORG"));
        KAEvent event(kcalevent);
        QCOMPARE(event.lateCancel(), 0);
        QVERIFY(!event.autoClose());
        QVERIFY(event.copyToKOrganizer());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ARCHIVE"));
        KAEvent event(kcalevent);
        QVERIFY(!event.copyToKOrganizer());
        QVERIFY(event.toBeArchived());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DEFER;7"));
        KAEvent event(kcalevent);
        QVERIFY(!event.toBeArchived());
        QCOMPARE(event.deferDefaultMinutes(), 7);
        QVERIFY(!event.deferDefaultDateOnly());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DEFER;6D"));
        KAEvent event(kcalevent);
        QCOMPARE(event.deferDefaultMinutes(), 6);
        QVERIFY(event.deferDefaultDateOnly());
    }
    {
        // Reminder after the event, first recurrence only
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;ONCE;27M"));
        kcalalarm = copyKcalAlarm(kcalevent, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER"));
        kcalalarm->setStartOffset(-27*60);
        KAEvent event(kcalevent);
        QCOMPARE(event.deferDefaultMinutes(), 0);
        QVERIFY(!event.deferDefaultDateOnly());
        QVERIFY(event.reminderOnceOnly());
        QCOMPARE(event.reminderMinutes(), -27);
    }
    {
        // Reminder before the event
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;-27H"));
        kcalalarm = copyKcalAlarm(kcalevent, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER"));
        kcalalarm->setStartOffset(-27*3600);
        KAEvent event(kcalevent);
        QVERIFY(event.reminderActive());
        QVERIFY(!event.reminderOnceOnly());
        QCOMPARE(event.reminderMinutes(), 27*60);
    }
    {
        // Reminder after the event
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;27D"));
        kcalalarm = copyKcalAlarm(kcalevent, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER"));
        kcalalarm->setStartOffset(Duration(27, Duration::Days));
        KAEvent event(kcalevent);
        QVERIFY(event.reminderActive());
        QVERIFY(!event.reminderOnceOnly());
        QCOMPARE(event.reminderMinutes(), -27*60*24);
    }
    {
        // Reminder before the event
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("REMINDER;10M"));
        kcalalarm = copyKcalAlarm(kcalevent, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("REMINDER"));
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("HIDE"));
        kcalalarm->setStartOffset(10*60);
        KAEvent event(kcalevent);
        QVERIFY(!event.reminderActive());
        QVERIFY(!event.reminderOnceOnly());
        QCOMPARE(event.reminderMinutes(), -10);
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("BCC"));
        KAEvent event(kcalevent);
        QVERIFY(!event.reminderOnceOnly());
        QCOMPARE(event.reminderMinutes(), 0);
        QVERIFY(event.emailBcc());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("TMPLAFTTIME;31"));
        KAEvent event(kcalevent);
        QVERIFY(!event.emailBcc());
        QCOMPARE(event.templateAfterTime(), 31);
        QCOMPARE(event.emailId(), KAEvent::EmailId(-1));
    }
    {
        // Akonadi item ID, with alarm message in email format
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KMAIL;759231"));
        kcalalarm->setText(QStringLiteral("From: a@b.c\nTo: d@e.f\nDate: Sun, 01 Apr 2018 17:36:06 +0100\nSubject: About this"));
        KAEvent event(kcalevent);
        QCOMPARE(event.templateAfterTime(), -1);
        QCOMPARE(event.emailId(), KAEvent::EmailId(759231));
    }
    {
        // Akonadi item ID, with alarm message in wrong format
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("KMAIL;759231"));
        KAEvent event(kcalevent);
        QCOMPARE(event.emailId(), KAEvent::EmailId(-1));
    }

    // Alarm custom properties
    {
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("FILE"));
        kcalalarm->setStartOffset(5*60);
        KAEvent event(kcalevent);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::File);
    }
    {
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL"));
        kcalalarm->setStartOffset(5*60);
        KAEvent event(kcalevent);
        QCOMPARE(event.actionSubType(), KAEvent::SubAction::Message);
        QVERIFY(event.mainExpired());
    }
    {
        QFont font(QStringLiteral("Monospace"), 8);
        font.setBold(true);
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "FONTCOLOR", QStringLiteral("#27A8F3;#94B0FF;") + font.toString());
        KAEvent event(kcalevent);
        QVERIFY(!event.mainExpired());
        QCOMPARE(event.bgColour(), QColor("#27A8F3"));
        QCOMPARE(event.fgColour(), QColor("#94B0FF"));
        QCOMPARE(event.font(), font);
    }
    {
        // Non-repeating sound
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm = kcalevent->newAlarm();
        kcalalarm->setType(Alarm::Audio);
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("SPEAK"));
        KAEvent event(kcalevent);
        QVERIFY(event.speak());
        QCOMPARE(event.repeatSoundPause(), -1);
    }
    {
        // Sound volume
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm = kcalevent->newAlarm();
        kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg"));
        kcalalarm->setCustomProperty("KALARM", "VOLUME", QStringLiteral("0.7;0.3;9"));
        KAEvent event(kcalevent);
        QVERIFY(!event.speak());
        QCOMPARE(event.soundVolume(), 0.7f);
        QCOMPARE(event.fadeVolume(), 0.3f);
        QCOMPARE(event.fadeSeconds(), 9);
    }
    {
        // Sound volume
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio);
        kcalalarm->setCustomProperty("KALARM", "VOLUME", QStringLiteral("0.7;0.3;9"));
        KAEvent event(kcalevent);
        QCOMPARE(event.soundVolume(), 0.7f);
        QCOMPARE(event.fadeVolume(), 0.3f);
        QCOMPARE(event.fadeSeconds(), 9);
    }
    {
        // Display alarm with repeating sound, without pause
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm = kcalevent->newAlarm();
        kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg"));
        kcalalarm->setRepeatCount(-1);
        kcalalarm->setSnoozeTime(Duration(0));
        KAEvent event(kcalevent);
        QCOMPARE(event.repeatSoundPause(), 0);
    }
    {
        // Display alarm with repeating sound, with pause
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm = kcalevent->newAlarm();
        kcalalarm->setAudioAlarm(QStringLiteral("/tmp/next.ogg"));
        kcalalarm->setRepeatCount(-2);
        kcalalarm->setSnoozeTime(Duration(6));
        KAEvent event(kcalevent);
        QCOMPARE(event.repeatSoundPause(), 6);
    }
    {
        // Audio alarm with repeating sound, without pause
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("SOUNDREPEAT,0"));
        KAEvent event(kcalevent);
        QCOMPARE(event.repeatSoundPause(), 0);
    }
    {
        // Audio alarm with repeating sound, with pause
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Audio);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("SOUNDREPEAT,4"));
        KAEvent event(kcalevent);
        QCOMPARE(event.repeatSoundPause(), 4);
    }

    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, Alarm::Procedure);
        kcalevent->setCustomProperty("KALARM", "LOG", QStringLiteral("xterm:"));
        KAEvent event(kcalevent);
        QVERIFY(event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QVERIFY(event.logFile().isEmpty());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "LOG", QStringLiteral("display:"));
        KAEvent event(kcalevent);
        QVERIFY(!event.commandXterm());
        QVERIFY(event.commandDisplay());
        QVERIFY(event.logFile().isEmpty());
    }
    {
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        const QString file(QStringLiteral("/tmp/file"));
        kcalevent->setCustomProperty("KALARM", "LOG", file);
        KAEvent event(kcalevent);
        QVERIFY(!event.commandXterm());
        QVERIFY(!event.commandDisplay());
        QCOMPARE(event.logFile(), file);

        QVERIFY(!event.recurs());
    }

    {
        // Test date/time event with recurrence and sub-repetition
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setHourly(3);
        {
            KAEvent event(kcalevent);
            QCOMPARE(event.repetition().interval().asSeconds(), 0);
            QCOMPARE(event.repetition().count(), 0);
            QCOMPARE(event.nextRepetition(), 0);
        }
        kcalalarm->setSnoozeTime(17*60);
        kcalalarm->setRepeatCount(5);
        kcalalarm->setCustomProperty("KALARM", "NEXTREPEAT", QStringLiteral("2"));
        {
            KAEvent event(kcalevent);
            QCOMPARE(event.repetition().interval().asSeconds(), 17*60);
            QCOMPARE(event.repetition().count(), 5);
            QCOMPARE(event.nextRepetition(), 2);
        }
    }
    {
        // Test deferred event whose main alarm has expired, with sub-repetition
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("DEFERRAL"));
        kcalevent->setCustomProperty("KALARM", "REPEAT", QStringLiteral("17:5"));
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setHourly(3);
        KAEvent event(kcalevent);
        QCOMPARE(event.recurType(), KARecurrence::MINUTELY);
        QCOMPARE(event.recurInterval(), 3*60);
        QCOMPARE(event.repetition().interval().asSeconds(), 17*60);
        QCOMPARE(event.repetition().count(), 5);
    }
    {
        // Test deferred event whose main alarm has not expired, with sub-repetition
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        kcalevent->setCustomProperty("KALARM", "REPEAT", QStringLiteral("17:5"));
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setHourly(3);
        KAEvent event(kcalevent);
        QCOMPARE(event.recurType(), KARecurrence::MINUTELY);
        QCOMPARE(event.recurInterval(), 3*60);
        QCOMPARE(event.repetition().interval().asSeconds(), 0);
        QCOMPARE(event.repetition().count(), 0);
    }

    {
        // Test date/time event with next recurrence
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setHourly(3);
        kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100514T051700"));
        KAEvent event(kcalevent);
        QVERIFY(event.recurs());
        QCOMPARE(event.recurType(), KARecurrence::MINUTELY);
        QCOMPARE(event.recurInterval(), 3*60);
        QCOMPARE(event.repetition().interval().asSeconds(), 0);
        QCOMPARE(event.repetition().count(), 0);
        QVERIFY(event.mainDateTime() > event.startDateTime());
    }
    {
        // Test date/time event with date-only next recurrence
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setHourly(3);
        kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100514"));
        KAEvent event(kcalevent);
        QVERIFY(event.recurs());
        QCOMPARE(event.recurType(), KARecurrence::MINUTELY);
        QCOMPARE(event.recurInterval(), 3*60);
        QCOMPARE(event.repetition().interval().asSeconds(), 0);
        QCOMPARE(event.repetition().count(), 0);
        QCOMPARE(event.mainDateTime(), event.startDateTime());
    }
    {
        // Test date-only event with next recurrence
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setDaily(3);
        kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100516"));
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE"));
        KAEvent event(kcalevent);
        QVERIFY(event.recurs());
        QCOMPARE(event.recurType(), KARecurrence::DAILY);
        QCOMPARE(event.recurInterval(), 3);
        QCOMPARE(event.repetition().interval().asSeconds(), 0);
        QCOMPARE(event.repetition().count(), 0);
        QVERIFY(event.mainDateTime() > event.startDateTime());
    }
    {
        // Test date-only event with date/time next recurrence
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Recurrence* recurrence = kcalevent->recurrence();
        recurrence->setStartDateTime(QDateTime(QDate(2010,5,13), QTime(5,17,0), QTimeZone("Europe/London")), false);
        recurrence->setDaily(3);
        kcalevent->setCustomProperty("KALARM", "NEXTRECUR", QStringLiteral("20100516T051700"));
        kcalevent->setCustomProperty("KALARM", "FLAGS", QStringLiteral("DATE"));
        KAEvent event(kcalevent);
        QVERIFY(event.recurs());
        QCOMPARE(event.recurType(), KARecurrence::DAILY);
        QCOMPARE(event.recurInterval(), 3);
        QCOMPARE(event.repetition().interval().asSeconds(), 0);
        QCOMPARE(event.repetition().count(), 0);
        QCOMPARE(event.mainDateTime(), event.startDateTime());
    }
    {
        // Pre-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE"));
        KAEvent event(kcalevent);
        QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), 0);
    }
    {
        // Pre-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE"));
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EXECDEFER"));
        KAEvent event(kcalevent);
        QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), KAEvent::ExecPreActOnDeferral);
    }
    {
        // Pre-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE"));
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRCANCEL"));
        KAEvent event(kcalevent);
        QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), KAEvent::CancelOnPreActError);
    }
    {
        // Pre-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE"));
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRNOSHOW"));
        KAEvent event(kcalevent);
        QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), KAEvent::DontShowPreActError);
    }
    {
        // Pre-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("PRE"));
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("ERRNOSHOW;ERRCANCEL;EXECDEFER"));
        KAEvent event(kcalevent);
        QCOMPARE(event.preAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), KAEvent::DontShowPreActError | KAEvent::CancelOnPreActError | KAEvent::ExecPreActOnDeferral);
    }
    {
        // Post-action alarm
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt);
        Alarm::Ptr kcalalarm = kcalevent->newAlarm();
        kcalalarm->setProcedureAlarm(QStringLiteral("/tmp/action.sh"), QStringLiteral("-h"));
        kcalalarm->setStartOffset(0);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("POST"));
        KAEvent event(kcalevent);
        QCOMPARE(event.postAction(), QStringLiteral("/tmp/action.sh -h"));
        QCOMPARE(event.extraActionOptions(), 0);
    }
    {
        // Email-from ID
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm, Alarm::Email);
        kcalalarm->setCustomProperty("KALARM", "FLAGS", QStringLiteral("EMAILID;2589"));
        KAEvent event(kcalevent);
        QCOMPARE(event.emailFromId(), 2589U);
    }
    {
        // Archived repeat-at-login
        Alarm::Ptr kcalalarm;
        Event::Ptr kcalevent = createKcalEvent(dt.qDateTime(), createdDt, kcalalarm);
        kcalalarm->setCustomProperty("KALARM", "TYPE", QStringLiteral("LOGIN"));
        KAEvent event(kcalevent);
        QVERIFY(event.repeatAtLogin(false));
    }
}

void KAEventTest::toKCalEvent()
{
    // Check KCalendarCore::Event custom properties.
    const KADateTime dt(QDate(2010,5,13), QTime(3, 45, 0), QTimeZone("Europe/London"));
    const KADateTime createdDt(QDate(2009,4,13), QTime(11,14,0), QTimeZone("UTC"));
    const QString name(QStringLiteral("name"));
    const QString text = QStringLiteral("message");
    const QColor fgColour(0x82, 0x6e, 0xf0);
    const QColor bgColour(0x14, 0x46, 0x8c);
    const QFont  font(QStringLiteral("Helvetica"), 10, QFont::Bold, true);
    const QString uid = QStringLiteral("fa74ec931");

    // Ensure that a system time zone is set so that Qt::LocalTime == system time
    const QByteArray originalZone = qgetenv("TZ");   // save the original system time zone
    qputenv("TZ", ":Europe/Berlin");
    ::tzset();

    {
        // Event category, UID, revision, start time using time zone, created time
        KAEvent event(dt, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, KAEvent::ConfirmAck);
        event.setEventId(uid);
        event.incrementRevision();
        event.incrementRevision();
        event.setCategory(CalEvent::ACTIVE);
        event.setCreatedDateTime(createdDt);
        Event::Ptr kcalevent(new Event);
        QVERIFY(event.updateKCalEvent(kcalevent, KAEvent::UidAction::Set, true));
        QCOMPARE(kcalevent->uid(), uid);
        QCOMPARE(kcalevent->revision(), 2);
        QCOMPARE(kcalevent->customProperty("KALARM", "TYPE"), QStringLiteral("ACTIVE"));
        QStringList flags = kcalevent->customProperty("KALARM", "FLAGS").split(SC);
        QCOMPARE(flags.size(), 3);   // must contain LATECANCEL;3 and ACKCONF
        QCOMPARE(flags.removeAll(QStringLiteral("ACKCONF")), 1);
        QCOMPARE(flags.at(0), QStringLiteral("LATECANCEL"));
        QCOMPARE(flags.at(1), QStringLiteral("3"));
        QCOMPARE(kcalevent->dtStart(), dt.qDateTime());
        QCOMPARE(kcalevent->created(), createdDt.qDateTime());
        QCOMPARE(kcalevent->summary(), name);
        const Alarm::List kcalalarms = kcalevent->alarms();
        QCOMPARE(kcalalarms.size(), 1);
        Alarm::Ptr kcalalarm(kcalalarms[0]);
        QCOMPARE(kcalalarm->type(), Alarm::Display);
        QCOMPARE(kcalalarm->text(), text);
        QCOMPARE(kcalalarm->customProperty("KALARM", "FONTCOLOR").toUpper(), (QStringLiteral("#14468C;#826EF0;") + font.toString()).toUpper());
    }
    {
        // Start time using LocalZone
        const KADateTime dtl(QDate(2010,5,13), QTime(3, 45, 0), KADateTime::LocalZone);
        KAEvent event(dtl, name, text, bgColour, fgColour, font, KAEvent::SubAction::Message, 3, KAEvent::ConfirmAck);
        event.setEventId(uid);
        event.incrementRevision();
        event.setCategory(CalEvent::ACTIVE);
        event.setCreatedDateTime(createdDt);
        Event::Ptr kcalevent(new Event);
        QVERIFY(event.updateKCalEvent(kcalevent, KAEvent::UidAction::Set, true));
        QCOMPARE(kcalevent->uid(), uid);
        QCOMPARE(kcalevent->revision(), 1);
        QCOMPARE(kcalevent->customProperty("KALARM", "TYPE"), QStringLiteral("ACTIVE"));
        QStringList flags = kcalevent->customProperty("KALARM", "FLAGS").split(SC);
        QCOMPARE(flags.size(), 4);   // must contain LOCAL, LATECANCEL;3 and ACKCONF
        QVERIFY(flags.contains(QLatin1StringView("LOCAL")));
        const QTimeZone sysTz = QTimeZone::systemTimeZone();
        const QDateTime dtCurrentTz(dtl.date(), dtl.time(), sysTz);
        QCOMPARE(kcalevent->dtStart(), dtl.qDateTime());
        if (sysTz.isValid())
            QCOMPARE(kcalevent->dtStart().toTimeZone(sysTz), QDateTime(dtl.date(), dtl.time(), sysTz));
        QCOMPARE(kcalevent->created(), createdDt.qDateTime());
    }

    // Restore the original system time zone
    if (originalZone.isEmpty())
        unsetenv("TZ");
    else
        qputenv("TZ", originalZone);
    ::tzset();
}

void KAEventTest::setNextOccurrence()
{
    // Test setNextOccurrence() going from before to after a shift from daylight savings
    // to standard time, for a daily recurrence at a clock time which occurs twice (once
    // before the time shift, and once an hour later when the clock time repeats).
    const KADateTime dt(QDate(2005, 10, 29), QTime(1, 30, 0), QTimeZone("Europe/London"));
    KAEvent event(dt, QStringLiteral("name"), QStringLiteral("text"), Qt::black, Qt::white, QFont(), KAEvent::SubAction::Message, 0, KAEvent::DefaultFont);
    event.setRecurDaily(1, QBitArray(7, true), -1, QDate());
    KAEvent::OccurType type;
    bool updated = event.setNextOccurrence(dt, type);
    const DateTime next1 = event.mainDateTime();
    QVERIFY(updated);
    QCOMPARE(type, KAEvent::OccurType::Recur);
    QCOMPARE(next1.date(), QDate(2005, 10, 30));
    QCOMPARE(next1.effectiveTime(), QTime(1, 30, 0));
    KAEvent eventUTC = event;

    const KADateTime dt1(QDate(2005, 10, 30), QTime(1, 30, 0), QTimeZone("Europe/London"));
    updated = event.setNextOccurrence(next1.kDateTime(), type);
    const DateTime next2 = event.mainDateTime();
    QVERIFY(updated);
    QCOMPARE(type, KAEvent::OccurType::Recur);
    QCOMPARE(next2.date(), QDate(2005, 10, 31));
    QCOMPARE(next2.effectiveTime(), QTime(1, 30, 0));

    const KADateTime dt2(QDate(2005, 10, 30), QTime(1, 30, 0), KADateTime::UTC);
    updated = eventUTC.setNextOccurrence(dt2, type);
    const DateTime next3 = eventUTC.mainDateTime();
    QVERIFY(updated);
    QCOMPARE(type, KAEvent::OccurType::Recur);
    QCOMPARE(next3.date(), QDate(2005, 10, 31));
    QVERIFY(!next3.isSecondOccurrence());
}

void KAEventTest::nextDateTime()
{
    // Test behaviour of working time only, excluding holidays and excluding date/time
    // works as expected, for recurrences and for sub-repetitions.

    // NOTE: Because the Holidays class only deals with dates in the future,
    //       we have to find a future year whose dates are on the correct 
    //       days of the week for this test.
    int year;
    for (year = QDate::currentDate().year() + 1;  QDate(year, 6, 3).dayOfWeek() != 1;  ++year) ;
    const KADateTime dtMon_6_3(QDate(year, 6, 3), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtThu_6_13(QDate(year, 6, 13), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtSunHol_6_23(QDate(year, 6, 23), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtWedHol_7_3(QDate(year, 7, 3), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtSat_7_13(QDate(year, 7, 13), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtTue_7_23(QDate(year, 7, 23), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtFri_8_2(QDate(year, 8, 2), QTime(12, 0, 0), QTimeZone("Europe/London"));
    const KADateTime dtThu_8_22(QDate(year, 8, 22), QTime(12, 0, 0), QTimeZone("Europe/London"));

    // Set KAEvent holidays to June 15 and June 23.
    QTemporaryDir dir;
    QVERIFY(dir.isValid());
    const QString path = dir.path();
    QFile holidayFile(path + QStringLiteral("/holiday_gb-eaw_en-gb_Test"));
    QVERIFY(holidayFile.open(QIODeviceBase::WriteOnly));
    QTextStream fStream(&holidayFile);
    fStream << "country     \"GB-EAW\"\n"
               "language    \"en_GB\"\n"
               "name        \"England and Wales\"\n"
               "description \"Test holiday file\"\n\n"
               "\"Holiday Wed Rep\" public on june 5\n"
               "\"Holiday Sun Rec\" public on june 23\n"
               "\"Holiday Wed Rec\" public on july 3\n"
               "\"Holiday Sun Rep\" public on august 4\n";
    holidayFile.close();
    KHolidays::HolidayRegion hRegion{QFileInfo(holidayFile)};
    QVERIFY(hRegion.isValid());
    const auto holidayList = hRegion.rawHolidays(QDate(year,1,1), QDate(year,12,31));
    QCOMPARE(holidayList.count(), 4);
    static Holidays holidays(hRegion);
    KAEvent::setHolidays(holidays);
    QVERIFY(holidays.isHoliday(QDate(year, 6, 5)));
    QVERIFY(holidays.isHoliday(QDate(year, 6, 23)));
    QVERIFY(holidays.isHoliday(QDate(year, 7, 3)));
    QVERIFY(holidays.isHoliday(QDate(year, 8, 4)));

    // Set KAEvent working days to Monday - Friday, 9am - 5pm.
    QBitArray workDays(7, false);
    workDays.fill(true, 0, 5);
    KAEvent::setWorkTime(workDays, QTime(9,0,0), QTime(17,0,0), KADateTime::LocalZone);

    // Set event recurrence to start June 3, recur every 10 days,
    // with one sub-repetition 2 days later.
    KAEvent event(dtMon_6_3, QStringLiteral("name"), QStringLiteral("text"), Qt::black, Qt::white, QFont(), KAEvent::SubAction::Message, 0, KAEvent::DefaultFont);
    event.setRecurDaily(10, QBitArray(7, true), -1, QDate());  // recur every 10 days
    event.setRepetition(Repetition(Duration(2, Duration::Days), 1));   // 1 sub-rep after 2 days
    DateTime next;

    // Get next recurrence/sub-repetition.
    KAEvent::TriggerType type = event.nextDateTime(dtSunHol_6_23.addSecs(-60), next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23);
    type = event.nextDateTime(dtSunHol_6_23, next, KAEvent::NextRepeat);
    QVERIFY(KAEvent::isRepeat(type));
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(2));
    type = event.nextDateTime(dtSunHol_6_23.addDays(2), next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));

    // Exclude a recurrence date: returns next recurrence, not sub-repetition
    event.setExceptionDates({dtSunHol_6_23.date()});
    type = event.nextDateTime(dtSunHol_6_23.addSecs(-60), next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));
    type = event.nextDateTime(dtSunHol_6_23, next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));
    type = event.nextDateTime(dtSunHol_6_23.addDays(2), next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));

    event.setExceptionDates({});   // clear the exception
    type = event.nextDateTime(dtSunHol_6_23.addSecs(-60), next, KAEvent::NextRepeat);
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23);

    // Working time only: returns recurrence, or sub-repetition only if the recurrence
    // actually triggered.
    event.setWorkTimeOnly(true);
    // Check recurrence unaffected by working time still triggers
    type = event.nextDateTime(dtMon_6_3.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isFirstRecur(type));
    QCOMPARE(next.kDateTime(), dtMon_6_3);
    type = event.nextDateTime(dtMon_6_3, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isRepeat(type));
    QCOMPARE(next.kDateTime(), dtMon_6_3.addDays(2));
    // Recurrence, and therefore its sub-repetition, suppressed by working time
    type = event.nextDateTime(dtSunHol_6_23.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));
    type = event.nextDateTime(dtSunHol_6_23, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSunHol_6_23.addDays(10));
    // Individual sub-repetition suppressed by working time
    event.setRepetition(Repetition(Duration(2, Duration::Days), 2));   // 2 sub-reps after 2 days
    type = event.nextDateTime(dtThu_6_13, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isRepeat(type));
    QCOMPARE(next.kDateTime(), dtThu_6_13.addDays(4));
    event.setRepetition(Repetition(Duration(2, Duration::Days), 1));   // 1 sub-rep after 2 days
    event.setWorkTimeOnly(false);

    // Holidays excluded: returns recurrence, or sub-repetition only if the recurrence
    // actually triggered.
    event.setExcludeHolidays(true);
    // Check recurrence unaffected by holidays still triggers
    type = event.nextDateTime(dtThu_8_22.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtThu_8_22);
    type = event.nextDateTime(dtThu_8_22, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isRepeat(type));
    QCOMPARE(next.kDateTime(), dtThu_8_22.addDays(2));
    type = event.nextDateTime(dtThu_8_22.addDays(2), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtThu_8_22.addDays(10));
    // Recurrence, and therefore its sub-repetition, suppressed by holiday
    QVERIFY(holidays.isHoliday(dtWedHol_7_3.date()));
    type = event.nextDateTime(dtWedHol_7_3.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtWedHol_7_3.addDays(10));
    type = event.nextDateTime(dtWedHol_7_3, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtWedHol_7_3.addDays(10));
    // Individual sub-repetition suppressed by holiday
    type = event.nextDateTime(dtMon_6_3.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isFirstRecur(type));
    QCOMPARE(next.kDateTime(), dtMon_6_3);
    type = event.nextDateTime(dtMon_6_3, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtMon_6_3.addDays(10));
    event.setExcludeHolidays(false);

    // Work time only, and holidays excluded: returns recurrence, or sub-repetition only
    // if the recurrence actually triggered.
    event.setWorkTimeOnly(true);
    event.setExcludeHolidays(true);
    // Check recurrence unaffected by working time or holidays still triggers
    type = event.nextDateTime(dtTue_7_23.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtTue_7_23);
    type = event.nextDateTime(dtTue_7_23, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isRepeat(type));
    QCOMPARE(next.kDateTime(), dtTue_7_23.addDays(2));
    type = event.nextDateTime(dtTue_7_23.addDays(2), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtTue_7_23.addDays(10));
    // Recurrence, and therefore its sub-repetition, suppressed by holiday
    type = event.nextDateTime(dtWedHol_7_3.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtWedHol_7_3.addDays(20));
    type = event.nextDateTime(dtWedHol_7_3, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtWedHol_7_3.addDays(20));
    // Recurrence, and therefore its sub-repetition, suppressed by working time
    type = event.nextDateTime(dtSat_7_13.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSat_7_13.addDays(10));
    type = event.nextDateTime(dtSat_7_13, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtSat_7_13.addDays(10));
    // Recurrence, and therefore its sub-repetition, suppressed by both working time
    // and holiday
    type = event.nextDateTime(dtSunHol_6_23.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtTue_7_23);
    type = event.nextDateTime(dtSunHol_6_23, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtTue_7_23);
    // Individual sub-repetition suppressed by working time
    type = event.nextDateTime(dtThu_8_22, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtThu_8_22.addDays(20));
    // Individual sub-repetition suppressed by holiday
    type = event.nextDateTime(dtMon_6_3.addSecs(-60), next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QVERIFY(KAEvent::isFirstRecur(type));
    QCOMPARE(next.kDateTime(), dtMon_6_3);
    // Individual sub-repetition suppressed by both working time and holiday
    type = event.nextDateTime(dtFri_8_2, next, (KAEvent::NextRepeat | KAEvent::NextWorkHoliday));
    QCOMPARE(type, KAEvent::TriggerType::Recur);
    QCOMPARE(next.kDateTime(), dtFri_8_2.addDays(10));

//TODO: Test for date-only alarms

    event.setWorkTimeOnly(false);
    event.setExcludeHolidays(false);
    KAEvent::setHolidays();
}

#include "moc_kaeventtest.cpp"

// vim: et sw=4:
