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 }