File indexing completed on 2024-12-22 04:59:46

0001 /*
0002     SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "sortutil.h"
0008 
0009 #include <KItinerary/BoatTrip>
0010 #include <KItinerary/BusTrip>
0011 #include <KItinerary/Event>
0012 #include <KItinerary/Flight>
0013 #include <KItinerary/MergeUtil>
0014 #include <KItinerary/Person>
0015 #include <KItinerary/Place>
0016 #include <KItinerary/RentalCar>
0017 #include <KItinerary/Reservation>
0018 #include <KItinerary/Ticket>
0019 #include <KItinerary/TrainTrip>
0020 #include <KItinerary/Visit>
0021 
0022 #include "knowledgedb/airportdb.h"
0023 
0024 #include <QDateTime>
0025 #include <QTimeZone>
0026 
0027 using namespace KItinerary;
0028 
0029 QDateTime SortUtil::startDateTime(const QVariant &elem)
0030 {
0031     // reservation types
0032     if (JsonLd::isA<FoodEstablishmentReservation>(elem)) {
0033         return elem.value<FoodEstablishmentReservation>().startTime();
0034     }
0035     if (JsonLd::isA<LodgingReservation>(elem)) {
0036         const auto hotel = elem.value<LodgingReservation>();
0037         // hotel checkin/checkout is always considered the first/last thing of the day
0038         auto dt = QDateTime(hotel.checkinTime().date(), QTime(23, 59, 59));
0039         if (hotel.checkinTime().timeSpec() == Qt::TimeZone) {
0040             dt.setTimeZone(hotel.checkinTime().timeZone());
0041         }
0042         return dt;
0043     }
0044     if (JsonLd::isA<RentalCarReservation>(elem)) {
0045         return elem.value<RentalCarReservation>().pickupTime();
0046     }
0047     if (JsonLd::isA<TaxiReservation>(elem)) {
0048         return elem.value<TaxiReservation>().pickupTime();
0049     }
0050     if (JsonLd::canConvert<Reservation>(elem)) {
0051         const auto res = JsonLd::convert<Reservation>(elem).reservationFor();
0052         return startDateTime(res);
0053     }
0054     if (JsonLd::isA<TouristAttractionVisit>(elem)) {
0055         return elem.value<TouristAttractionVisit>().arrivalTime();
0056     }
0057 
0058     // "reservationFor" types
0059     if (JsonLd::isA<Flight>(elem)) {
0060         const auto flight = elem.value<Flight>();
0061         if (flight.departureTime().isValid()) {
0062             return flight.departureTime();
0063         }
0064         if (flight.boardingTime().isValid()) {
0065             return flight.boardingTime();
0066         }
0067         QDateTime dt(flight.departureDay(), QTime(23, 59, 59));
0068         dt.setTimeZone(KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{flight.departureAirport().iataCode()}));
0069         return dt;
0070     }
0071     if (JsonLd::isA<TrainTrip>(elem)) {
0072         const auto trip = elem.value<TrainTrip>();
0073         if (trip.departureTime().isValid()) {
0074             return trip.departureTime();
0075         }
0076         return QDateTime(trip.departureDay(), QTime(23, 59, 59));
0077     }
0078     if (JsonLd::isA<BusTrip>(elem)) {
0079         return elem.value<BusTrip>().departureTime();
0080     }
0081     if (JsonLd::isA<BoatTrip>(elem)) {
0082         return elem.value<BoatTrip>().departureTime();
0083     }
0084     if (JsonLd::isA<Event>(elem)) {
0085         return elem.value<Event>().startDate();
0086     }
0087 
0088     return {};
0089 }
0090 
0091 QDateTime SortUtil::endDateTime(const QVariant &elem)
0092 {
0093     // reservation types
0094     if (JsonLd::isA<FoodEstablishmentReservation>(elem)) {
0095         auto endTime = elem.value<FoodEstablishmentReservation>().endTime();
0096         if (!endTime.isValid()) {
0097             endTime = QDateTime(elem.value<FoodEstablishmentReservation>().startTime().date(), QTime(23, 59, 59));
0098         }
0099         return endTime;
0100     }
0101     if (JsonLd::isA<RentalCarReservation>(elem)) {
0102         return elem.value<RentalCarReservation>().dropoffTime();
0103     }
0104     if (JsonLd::isA<LodgingReservation>(elem)) {
0105         const auto hotel = elem.value<LodgingReservation>();
0106         // hotel checkin/checkout is always considered the first/last thing of the day
0107         auto dt = QDateTime(hotel.checkoutTime().date(), QTime(0, 0, 0));
0108         if (hotel.checkoutTime().timeSpec() == Qt::TimeZone) {
0109             dt.setTimeZone(hotel.checkoutTime().timeZone());
0110         }
0111         return dt;
0112     }
0113     if (JsonLd::canConvert<Reservation>(elem)) {
0114         const auto res = JsonLd::convert<Reservation>(elem).reservationFor();
0115         return endDateTime(res);
0116     }
0117     if (JsonLd::isA<TouristAttractionVisit>(elem)) {
0118         return elem.value<TouristAttractionVisit>().departureTime();
0119     }
0120 
0121     // "reservationFor" types
0122     if (JsonLd::isA<Event>(elem)) {
0123         return elem.value<Event>().endDate();
0124     }
0125     if (JsonLd::isA<Flight>(elem)) {
0126         const auto flight = elem.value<Flight>();
0127         if (flight.arrivalTime().isValid()) {
0128             return flight.arrivalTime();
0129         }
0130         QDateTime dt(flight.departureDay(), QTime(23, 59, 59));
0131         dt.setTimeZone(KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{flight.arrivalAirport().iataCode()}));
0132         return dt;
0133     }
0134     if (JsonLd::isA<TrainTrip>(elem)) {
0135         const auto trip = elem.value<TrainTrip>();
0136         if (trip.arrivalTime().isValid()) {
0137             return trip.arrivalTime();
0138         }
0139         return QDateTime(trip.departureDay(), QTime(23, 59, 59));
0140     }
0141     if (JsonLd::isA<BusTrip>(elem)) {
0142         return elem.value<BusTrip>().arrivalTime();
0143     }
0144     if (JsonLd::isA<BoatTrip>(elem)) {
0145         return elem.value<BoatTrip>().arrivalTime();
0146     }
0147 
0148     return {};
0149 }
0150 
0151 bool SortUtil::isBefore(const QVariant &lhs, const QVariant &rhs)
0152 {
0153     if (startDateTime(lhs) == startDateTime(rhs) && lhs.userType() == rhs.userType() && JsonLd::canConvert<Reservation>(lhs)) {
0154         // sort by end date if available next, e.g. for overlapping hotel bookings
0155         const auto lhsEndDt = endDateTime(lhs);
0156         const auto rhsEndDt = endDateTime(rhs);
0157         if (JsonLd::isA<LodgingReservation>(lhs) && lhsEndDt.isValid() && rhsEndDt.isValid() && lhsEndDt != rhsEndDt) {
0158             return lhsEndDt < rhsEndDt;
0159         }
0160 
0161         // for multi-traveler reservations, sort by traveler name to achieve a stable result
0162         const auto lhsRes = JsonLd::convert<Reservation>(lhs);
0163         const auto rhsRes = JsonLd::convert<Reservation>(rhs);
0164         if (!lhsRes.underName().isNull() && !rhsRes.underName().isNull() && MergeUtil::isSame(lhsRes.reservationFor(), rhsRes.reservationFor())) {
0165             const auto lhsUN = lhsRes.underName().value<Person>();
0166             const auto rhsUN = rhsRes.underName().value<Person>();
0167 
0168             // for multi-ticket reservations for the same person, sort by ticket name
0169             if (lhsUN.name() == rhsUN.name()) {
0170                 const auto lhsTicket = lhsRes.reservedTicket().value<Ticket>();
0171                 const auto rhsTicket = rhsRes.reservedTicket().value<Ticket>();
0172                 return lhsTicket.name() < rhsTicket.name();
0173             }
0174             return lhsUN.name() < rhsUN.name();
0175         }
0176     }
0177     return startDateTime(lhs) < startDateTime(rhs);
0178 }
0179 
0180 bool SortUtil::hasStartTime(const QVariant &elem)
0181 {
0182     if (JsonLd::canConvert<Reservation>(elem)) {
0183         return hasStartTime(JsonLd::convert<Reservation>(elem).reservationFor());
0184     }
0185     if (JsonLd::isA<TrainTrip>(elem)) {
0186         return elem.value<TrainTrip>().departureTime().isValid();
0187     }
0188     if (JsonLd::isA<Flight>(elem)) {
0189         return elem.value<Flight>().departureTime().isValid();
0190     }
0191 
0192     return SortUtil::startDateTime(elem).isValid();
0193 }
0194 
0195 bool SortUtil::hasEndTime(const QVariant &elem)
0196 {
0197     if (JsonLd::canConvert<Reservation>(elem)) {
0198         return hasEndTime(JsonLd::convert<Reservation>(elem).reservationFor());
0199     }
0200     if (JsonLd::isA<TrainTrip>(elem)) {
0201         return elem.value<TrainTrip>().arrivalTime().isValid();
0202     }
0203     if (JsonLd::isA<Flight>(elem)) {
0204         return elem.value<Flight>().arrivalTime().isValid();
0205     }
0206 
0207     return SortUtil::endDateTime(elem).isValid();
0208 }