File indexing completed on 2024-05-19 13:15:16
0001 /* 0002 * SPDX-FileCopyrightText: 2006 Aaron Seigo <aseigo@kde.org> 0003 * SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com> 0004 * SPDX-FileCopyrightText: 2015 Vishesh Handa <vhanda@kde.org> 0005 * SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de> 0006 * 0007 * SPDX-License-Identifier: LGPL-2.0-only 0008 */ 0009 0010 #include "datetimerunner.h" 0011 0012 #include <QIcon> 0013 #include <QLocale> 0014 #include <QTimeZone> 0015 0016 #include <KFormat> 0017 #include <KLocalizedString> 0018 0019 #include <math.h> 0020 0021 static const QString dateWord = i18nc("Note this is a KRunner keyword", "date"); 0022 static const QString timeWord = i18nc("Note this is a KRunner keyword", "time"); 0023 0024 DateTimeRunner::DateTimeRunner(QObject *parent, const KPluginMetaData &metaData, const QVariantList &args) 0025 : AbstractRunner(parent, metaData, args) 0026 { 0027 setObjectName(QLatin1String("DateTimeRunner")); 0028 0029 addSyntax(RunnerSyntax(dateWord, i18n("Displays the current date"))); 0030 addSyntax(RunnerSyntax(timeWord, i18n("Displays the current time"))); 0031 addSyntax(RunnerSyntax(dateWord + i18nc("The <> and space are part of the example query", " <timezone>"), // 0032 i18n("Displays the current date and difference to system date in a given timezone"))); 0033 addSyntax(RunnerSyntax(timeWord + i18nc("The <> and space are part of the example query", " <timezone>"), // 0034 i18n("Displays the current time and difference to system time in a given timezone"))); 0035 setTriggerWords({timeWord, dateWord}); 0036 } 0037 0038 DateTimeRunner::~DateTimeRunner() 0039 { 0040 } 0041 0042 void DateTimeRunner::match(RunnerContext &context) 0043 { 0044 const QString term = context.query(); 0045 if (term.compare(dateWord, Qt::CaseInsensitive) == 0) { 0046 const QDate date = QDate::currentDate(); 0047 const QString dateStr = QLocale().toString(date); 0048 addMatch(i18n("Today's date is %1", dateStr), dateStr, 1.0, QStringLiteral("view-calendar-day"), context); 0049 } else if (term.startsWith(dateWord + QLatin1Char(' '), Qt::CaseInsensitive)) { 0050 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0051 const auto zoneTerm = term.rightRef(term.length() - dateWord.length() - 1); 0052 #else 0053 const auto zoneTerm = QStringView(term).right(term.length() - dateWord.length() - 1); 0054 #endif 0055 const auto zones = datetimeAt(zoneTerm); 0056 for (auto it = zones.constBegin(), itEnd = zones.constEnd(); it != itEnd; ++it) { 0057 const QString zoneStr = it.key(); 0058 const QDateTime datetime = it.value(); 0059 const QString dateStr = QLocale().toString(datetime.date()); 0060 0061 const qint64 dateDiff = QDateTime::currentDateTime().daysTo(QDateTime(datetime.date(), datetime.time())) * (24 * 60 * 60 * 1000); // full days in ms 0062 const QString dateDiffStr = dateDiff > 0 ? i18nc("date difference between time zones, e.g. in Stockholm it's 1 calendar day later than in Brasilia", 0063 "%1 later", 0064 KFormat().formatSpelloutDuration(abs(dateDiff))) 0065 : dateDiff < 0 0066 ? i18nc("date difference between time zones, e.g. in Brasilia it's 1 calendar day earlier than in Stockholm", 0067 "%1 earlier", 0068 KFormat().formatSpelloutDuration(abs(dateDiff))) 0069 : i18nc("no date difference between time zones, e.g. in Stockholm it's the same calendar day as in Berlin", "no date difference"); 0070 0071 addMatch(QStringLiteral("%1: %2 (%3)").arg(zoneStr, dateStr, dateDiffStr), 0072 dateStr, 0073 ((qreal)(zoneStr.count(zoneTerm, Qt::CaseInsensitive)) * zoneTerm.length() - (qreal)zoneStr.indexOf(zoneTerm, Qt::CaseInsensitive)) 0074 / zoneStr.length(), 0075 QStringLiteral("view-calendar-day"), 0076 context); 0077 } 0078 } else if (term.compare(timeWord, Qt::CaseInsensitive) == 0) { 0079 const QTime time = QTime::currentTime(); 0080 const QString timeStr = QLocale().toString(time); 0081 addMatch(i18n("Current time is %1", timeStr), timeStr, 1.0, QStringLiteral("clock"), context); 0082 } else if (term.startsWith(timeWord + QLatin1Char(' '), Qt::CaseInsensitive)) { 0083 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0084 const auto zoneTerm = term.rightRef(term.length() - timeWord.length() - 1); 0085 #else 0086 const auto zoneTerm = QStringView(term).right(term.length() - timeWord.length() - 1); 0087 #endif 0088 const auto zones = datetimeAt(zoneTerm); 0089 for (auto it = zones.constBegin(), itEnd = zones.constEnd(); it != itEnd; ++it) { 0090 const QString zoneStr = it.key(); 0091 const QDateTime datetime = it.value(); 0092 const QString timeStr = QLocale().toString(datetime.time(), QLocale::ShortFormat); 0093 0094 const qint64 dateDiff = QDateTime::currentDateTime().daysTo(QDateTime(datetime.date(), datetime.time())) * (24 * 60 * 60 * 1000); // full days in ms 0095 const QString dayDiffStr = dateDiff > 0 ? QString(" + %1").arg(KFormat().formatSpelloutDuration(abs(dateDiff))) 0096 : dateDiff < 0 ? QString(" - %1").arg(KFormat().formatSpelloutDuration(abs(dateDiff))) 0097 : QString(); 0098 0099 const qint64 timeDiff = round((double)QDateTime::currentDateTime().secsTo(QDateTime(datetime.date(), datetime.time())) / 60) 0100 * (60 * 1000); // time in ms rounded to the nearest full minutes 0101 const QString timeDiffStr = timeDiff > 0 ? i18nc("time difference between time zones, e.g. in Stockholm it's 4 hours later than in Brasilia", 0102 "%1 later", 0103 KFormat().formatSpelloutDuration(abs(timeDiff))) 0104 : timeDiff < 0 ? i18nc("time difference between time zones, e.g. in Brasilia it's 4 hours ealier than in Stockholm", 0105 "%1 earlier", 0106 KFormat().formatSpelloutDuration(abs(timeDiff))) 0107 : i18nc("no time difference between time zones, e.g. in Stockholm it's the same time as in Berlin", "no time difference"); 0108 0109 addMatch(QStringLiteral("%1: %2%3 (%4)").arg(zoneStr, timeStr, dayDiffStr, timeDiffStr), 0110 timeStr, 0111 ((qreal)(zoneStr.count(zoneTerm, Qt::CaseInsensitive)) * zoneTerm.length() - (qreal)zoneStr.indexOf(zoneTerm, Qt::CaseInsensitive)) 0112 / zoneStr.length(), 0113 QStringLiteral("clock"), 0114 context); 0115 } 0116 } 0117 } 0118 0119 void DateTimeRunner::run(const RunnerContext &context, const QueryMatch &match) 0120 { 0121 const QString clipboardText = match.data().toString(); 0122 context.requestQueryStringUpdate(clipboardText, clipboardText.length()); 0123 } 0124 0125 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 0126 QHash<QString, QDateTime> DateTimeRunner::datetimeAt(const QStringRef &zoneTerm, const QDateTime referenceTime) 0127 #else 0128 QHash<QString, QDateTime> DateTimeRunner::datetimeAt(const QStringView &zoneTerm, const QDateTime referenceTime) 0129 #endif 0130 { 0131 QHash<QString, QDateTime> ret; 0132 const QList<QByteArray> timeZoneIds = QTimeZone::availableTimeZoneIds(); 0133 for (const QByteArray &zoneId : timeZoneIds) { 0134 QTimeZone timeZone(zoneId); 0135 QDateTime datetime = referenceTime.toTimeZone(QTimeZone(zoneId)); 0136 0137 const QString zoneName = QString::fromUtf8(zoneId); 0138 if (zoneName.startsWith(QStringLiteral("UTC+")) || zoneName.startsWith(QStringLiteral("UTC-"))) { 0139 // Qt time zones are either of the form 0140 // (where {zone name} {long name} {abbreviation} {short name} {offset name} {country}) 0141 // - "Europe/Stockholm" "Central European Standard Time" "CET" "GMT+1" "UTC+01:00" "Sweden" 0142 // - "UTC+01:00" "UTC+01:00" "UTC+01:00" "UTC+01:00" "UTC+01:00" "Default" 0143 // The latter are already covered by the offset name of the former, which we want to match exactly, so skip these 0144 continue; 0145 } 0146 0147 // eg "Sweden" 0148 const QString country = QLocale::countryToString(timeZone.country()); 0149 const QString comment = timeZone.comment(); 0150 if (country.contains(zoneTerm, Qt::CaseInsensitive) || comment.contains(zoneTerm, Qt::CaseInsensitive)) { 0151 const QString regionName = comment.isEmpty() ? country : QLatin1String("%1 - %2").arg(country, comment); 0152 ret[regionName] = datetime; 0153 continue; 0154 } 0155 0156 // eg "Stockholm" 0157 const QString city = zoneName.mid(zoneName.indexOf(QStringLiteral("/")) + 1).replace("_", " "); 0158 if (city.contains(zoneTerm, Qt::CaseInsensitive)) { 0159 ret[city] = datetime; 0160 continue; 0161 } 0162 0163 // eg "Central European Standard Time" 0164 const QString longName = timeZone.displayName(datetime, QTimeZone::LongName); 0165 if (longName.contains(zoneTerm, Qt::CaseInsensitive)) { 0166 ret[longName] = datetime; 0167 continue; 0168 } 0169 0170 // eg "CET" 0171 // FIXME: This only includes the current abbreviation and not old abbreviation or other possible names. 0172 // Eg - depending on the current date, only CET or CEST will work 0173 const QString abbr = timeZone.abbreviation(datetime); 0174 if (abbr.contains(zoneTerm, Qt::CaseInsensitive)) { 0175 // Combine abbreviation with display name to disambiguate 0176 // Eg - Pacific Standard Time (PST) and Philippine Standard Time (PST) 0177 const QString abbrName = QLatin1String("%1 (%2)").arg(longName, abbr); 0178 ret[abbrName] = datetime; 0179 continue; 0180 } 0181 0182 // eg "GMT+1" 0183 const QString shortName = timeZone.displayName(datetime, QTimeZone::ShortName); 0184 if (shortName.compare(zoneTerm, Qt::CaseInsensitive) == 0) { 0185 ret[shortName] = datetime; 0186 continue; 0187 } 0188 0189 // eg "UTC+01:00" 0190 const QString offsetName = timeZone.displayName(datetime, QTimeZone::OffsetName); 0191 if (offsetName.compare(zoneTerm, Qt::CaseInsensitive) == 0) { 0192 ret[offsetName] = datetime; 0193 continue; 0194 } 0195 } 0196 0197 return ret; 0198 } 0199 0200 void DateTimeRunner::addMatch(const QString &text, const QString &clipboardText, const qreal &relevance, const QString &iconName, RunnerContext &context) 0201 { 0202 QueryMatch match(this); 0203 match.setText(text); 0204 match.setData(clipboardText); 0205 match.setType(QueryMatch::HelperMatch); 0206 match.setRelevance(relevance); 0207 match.setIconName(iconName); 0208 match.setMultiLine(true); 0209 0210 context.addMatch(match); 0211 } 0212 0213 K_PLUGIN_CLASS_WITH_JSON(DateTimeRunner, "plasma-runner-datetime.json") 0214 0215 #include "datetimerunner.moc"