Commit 11f54815 authored by Volker Krause's avatar Volker Krause
Browse files

Expand time finder to also do basic date parsing

This is just enough to be able to filter out generation timestamps from
boarding passes for now, but can potentially be extended further.
parent a5f8115f
......@@ -51,8 +51,8 @@ private Q_SLOTS:
TimeFinder finder;
finder.find(input);
QCOMPARE(finder.times().size(), 1);
QCOMPARE(finder.times()[0], time);
QCOMPARE(finder.results().size(), 1);
QCOMPARE(finder.results()[0].dateTime.toTime(), time);
}
void testTimeFinderNone_data()
......@@ -80,17 +80,38 @@ private Q_SLOTS:
TimeFinder finder;
finder.find(input);
QCOMPARE(finder.times().size(), 0);
QCOMPARE(finder.results().size(), 0);
}
void testTimeFinderPlural()
{
TimeFinder finder;
finder.find(u"from 09:00 to 17:00");
QCOMPARE(finder.times().size(), 2);
QCOMPARE(finder.times()[0], QTime(9, 0));
QCOMPARE(finder.times()[1], QTime(17, 0));
QCOMPARE(finder.results().size(), 2);
QCOMPARE(finder.results()[0].dateTime.toTime(), QTime(9, 0));
QCOMPARE(finder.results()[1].dateTime.toTime(), QTime(17, 0));
}
void testTimeFinderDateTime_data()
{
QTest::addColumn<QString>("input");
QTest::addColumn<QDateTime>("dateTime");
QTest::newRow("US") << s("26 Sep 2021 16:43") << QDateTime({2021, 9, 26}, {16, 43});
QTest::newRow("US") << s("26.09.2021 16:43") << QDateTime({2021, 9, 26}, {16, 43});
}
void testTimeFinderDateTime()
{
QFETCH(QString, input);
QFETCH(QDateTime, dateTime);
TimeFinder finder;
finder.find(input);
QCOMPARE(finder.results().size(), 1);
QCOMPARE(finder.results()[0].dateTime.toDateTime(), dateTime);
}
};
QTEST_GUILESS_MAIN(TimeFinderTest)
......
......@@ -6,6 +6,7 @@
#include "timefinder_p.h"
#include <QDebug>
#include <QLocale>
#include <QRegularExpression>
#include <QStringView>
#include <QTime>
......@@ -20,7 +21,19 @@ static bool isSeparator(QChar c)
void TimeFinder::find(QStringView text)
{
m_times.clear();
findTimes(text);
if (!m_results.empty()) {
findDates(text);
mergeResults();
}
for (const auto &res : m_results) {
qDebug() << " " << res.dateTime << res.begin << res.end;
}
}
void TimeFinder::findTimes(QStringView text)
{
static const QRegularExpression rxTimes[] = {
QRegularExpression(QStringLiteral("(?<hour>\\d?\\d)時(?<min>\\d\\d)分")),
QRegularExpression(QStringLiteral("(?:(?<am>오전)|(?<pm>오후) ?)?(?<hour>\\d?\\d)시 ?(?<min>\\d?\\d)분")),
......@@ -72,13 +85,13 @@ void TimeFinder::find(QStringView text)
continue;
}
auto hour = rxTimeMatch.captured(u"hour").toInt();
const auto min = rxTimeMatch.captured(u"min").toInt();
auto hour = rxTimeMatch.capturedView(u"hour").toInt();
const auto min = rxTimeMatch.capturedView(u"min").toInt();
if (hour < 0 || hour > 23 || min < 0 || min > 59) {
continue;
}
const bool isPm = !rxApMatch.captured(u"pm").isEmpty() || !rxTimeMatch.captured(u"pm").isEmpty();
const bool isAm = !rxApMatch.captured(u"am").isEmpty() || !rxTimeMatch.captured(u"am").isEmpty();
const bool isPm = !rxApMatch.capturedView(u"pm").isEmpty() || !rxTimeMatch.capturedView(u"pm").isEmpty();
const bool isAm = !rxApMatch.capturedView(u"am").isEmpty() || !rxTimeMatch.capturedView(u"am").isEmpty();
if (isPm && isAm) {
continue;
} else if (isPm && hour < 12) {
......@@ -90,6 +103,79 @@ void TimeFinder::find(QStringView text)
if (std::find(m_times.begin(), m_times.end(), QTime(hour, min)) == m_times.end()) {
m_times.push_back(QTime(hour, min));
}
Result result;
result.dateTime = QTime(hour, min);
result.begin = rxTimeMatch.capturedStart();
result.end = i;
m_results.push_back(std::move(result));
}
}
static int monthToNumber(QStringView month)
{
bool result = false;
auto num = month.toInt(&result);
if (result) {
return num;
}
for (int i = 1; i <= 12; ++i) {
if (QLocale::c().monthName(i, QLocale::ShortFormat).compare(month, Qt::CaseInsensitive) == 0) {
return i;
}
}
return 0;
}
void TimeFinder::findDates(QStringView text)
{
// ### unlike times, this is far from complete and is only here to the extend necessary for
// detecting generation timestamps we don't want to consider in boarding passes
static const QRegularExpression rxDates[] = {
QRegularExpression(QStringLiteral("(?<day>\\d\\d)\\.(?<mon>\\d\\d)\\.(?<year>\\d{4})")),
QRegularExpression(QStringLiteral("(?<day>\\d\\d) (?<mon>[A-Z][a-zA-Z]{2}) (?<year>\\d{4})")),
};
for (const auto &rx : rxDates) {
for (int idx = 0; idx < text.size(); ++idx) {
const auto rxDateMatch = rx.match(text, idx);
if (!rxDateMatch.hasMatch()) {
break;
}
idx = rxDateMatch.capturedEnd();
const auto day = rxDateMatch.capturedView(u"day").toInt();
const auto month = monthToNumber(rxDateMatch.capturedView(u"mon"));
const auto year = rxDateMatch.capturedView(u"year").toInt();
QDate date(year, month, day);
if (!date.isValid()) {
continue;
}
Result result;
result.dateTime = date;
result.begin = rxDateMatch.capturedStart();
result.end = idx;
m_results.push_back(std::move(result));
}
}
}
void TimeFinder::mergeResults()
{
std::sort(m_results.begin(), m_results.end(), [](const auto &lhs, const auto &rhs) {
return lhs.begin < rhs.begin;
});
for (auto it = m_results.begin(); it != m_results.end() && it != std::prev(m_results.end());) {
auto nextIt = std::next(it);
if ((*it).end + 1 == (*nextIt).begin && (*it).dateTime.type() == QVariant::Date && (*nextIt).dateTime.type() == QVariant::Time) {
(*it).end = (*nextIt).end;
(*it).dateTime = QDateTime((*it).dateTime.toDate(), (*nextIt).dateTime.toTime());
it = m_results.erase(nextIt);
} else {
++it;
}
}
}
......@@ -97,3 +183,8 @@ const std::vector<QTime>& TimeFinder::times() const
{
return m_times;
}
const std::vector<TimeFinder::Result>& TimeFinder::results() const
{
return m_results;
}
......@@ -9,6 +9,8 @@
class QStringView;
class QTime;
#include <QVariant>
#include <vector>
namespace KItinerary {
......@@ -21,8 +23,21 @@ public:
const std::vector<QTime>& times() const;
struct Result {
int begin = -1;
int end = -1;
QVariant dateTime;
};
const std::vector<Result>& results() const;
private:
void findTimes(QStringView text);
void findDates(QStringView text);
void mergeResults();
std::vector<QTime> m_times;
std::vector<Result> m_results;
};
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment