KItinerary

timefinder.cpp
1/*
2 SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "timefinder_p.h"
7
8#include <QDebug>
9#include <QLocale>
10#include <QRegularExpression>
11#include <QStringView>
12#include <QTime>
13
14using namespace KItinerary;
15
16static bool isSeparator(QChar c)
17{
18 return !c.isDigit() && !c.isLetter() && c != QLatin1Char(':');
19}
20
21void TimeFinder::find(QStringView text)
22{
23 m_results.clear();
24 findTimes(text);
25 if (!m_results.empty()) {
26 findDates(text);
27 mergeResults();
28 }
29
30#ifdef TIMEFINDER_DEBUG
31 for (const auto &res : m_results) {
32 qDebug() << " " << res.dateTime << res.begin << res.end;
33 }
34#endif
35}
36
37QTime TimeFinder::findSingularTime(QStringView text)
38{
39 find(text);
40 if (m_results.size() != 1 || m_results[0].dateTime.userType() != QMetaType::QTime) {
41 return {};
42 }
43 return m_results[0].dateTime.toTime();
44}
45
46void TimeFinder::findTimes(QStringView text)
47{
48 static const QRegularExpression rxTimes[] = {
49 QRegularExpression(QStringLiteral("(?<hour>\\d?\\d)時(?<min>\\d\\d)分")),
50 QRegularExpression(QStringLiteral("(?:(?<am>오전)|(?<pm>오후) ?)?(?<hour>\\d?\\d)시 ?(?<min>\\d?\\d)분")),
51 QRegularExpression(QStringLiteral("(?:(?<am>上午)|(?<pm>下午))?(?<hour>\\d?\\d)點(?<min>\\d?\\d)分")),
52 QRegularExpression(QStringLiteral("(?:(?<am>上午)|(?<pm>下午))(?<hour>\\d?\\d):(?<min>\\d?\\d)")),
53 QRegularExpression(QStringLiteral("\\b(?<hour>\\d?\\d)[:h](?<min>\\d\\d)")),
54 QRegularExpression(QStringLiteral("\\b(?<hour>\\d\\d)\\.(?<min>\\d\\d)(?=$|[^.])")),
55 QRegularExpression(QStringLiteral("\\b(?<hour>\\d\\d)(?<min>\\d\\d) Hrs")),
56 };
57 static const QRegularExpression rxApSuffixes[] = {
58 QRegularExpression(QStringLiteral("(?<pm> ?(?:pm|PM|p\\.m\\.|م|μ\\.μ\\.))")),
59 QRegularExpression(QStringLiteral("(?<am> ?(?:am|AM|a\\.m\\.|ص|π\\.μ\\.))")),
60 QRegularExpression(QStringLiteral("(?<pm>p)")),
61 QRegularExpression(QStringLiteral("(?<am>a)")),
62 };
63
64 int rxTimesPattern = -1;
65 for (auto i = 0; i < text.size(); ++i) {
66 QRegularExpressionMatch rxTimeMatch;
67 if (rxTimesPattern < 0) {
68 rxTimesPattern = 0;
69 for (const auto &rx : rxTimes) {
70 rxTimeMatch = rx.matchView(text, i);
71 if (rxTimeMatch.hasMatch()) {
72 break;
73 }
74 ++rxTimesPattern;
75 }
76 } else {
77 rxTimeMatch = rxTimes[rxTimesPattern].matchView(text, i);
78 }
79
80 if (!rxTimeMatch.hasMatch()) {
81 return;
82 }
83
84 i = rxTimeMatch.capturedEnd();
86 for (const auto &rx : rxApSuffixes) {
88 if (rxApMatch.hasMatch()) {
89 break;
90 }
91 }
92 if (rxApMatch.hasMatch()) {
93 i = rxApMatch.capturedEnd();
94 }
95
96 if (i < text.size() && !isSeparator(text[i])) {
97 continue;
98 }
99
100 auto hour = rxTimeMatch.capturedView(u"hour").toInt();
101 const auto min = rxTimeMatch.capturedView(u"min").toInt();
102 if (hour < 0 || hour > 23 || min < 0 || min > 59) {
103 continue;
104 }
105 const bool isPm = !rxApMatch.capturedView(u"pm").isEmpty() || !rxTimeMatch.capturedView(u"pm").isEmpty();
106 const bool isAm = !rxApMatch.capturedView(u"am").isEmpty() || !rxTimeMatch.capturedView(u"am").isEmpty();
107 if (isPm && isAm) {
108 continue;
109 } else if (isPm && hour < 12) {
110 hour += 12;
111 } else if (isAm && hour == 12) {
112 hour = 0;
113 }
114
115 Result result;
116 result.dateTime = QTime(hour, min);
117 result.begin = rxTimeMatch.capturedStart();
118 result.end = i;
119 m_results.push_back(std::move(result));
120 }
121}
122
123static int monthToNumber(QStringView month)
124{
125 bool result = false;
126 auto num = month.toInt(&result);
127 if (result) {
128 return num;
129 }
130
131 for (int i = 1; i <= 12; ++i) {
132 if (QLocale::c().monthName(i, QLocale::ShortFormat).compare(month, Qt::CaseInsensitive) == 0) {
133 return i;
134 }
135 }
136 return 0;
137}
138
139void TimeFinder::findDates(QStringView text)
140{
141 // ### unlike times, this is far from complete and is only here to the extend necessary for
142 // detecting generation timestamps we don't want to consider in boarding passes
143 static const QRegularExpression rxDates[] = {
144 QRegularExpression(QStringLiteral("(?<day>\\d\\d)\\.(?<mon>\\d\\d)\\.(?<year>\\d{4})")),
145 QRegularExpression(QStringLiteral("(?<day>\\d\\d) (?<mon>[A-Z][a-zA-Z]{2}) (?<year>\\d{4})")),
146 };
147
148 for (const auto &rx : rxDates) {
149 for (int idx = 0; idx < text.size(); ++idx) {
150 const auto rxDateMatch = rx.matchView(text, idx);
151 if (!rxDateMatch.hasMatch()) {
152 break;
153 }
154
155 idx = rxDateMatch.capturedEnd();
156 const auto day = rxDateMatch.capturedView(u"day").toInt();
157 const auto month = monthToNumber(rxDateMatch.capturedView(u"mon"));
158 const auto year = rxDateMatch.capturedView(u"year").toInt();
159 QDate date(year, month, day);
160 if (!date.isValid()) {
161 continue;
162 }
163
164 Result result;
165 result.dateTime = date;
166 result.begin = rxDateMatch.capturedStart();
167 result.end = idx;
168 m_results.push_back(std::move(result));
169 }
170 }
171}
172
173void TimeFinder::mergeResults()
174{
175 std::sort(m_results.begin(), m_results.end(), [](const auto &lhs, const auto &rhs) {
176 return lhs.begin < rhs.begin;
177 });
178
179 for (auto it = m_results.begin(); it != m_results.end() && it != std::prev(m_results.end());) {
180 auto nextIt = std::next(it);
181 if ((*it).end + 1 == (*nextIt).begin && (*it).dateTime.userType() == QMetaType::QDate && (*nextIt).dateTime.userType() == QMetaType::QTime) {
182 (*it).end = (*nextIt).end;
183 (*it).dateTime = QDateTime((*it).dateTime.toDate(), (*nextIt).dateTime.toTime());
184 it = m_results.erase(nextIt);
185 } else {
186 ++it;
187 }
188 }
189}
190
191const std::vector<TimeFinder::Result>& TimeFinder::results() const
192{
193 return m_results;
194}
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
const QList< QKeySequence > & find()
bool isDigit(char32_t ucs4)
bool isLetter(char32_t ucs4)
QLocale c()
QRegularExpressionMatch matchView(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
qsizetype capturedEnd(QStringView name) const const
qsizetype capturedStart(QStringView name) const const
QStringView capturedView(QStringView name) const const
bool hasMatch() const const
bool isEmpty() const const
qsizetype size() const const
int toInt(bool *ok, int base) const const
CaseInsensitive
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.