Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package kopeninghours for openSUSE:Factory 
checked in at 2021-08-16 10:10:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/kopeninghours (Old)
 and      /work/SRC/openSUSE:Factory/.kopeninghours.new.1899 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "kopeninghours"

Mon Aug 16 10:10:28 2021 rev:5 rq:911722 version:21.08.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/kopeninghours/kopeninghours.changes      
2021-07-10 00:01:57.951299012 +0200
+++ /work/SRC/openSUSE:Factory/.kopeninghours.new.1899/kopeninghours.changes    
2021-08-16 10:16:00.138773327 +0200
@@ -1,0 +2,38 @@
+Fri Aug  6 09:40:09 UTC 2021 - Christophe Giboudeaux <[email protected]>
+
+- Update to 21.08.0
+  * New feature release
+  * For more details please see:
+  * https://kde.org/announcements/gear/21.08/
+- No code change since 21.07.90
+
+-------------------------------------------------------------------
+Fri Jul 30 10:05:21 UTC 2021 - Christophe Giboudeaux <[email protected]>
+
+- Update to 21.07.90
+  * New feature release
+- No code change since 21.07.80
+
+-------------------------------------------------------------------
+Sat Jul 17 20:06:29 UTC 2021 - Christophe Giboudeaux <[email protected]>
+
+- Update to 21.07.80
+  * New feature release
+- Changes since 21.04.3:
+  * Migrate to new REUSE CI check
+  * Fix invalid simplification on wrapped weekday selectors
+  * Work around qmlplugindump choking on gadget singletons
+  * Fix crash in simplifiedExpression for a null expression
+  * Simplify weekday lists: Mo,Tu,We => Mo-We
+  * Add support for a dot after short weekday names (Mo. Tu. We. etc.)
+  * Add support for "whitsun +1 day", should be "easter +50 days"
+  * Add support for non-standard "whitsun" = easter +49 days
+  * Also perform simplification in the command line validation tool
+  * The recent commit "generalize to comma" was about a simplification.
+  * Add simplifiedExpression()
+  * Generalize time-only-autocorrect to work with ',' as well.
+  * Fix crash in my recently-added autocorrect check
+  * autocorrect wrong use of ';' where ',' was expected
+- Only install the license files once
+
+-------------------------------------------------------------------

Old:
----
  kopeninghours-21.04.3.tar.xz
  kopeninghours-21.04.3.tar.xz.sig

New:
----
  kopeninghours-21.08.0.tar.xz
  kopeninghours-21.08.0.tar.xz.sig

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ kopeninghours.spec ++++++
--- /var/tmp/diff_new_pack.JuXRVU/_old  2021-08-16 10:16:00.546772836 +0200
+++ /var/tmp/diff_new_pack.JuXRVU/_new  2021-08-16 10:16:00.546772836 +0200
@@ -18,7 +18,7 @@
 
 %bcond_without lang
 Name:           kopeninghours
-Version:        21.04.3
+Version:        21.08.0
 Release:        0
 Summary:        OSM opening hours expression parser and evaluator
 License:        LGPL-2.0-or-later
@@ -78,7 +78,6 @@
 %postun -n libKOpeningHours1 -p /sbin/ldconfig
 
 %files
-%license LICENSES/*
 %dir %{_kf5_qmldir}/org
 %dir %{_kf5_qmldir}/org/kde
 %{_kf5_qmldir}/org/kde/kopeninghours/
@@ -89,7 +88,6 @@
 %{_kf5_libdir}/libKOpeningHours.so.*
 
 %files devel
-%license LICENSES/*
 %{_includedir}/KOpeningHours/
 %{_includedir}/kopeninghours/
 %{_includedir}/kopeninghours_version.h


++++++ kopeninghours-21.04.3.tar.xz -> kopeninghours-21.08.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/.gitlab-ci.yml 
new/kopeninghours-21.08.0/.gitlab-ci.yml
--- old/kopeninghours-21.04.3/.gitlab-ci.yml    2021-07-05 20:56:43.000000000 
+0200
+++ new/kopeninghours-21.08.0/.gitlab-ci.yml    2021-08-04 23:42:48.000000000 
+0200
@@ -1,9 +1,5 @@
 # SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
 # SPDX-License-Identifier: CC0-1.0
 
-reuse:
-  image:
-    name: fsfe/reuse:latest
-    entrypoint: [""]
-  script:
-    - reuse lint
+include:
+  - https://invent.kde.org/sysadmin/ci-tooling/raw/master/invent/ci-reuse.yml
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/CMakeLists.txt 
new/kopeninghours-21.08.0/CMakeLists.txt
--- old/kopeninghours-21.04.3/CMakeLists.txt    2021-07-05 20:56:43.000000000 
+0200
+++ new/kopeninghours-21.08.0/CMakeLists.txt    2021-08-04 23:42:48.000000000 
+0200
@@ -5,8 +5,8 @@
 
 # KDE Application Version, managed by release script
 set (RELEASE_SERVICE_VERSION_MAJOR "21")
-set (RELEASE_SERVICE_VERSION_MINOR "04")
-set (RELEASE_SERVICE_VERSION_MICRO "3")
+set (RELEASE_SERVICE_VERSION_MINOR "08")
+set (RELEASE_SERVICE_VERSION_MICRO "0")
 set (RELEASE_SERVICE_VERSION 
"${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
 project(KOpeningHours VERSION ${RELEASE_SERVICE_VERSION})
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/autotests/parsertest.cpp 
new/kopeninghours-21.08.0/autotests/parsertest.cpp
--- old/kopeninghours-21.04.3/autotests/parsertest.cpp  2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/autotests/parsertest.cpp  2021-08-04 
23:42:48.000000000 +0200
@@ -18,9 +18,11 @@
     {
         QTest::addColumn<QByteArray>("input");
         QTest::addColumn<QByteArray>("expectedOutput");
+        QTest::addColumn<QByteArray>("expectedSimplifiedOutput");
 
-#define T(x) QTest::newRow(x) << QByteArray(x) << QByteArray(x)
-#define T2(x, y) QTest::newRow(x) << QByteArray(x) << QByteArray(y)
+#define T(x) QTest::newRow(x) << QByteArray(x) << QByteArray(x) << 
QByteArray(x)
+#define T2(x, y) QTest::newRow(x) << QByteArray(x) << QByteArray(y) << 
QByteArray(y)
+#define T3(x, y, z) QTest::newRow(x) << QByteArray(x) << QByteArray(y ? y : x) 
<< QByteArray(z)
         T("24/7");
         T("24/7 \"comment\"");
         T("24/7 closed");
@@ -41,6 +43,8 @@
         T("easter off");
         T("easter +1 day off");
         T("easter -2 days off");
+        T2("whitsun off", "easter +49 days off");
+        T2("whitsun +1 day off", "easter +50 days off");
         T("2020");
         T("2020-2021");
         T("1970-2022/2");
@@ -71,13 +75,15 @@
         T2("Dec 01 -Su 08:00-12:00", "Dec 01 Su[-1] 08:00-12:00");
         T("Aug Mo[1]-Aug Sa[-1] closed");
         T("2020/2");
-        T("\"Au??erhalb der Semesterferien\": Mo-Fr 08:00-22:00; Sa-Su 
10:00-20:00; \"Innerhalb der Semesterferien\": Mo-Fr 08:00-18:00; Sa-Su 
10:00-16:00");
+        T("\"Au??erhalb der Semesterferien\": Mo-Fr 08:00-22:00; Sa,Su 
10:00-20:00; \"Innerhalb der Semesterferien\": Mo-Fr 08:00-18:00; Sa,Su 
10:00-16:00");
         T("PH Mo-Th 14:00-23:00");
         T2("Mo-Th PH 14:00-23:00", "PH Mo-Th 14:00-23:00");
         T("PH Fr,SH Fr 11:30-02:00");
         T2("PH Fr, SH Fr 11:30-02:00", "PH Fr,SH Fr 11:30-02:00");
         T2("PH Th,Fr, SH Tu,Fr 11:30-02:00", "PH Th,Fr,SH Tu,Fr 11:30-02:00");
-        T2("Mo-Th 17:00-23:00, Mo-Th SH 14:00-23:00, Fr 14:00-02:00, Sa 
11:30-22:00, Su 11:30-22:00, PH Mo-Th 11:30-23:00, PH Fr, SH Fr 11:30-02:00, PH 
Sa-Su 11:30-22:00", "Mo-Th 17:00-23:00, SH Mo-Th 14:00-23:00, Fr 14:00-02:00, 
Sa 11:30-22:00, Su 11:30-22:00, PH Mo-Th 11:30-23:00, PH Fr,SH Fr 11:30-02:00, 
PH Sa-Su 11:30-22:00");
+        T3("Mo-Th 17:00-23:00, Mo-Th SH 14:00-23:00, Fr 14:00-02:00, Sa 
11:30-22:00, Su 11:30-22:00, PH Mo-Th 11:30-23:00, PH Fr, SH Fr 11:30-02:00, PH 
Sa-Su 11:30-22:00",
+           "Mo-Th 17:00-23:00, SH Mo-Th 14:00-23:00, Fr 14:00-02:00, Sa 
11:30-22:00, Su 11:30-22:00, PH Mo-Th 11:30-23:00, PH Fr,SH Fr 11:30-02:00, PH 
Sa-Su 11:30-22:00",
+           "Mo-Th 17:00-23:00, SH Mo-Th 14:00-23:00, Fr 14:00-02:00, Sa,Su 
11:30-22:00, PH Mo-Th 11:30-23:00, PH Fr,SH Fr 11:30-02:00, PH Sa-Su 
11:30-22:00");
         T("Sa,Su,PH,Mo");
         T2("Tu-Fr 10:00-17:00; Th 10:00-20:00; Sa,Su,PH Mo 11:00-18:00", 
"Tu-Fr 10:00-17:00; Th 10:00-20:00; Sa,Su,PH,Mo 11:00-18:00");
         T2("Mo,We,Th PH off", "PH Mo,We,Th off");
@@ -86,6 +92,7 @@
         T("Mar 01-Mar Su[-2] Mo 17:00-18:00; PH off");
         T("Oct Su[-1]-Dec 31 08:00-18:00");
         T("Oct Su[-1]-Dec 31 Su 08:00-18:00");
+        T("Mar Su[1]-Oct Su[1]: 11:00-20:00; PH 11:00-20:00");
 
         // from 
https://wiki.openstreetmap.org/wiki/Key:opening_hours#Simple_examples
         T("Mo-Fr 08:00-17:30");
@@ -95,7 +102,7 @@
         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00; PH 09:00-12:00");
 
         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Examples
-        T("Sa-Su 00:00-24:00");
+        T3("Sa-Su 00:00-24:00", nullptr, "Sa,Su 00:00-24:00");
         T("Mo-Fr 08:30-20:00");
         T("Mo 10:00-12:00,12:30-15:00; Tu-Fr 08:00-12:00,12:30-15:00; Sa 
08:00-12:00");
         T("Mo-Su 08:00-18:00; Apr 10-15 off; Jun 08:00-14:00; Aug off; Dec 25 
off");
@@ -105,14 +112,14 @@
         T("Su 10:00+");
         T2("week 1-53/2 Fr 09:00-12:00; week 2-52/2 We 09:00-12:00", "week 
01-53/2 Fr 09:00-12:00; week 02-52/2 We 09:00-12:00");
         T("Mo-Sa 08:00-13:00,14:00-17:00 || \"by appointment\"");
-        T("Su-Tu 11:00-01:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 
11:00-07:00");
+        T3("Su-Tu 11:00-01:00, We-Th 11:00-03:00, Fr 11:00-06:00, Sa 
11:00-07:00", nullptr, "Su-Tu 11:00-01:00, We,Th 11:00-03:00, Fr 11:00-06:00, 
Sa 11:00-07:00");
         T("Mo-Su,PH 15:00-03:00; easter -2 days off");
 
         // from https://openingh.openstreetmap.de/evaluation_tool/
         T("Mo-Fr 10:00-20:00; PH off");
         T("Mo,Tu,Th,Fr 12:00-18:00; Sa,PH 12:00-17:00; Th[3],Th[-1] off");
         T("00:00-24:00; Tu-Su,PH 08:30-09:00 off; Tu-Su 14:00-14:30 off; Mo 
08:00-13:00 off");
-        T("Fr-Sa 18:00-06:00; PH off");
+        T3("Fr-Sa 18:00-06:00; PH off", nullptr, "Fr,Sa 18:00-06:00; PH off");
         T("Mo 10:00-12:00,12:30-15:00");
         T("Mo 10:00-12:00,12:30-15:00; Tu-Fr 08:00-12:00,12:30-15:00; Sa 
08:00-12:00");
         T("\"only after registration\"; PH off");
@@ -122,7 +129,7 @@
         T("Mo-Su 22:00-23:00; We,PH off");
         T("We-Fr 10:00-24:00 open \"it is open\" || \"please call\"; PH off");
         T("Mo-Fr 08:00-11:00 || Tu-Th,PH open \"Emergency only\"");
-        T("Tu-Th,We 22:00-23:00 open \"Hot meals\"; PH off");
+        T3("Tu-Th,We 22:00-23:00 open \"Hot meals\"; PH off", nullptr, "Tu-Th 
22:00-23:00 open \"Hot meals\"; PH off"); // We redundant
         T("Mo 12:00-14:00 open \"female only\", Mo 14:00-16:00 open \"male 
only\"; PH off");
         T("Apr: 22:00-23:00; PH off");
         T("Jul-Jan: 22:00-23:00; PH off");
@@ -134,7 +141,7 @@
         T("Mar Su[-1]-Dec Su[1] -2 days: 22:00-23:00; PH off");
         T("Sa[1],Sa[1] +1 day 10:00-12:00 open \"first weekend in the month\"; 
PH off");
         T("Sa[-1],Sa[-1] +1 day 10:00-12:00 open \"last weekend in the 
month\"; PH off");
-        T("Sa-Su 00:00-24:00; PH off");
+        T3("Sa-Su 00:00-24:00; PH off", nullptr, "Sa,Su 00:00-24:00; PH off");
         T("Mo-Fr 00:00-24:00; PH off");
         T("sunrise-sunset open \"Beware of sunburn!\"; PH off");
         T("sunset-sunrise open \"Beware of vampires!\"; PH off");
@@ -173,7 +180,8 @@
         T("Oct: We[1]");
 
         // from 
https://github.com/dfaure/DataNovaImportScripts/blob/master/saved_opening_hours
-        T("Mo-Tu,Th-Fr 09:30-12:00; 2020 Dec 28 off; 2020 Dec 22,2020 Dec 29 
off; We 15:00-17:00; 2020 Dec 23,2020 Dec 30 off; 2020 Dec 24,2020 Dec 31 off; 
Sa 10:00-12:00; 2020 Dec 26,2021 Jan 02 off; PH off");
+        T3("Mo-Tu,Th-Fr 09:30-12:00; 2020 Dec 28 off; 2020 Dec 22,2020 Dec 29 
off; We 15:00-17:00; 2020 Dec 23,2020 Dec 30 off; 2020 Dec 24,2020 Dec 31 off; 
Sa 10:00-12:00; 2020 Dec 26,2021 Jan 02 off; PH off", nullptr,
+           "Mo,Tu,Th,Fr 09:30-12:00; 2020 Dec 28 off; 2020 Dec 22,2020 Dec 29 
off; We 15:00-17:00; 2020 Dec 23,2020 Dec 30 off; 2020 Dec 24,2020 Dec 31 off; 
Sa 10:00-12:00; 2020 Dec 26,2021 Jan 02 off; PH off");
 
         // real-world tests from Osmose that we were handling wrongly
         T("Tu-Fr 11:30-14:30 open, 14:30-18:00 open \"pickup only\", 
18:00-22:00 open");
@@ -181,8 +189,9 @@
         T2("Apr-Sep 09:00-19:00; Mar-Oct. 09:00-18:00; Nov.-Feb. 09:00-17:00", 
"Apr-Sep 09:00-19:00; Mar-Oct 09:00-18:00; Nov-Feb 09:00-17:00");
         T2("week 23-37 12:30-15:00,20:00-23:00; week 01-22,38-53 off", "week 
23-37 12:30-15:00,20:00-23:00; week 01-22,38-53 off");
         T("Mo-Sa 09:00-20:00; Su[-2,-1] 12:30-18:00");
-        T("Su off, Sa-Fr 08:30-13:00; Mo-Th 08:30-13:30,16:00-20:00");
-        T("Mo-Tu 09:00-12:00,14:00-18:00, We closed, Th-Sa 
09:00-12:00,14:00-18:00; Su 09:30-12:30,14:30-18:00");
+        T("Su off, Mo-Fr 08:30-13:00; Mo-Th 08:30-13:30,16:00-20:00");
+        T3("Mo-Tu 09:00-12:00,14:00-18:00, We closed, Th-Sa 
09:00-12:00,14:00-18:00; Su 09:30-12:30,14:30-18:00", nullptr,
+           "Mo,Tu 09:00-12:00,14:00-18:00, We closed, Th-Sa 
09:00-12:00,14:00-18:00; Su 09:30-12:30,14:30-18:00");
         T2("SH Sep-Jun Mo 10:52-15:52", "SH, Sep-Jun Mo 10:52-15:52"); // 
likely incorrect, but at least no information loss
 
         // weekday autocorrect
@@ -270,15 +279,15 @@
         T2("Du lundi au vendredi : 9:00-18:00", "Mo-Fr 09:00-18:00");
 
         // Unicode symbols
-        T2("Mo???Tu", "Mo-Tu");
+        T3("Mo???Tu", "Mo-Tu", "Mo,Tu");
         T2("13???41", "13:41");
         T2("10???00???19???00", "10:00-19:00");
         T2("10???00???17???00", "10:00-17:00");
         T2("11:00???23:00", "11:00-23:00");
         T2("11:00???15:00", "11:00-15:00");
-        T2("We 09:00-18:00\xC2\xA0; Sa-Su 09:00-18:00", "We 09:00-18:00; Sa-Su 
09:00-18:00"); // weird space
+        T2("We 09:00-18:00\xC2\xA0; Sa 09:00-19:00", "We 09:00-18:00; Sa 
09:00-19:00"); // weird space
         T2("LUNDI 08:30?????????17:00", "Mo 08:30-17:00");
-        T2("???,???,???,???,??? 11:00-19:00", "Mo,Th,Fr,Sa,Su 11:00-19:00");
+        T3("???,???,???,???,??? 11:00-19:00", "Mo,Th,Fr,Sa,Su 11:00-19:00", 
"Th-Mo 11:00-19:00");
         T2("???-??? 09:00-18:00", "Mo-Sa 09:00-18:00");
         T2("?????????????????????10:00???19:00", "We-Sa 10:00-19:00");
         T2("????????? ???17:00???23:00", "Mo-Sa 17:00-23:00");
@@ -299,40 +308,65 @@
         T2("Senin-Sabtu 09:00-16:00, Minggu 09:00-18:00", "Mo-Sa 09:00-16:00, 
Su 09:00-18:00");
         T2("Lundi-samedi 8H00-12H00 16H00-20H00 dimanche,jours f??ri??s 
9H00-12H00 17H00-20H00", "Mo-Sa 08:00-12:00,16:00-20:00; Su,PH 
09:00-12:00,17:00-20:00");
         T2("De f??vrier ?? novembre", "Feb-Nov");
-        T2("Ma,Me,Je,Ve 8h-12h30, 14h-19h; Sa 8h-12h30, 14h-18h", "Tu,We,Th,Fr 
08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:00-18:00");
+        T3("Ma,Me,Je,Ve 8h-12h30, 14h-19h; Sa 8h-12h30, 14h-18h", "Tu,We,Th,Fr 
08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:00-18:00", "Tu-Fr 
08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:00-18:00");
         T2("Mo-Fr: 10:00-18:30 Uhr Sa: 10:00-13:30 Uhr", "Mo-Fr 10:00-18:30; 
Sa 10:00-13:30");
         T2("Montag & Dienstag Ruhetag", "Mo,Tu closed");
 
         // recovery from wrong rule separators
-        T2("Fr-Sa 10:00-02:00,Su 10:00-20:00", "Fr-Sa 10:00-02:00, Su 
10:00-20:00");
+        T2("Fr,Sa 10:00-02:00,Su 10:00-20:00", "Fr,Sa 10:00-02:00, Su 
10:00-20:00");
         T2("tu-sa 12:00-14:30,mo-sa 18:30-22:00", "Tu-Sa 12:00-14:30, Mo-Sa 
18:30-22:00");
         T2("Mo 07:00-12:00,Tu 15:00-20:00,We 07:00-12:00,Fr 15:00-20:00", "Mo 
07:00-12:00, Tu 15:00-20:00, We 07:00-12:00, Fr 15:00-20:00");
         T2("Mo-Fr 09:00-17:00 Sa 09:00-14:00", "Mo-Fr 09:00-17:00; Sa 
09:00-14:00");
         T2("Friday 11AM???2:30AM Saturday 10AM???3:30AM Sunday 9AM???4:30AM", 
"Fr 11:00-02:30; Sa 10:00-03:30; Su 09:00-04:30");
+        T2("Mo-Sa 12:00-15:00; 18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
+        T2("Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00", "Mo-Sa 
12:00-15:00,18:00-24:00");
+        T2("Mo 12:00-15:00; Mo 18:00-24:00", "Mo 12:00-15:00,18:00-24:00");
 
         // recovery from wrong time selector separators
-        T2("Dimanche Ferm?? Lundi 08:00 ??? 12:30 14:00 ??? 19:00 Mardi 08:00 
??? 12:30 14:00 ??? 19:00 Mercredi 08:00 ??? 12:30 14:00 ??? 19:00 Jeudi 08:00 
??? 12:30 14:00 ??? 19:00 Vendredi 08:00 ??? 12:30 14:00 ??? 19:00 Samedi 08:00 
??? 12:30 14:30 ??? 18:00", "Su closed; Mo 08:00-12:30,14:00-19:00; Tu 
08:00-12:30,14:00-19:00; We 08:00-12:30,14:00-19:00; Th 
08:00-12:30,14:00-19:00; Fr 08:00-12:30,14:00-19:00; Sa 
08:00-12:30,14:30-18:00");
+        T3("Dimanche Ferm?? Lundi 08:00 ??? 12:30 14:00 ??? 19:00 Mardi 08:00 
??? 12:30 14:00 ??? 19:00 Mercredi 08:00 ??? 12:30 14:00 ??? 19:00 Jeudi 08:00 
??? 12:30 14:00 ??? 19:00 Vendredi 08:00 ??? 12:30 14:00 ??? 19:00 Samedi 08:00 
??? 12:30 14:30 ??? 18:00",
+           "Su closed; Mo 08:00-12:30,14:00-19:00; Tu 08:00-12:30,14:00-19:00; 
We 08:00-12:30,14:00-19:00; Th 08:00-12:30,14:00-19:00; Fr 
08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:30-18:00",
+           "Su closed; Mo-Fr 08:00-12:30,14:00-19:00; Sa 
08:00-12:30,14:30-18:00");
 
         // recovery from slashes abused as rule or timespan separators
         T2("09:00-12:00/13:00-19:00", "09:00-12:00,13:00-19:00");
         T2("10:00 - 13:30 / 17:00 - 20:30", "10:00-13:30,17:00-20:30");
         T2("Mo-Fr 6:00-18:00 / Sa 6:00-13:00 / So 7:00-17:00", "Mo-Fr 
06:00-18:00; Sa 06:00-13:00; Su 07:00-17:00");
+
+        // Simplification of the output
+        T3("Mo 08:00-13:00; Tu 08:00-13:00", nullptr, "Mo,Tu 08:00-13:00");
+        T3("Mo-Th 08:00-13:00; Sa[1],Su[-1] 08:00-13:00", "Mo-Th 08:00-13:00; 
Sa[1],Su[-1] 08:00-13:00", "Mo-Th,Sa[1],Su[-1] 08:00-13:00");
+        T("easter +1 day 08:00-13:00; Tu,Sa,Su 08:00-13:00"); // does not 
simplify
+        T3("Mo-Sa 12:00-15:00, Mo-Sa 18:00-24:00", "Mo-Sa 12:00-15:00, Mo-Sa 
18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
+        T3("Mo 12:00-15:00, Mo 18:00-24:00", "Mo 12:00-15:00, Mo 18:00-24:00", 
"Mo 12:00-15:00,18:00-24:00");
+        T3("Mo-We,Fr,Su 08:00-13:00", "Mo-We,Fr,Su 08:00-13:00", "Su-We,Fr 
08:00-13:00");
+        T3("Mo,We,Th,Tu,Sa 08:00-13:00", "Mo,We,Th,Tu,Sa 08:00-13:00", 
"Mo-Th,Sa 08:00-13:00"); // reordering
+        T3("Mo-Fr,Tu,We 08:00-13:00", "Mo-Fr,Tu,We 08:00-13:00", "Mo-Fr 
08:00-13:00"); // Tu,We already included
+        T3("Sa-Mo 10:00-23:00, Th 10:00-23:00", "Sa-Mo 10:00-23:00, Th 
10:00-23:00", "Sa-Mo,Th 10:00-23:00"); // beginDay > endDay
+        T3("Sa-Mo 10:00-23:00, Fr 10:00-23:00", "Sa-Mo 10:00-23:00, Fr 
10:00-23:00", "Fr-Mo 10:00-23:00"); // beginDay > endDay
+        T3("Su-Th 10:00-23:00, Fr-Sa 10:00-23:00", "Su-Th 10:00-23:00, Fr-Sa 
10:00-23:00", "Mo-Su 10:00-23:00"); // beginDay > endDay
 #undef T
 #undef T2
+#undef T3
     }
 
     void testSuccess()
     {
         QFETCH(QByteArray, input);
         QFETCH(QByteArray, expectedOutput);
+        QFETCH(QByteArray, expectedSimplifiedOutput);
         OpeningHours oh(input);
         QVERIFY(oh.error() != OpeningHours::SyntaxError);
         QCOMPARE(oh.normalizedExpression(), expectedOutput);
+        QCOMPARE(oh.simplifiedExpression(), expectedSimplifiedOutput);
 
         // verify the expressions we generate are parsed correctly as well
         OpeningHours oh2(oh.normalizedExpression());
-        QVERIFY(oh.error() != OpeningHours::SyntaxError);
+        QVERIFY(oh2.error() != OpeningHours::SyntaxError);
         QCOMPARE(oh2.normalizedExpression(), oh.normalizedExpression());
+
+        OpeningHours oh3(oh.simplifiedExpression());
+        QVERIFY(oh3.error() != OpeningHours::SyntaxError);
+        QCOMPARE(oh3.normalizedExpression(), oh.simplifiedExpression());
     }
 
     void testFail_data()
@@ -423,6 +457,8 @@
 
         OpeningHours oh(expression);
         QCOMPARE(oh.error(), error);
+        (void)oh.normalizedExpression(); // don't crash
+        (void)oh.simplifiedExpression(); // don't crash
     }
 };
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/po/eu/kopeninghours.po 
new/kopeninghours-21.08.0/po/eu/kopeninghours.po
--- old/kopeninghours-21.04.3/po/eu/kopeninghours.po    1970-01-01 
01:00:00.000000000 +0100
+++ new/kopeninghours-21.08.0/po/eu/kopeninghours.po    2021-08-06 
02:26:26.000000000 +0200
@@ -0,0 +1,145 @@
+# Translation for kopeninghours.po to Euskara/Basque (eu).
+# Copyright (C) 2021, This file is copyright:
+# This file is distributed under the same license as the kopeninghours package.
+# KDE euskaratzeko proiektuko arduraduna <[email protected]>.
+#
+# Translators:
+# I??igo Salvador Azurmendi <[email protected]>, 2021.
+msgid ""
+msgstr ""
+"Project-Id-Version: kopeninghours\n"
+"Report-Msgid-Bugs-To: https://bugs.kde.org\n";
+"POT-Creation-Date: 2020-12-23 02:25+0100\n"
+"PO-Revision-Date: 2021-08-03 23:07+0200\n"
+"Last-Translator: I??igo Salvador Azurmendi <[email protected]>\n"
+"Language-Team: Basque <[email protected]>\n"
+"Language: eu\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Lokalize 21.04.3\n"
+
+#: lib/display.cpp:28
+#, kde-format
+msgid "Open"
+msgstr "Zabalik"
+
+#: lib/display.cpp:28
+#, kde-format
+msgid "Open (%1)"
+msgstr "Zabalik (%1)"
+
+#: lib/display.cpp:30
+#, kde-format
+msgid "Closed"
+msgstr "Itxita"
+
+#: lib/display.cpp:30
+#, kde-format
+msgid "Closed (%1)"
+msgstr "Itxita (%1)"
+
+#: lib/display.cpp:50
+#, kde-format
+msgid "Open for one more minute"
+msgid_plural "Open for %1 more minutes"
+msgstr[0] "Beste minutu batez zabalik"
+msgstr[1] "Beste %1 minutuz zabalik"
+
+#: lib/display.cpp:51
+#, kde-format
+msgid "Open for one more minute (%2)"
+msgid_plural "Open for %1 more minutes (%2)"
+msgstr[0] "Beste minutu batez zabalik (%2)"
+msgstr[1] "Beste %1 minutuz zabalik (%2)"
+
+#: lib/display.cpp:54
+#, kde-format
+msgid "Currently closed, opens in one minute"
+msgid_plural "Currently closed, opens in %1 minutes"
+msgstr[0] "Une honetan itxita, minutu bat barru zabaltzen du"
+msgstr[1] "Une honetan itxita, %1 minutu barru zabaltzen du"
+
+#: lib/display.cpp:55
+#, kde-format
+msgid "Currently closed (%2), opens in one minute"
+msgid_plural "Currently closed (%2), opens in %1 minutes"
+msgstr[0] "Une honetan itxita (%2), minutu bat barru zabaltzen du"
+msgstr[1] "Une honetan itxita (%2), %1 minutu barru zabaltzen du"
+
+#: lib/display.cpp:68
+#, kde-format
+msgid "Open for one more hour"
+msgid_plural "Open for %1 more hours"
+msgstr[0] "Beste ordu betez zabalik"
+msgstr[1] "Beste %1 orduz zabalik"
+
+#: lib/display.cpp:69
+#, kde-format
+msgid "Open for one more hour (%2)"
+msgid_plural "Open for %1 more hours (%2)"
+msgstr[0] "Beste ordu betez zabalik (%2)"
+msgstr[1] "Beste %1 orduz zabalik (%2)"
+
+#: lib/display.cpp:72
+#, kde-format
+msgid "Currently closed, opens in one hour"
+msgid_plural "Currently closed, opens in %1 hours"
+msgstr[0] "Une honetan itxita, ordubete barru zabaltzen du"
+msgstr[1] "Une honetan itxita, %1 ordu barru zabaltzen du"
+
+#: lib/display.cpp:73
+#, kde-format
+msgid "Currently closed (%2), opens in one hour"
+msgid_plural "Currently closed (%2), opens in %1 hours"
+msgstr[0] "Une honetan itxita (%2), ordubete barru zabaltzen du"
+msgstr[1] "Une honetan itxita (%2), %1 ordu barru zabaltzen du"
+
+#: lib/display.cpp:86
+#, kde-format
+msgid "Open for one more day"
+msgid_plural "Open for %1 more days"
+msgstr[0] "Beste egun batez zabalik"
+msgstr[1] "Beste %1 egunez zabalik"
+
+#: lib/display.cpp:87
+#, kde-format
+msgid "Open for one more day (%2)"
+msgid_plural "Open for %1 more days (%2)"
+msgstr[0] "Beste egun batez zabalik (%2)"
+msgstr[1] "Beste %1 egunez zabalik (%2)"
+
+#: lib/display.cpp:90
+#, kde-format
+msgid "Currently closed, opens in one day"
+msgid_plural "Currently closed, opens in %1 days"
+msgstr[0] "Une honetan itxita, egun bat barru zabaltzen du"
+msgstr[1] "Une honetan itxita, %1 egun barru zabaltzen du"
+
+#: lib/display.cpp:91
+#, kde-format
+msgid "Currently closed (%2), opens in one day"
+msgid_plural "Currently closed (%2), opens in %1 days"
+msgstr[0] "Une honetan itxita (%2), egun bat barru zabaltzen du"
+msgstr[1] "Une honetan itxita (%2), %1 egun barru zabaltzen du"
+
+#: lib/display.cpp:102
+#, kde-format
+msgid "Currently open"
+msgstr "Une honetan zabalik"
+
+#: lib/display.cpp:102
+#, kde-format
+msgid "Currently open (%1)"
+msgstr "Une honetan zabalik (%1)"
+
+#: lib/display.cpp:104
+#, kde-format
+msgid "Currently closed"
+msgstr "Une honetan itxita"
+
+#: lib/display.cpp:104
+#, kde-format
+msgid "Currently closed (%1)"
+msgstr "Une honetan itxita (%1)"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/po/hi/kopeninghours.po 
new/kopeninghours-21.08.0/po/hi/kopeninghours.po
--- old/kopeninghours-21.04.3/po/hi/kopeninghours.po    2021-07-06 
07:30:38.000000000 +0200
+++ new/kopeninghours-21.08.0/po/hi/kopeninghours.po    2021-08-06 
02:26:26.000000000 +0200
@@ -1,13 +1,14 @@
 # Copyright (C) YEAR This file is copyright:
 # This file is distributed under the same license as the kopeninghours package.
 #
+# Sameer Singh <[email protected]>, 2021.
 # Raghavendra Kamath <[email protected]>, 2021.
 msgid ""
 msgstr ""
 "Project-Id-Version: kopeninghours\n"
 "Report-Msgid-Bugs-To: https://bugs.kde.org\n";
 "POT-Creation-Date: 2020-12-23 02:25+0100\n"
-"PO-Revision-Date: 2021-06-29 18:31+0530\n"
+"PO-Revision-Date: 2021-06-27 14:45+0530\n"
 "Last-Translator: Raghavendra Kamath <[email protected]>\n"
 "Language-Team: kde-hindi\n"
 "Language: hi\n"
@@ -30,26 +31,26 @@
 #: lib/display.cpp:30
 #, kde-format
 msgid "Closed"
-msgstr "????????????"
+msgstr "???????????? ??????"
 
 #: lib/display.cpp:30
 #, kde-format
 msgid "Closed (%1)"
-msgstr "(%1) ????????????"
+msgstr "(%1) ???????????? ??????"
 
 #: lib/display.cpp:50
 #, kde-format
 msgid "Open for one more minute"
 msgid_plural "Open for %1 more minutes"
-msgstr[0] "?????? ???????????? ?????? ????????? ???????????? ??????"
-msgstr[1] "%1 ?????????????????? ?????? ????????? ???????????? ??????"
+msgstr[0] "?????? ?????? ???????????? ?????? ????????? ???????????? ??????"
+msgstr[1] "%1 ?????? ?????????????????? ?????? ????????? ???????????? ??????"
 
 #: lib/display.cpp:51
 #, kde-format
 msgid "Open for one more minute (%2)"
 msgid_plural "Open for %1 more minutes (%2)"
-msgstr[0] "?????? ???????????? ?????? ????????? ???????????? ?????? (%2)"
-msgstr[1] "%1 ?????????????????? ?????? ????????? ???????????? ?????? (%2)"
+msgstr[0] "?????? ?????? ???????????? ?????? ????????? ???????????? ?????? 
(%2)"
+msgstr[1] "%1 ?????? ?????????????????? ?????? ????????? ???????????? ?????? 
(%2)"
 
 #: lib/display.cpp:54
 #, kde-format
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/po/zh_CN/kopeninghours.po 
new/kopeninghours-21.08.0/po/zh_CN/kopeninghours.po
--- old/kopeninghours-21.04.3/po/zh_CN/kopeninghours.po 2021-07-06 
07:30:38.000000000 +0200
+++ new/kopeninghours-21.08.0/po/zh_CN/kopeninghours.po 2021-08-06 
02:26:27.000000000 +0200
@@ -3,7 +3,7 @@
 "Project-Id-Version: kdeorg\n"
 "Report-Msgid-Bugs-To: https://bugs.kde.org\n";
 "POT-Creation-Date: 2020-12-23 02:25+0100\n"
-"PO-Revision-Date: 2021-06-20 07:38\n"
+"PO-Revision-Date: 2021-07-26 13:50\n"
 "Last-Translator: \n"
 "Language-Team: Chinese Simplified\n"
 "Language: zh_CN\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/openinghours.cpp 
new/kopeninghours-21.08.0/src/lib/openinghours.cpp
--- old/kopeninghours-21.04.3/src/lib/openinghours.cpp  2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/openinghours.cpp  2021-08-04 
23:42:48.000000000 +0200
@@ -12,6 +12,7 @@
 #include "interval.h"
 #include "rule_p.h"
 #include "logging.h"
+#include "consecutiveaccumulator_p.h"
 
 #include <QDateTime>
 #include <QJsonArray>
@@ -52,48 +53,130 @@
         auto rule = (*it).get();
         auto prevRule = (*(std::prev(it))).get();
 
-        if (rule->m_ruleType != Rule::AdditionalRule || rule->hasComment() || 
prevRule->hasComment() || !prevRule->hasImplicitState()) {
+        if (rule->hasComment() || prevRule->hasComment() || 
!prevRule->hasImplicitState()) {
             continue;
         }
-
         const auto prevRuleSingleSelector = prevRule->selectorCount() == 1;
         const auto curRuleSingleSelector = rule->selectorCount() == 1;
 
-        // the previous rule has no time selector, the current rule only has a 
weekday selector
-        // so we fold the two rules together
-        if (!prevRule->m_timeSelector && prevRule->m_weekdaySelector && 
rule->m_weekdaySelector && !rule->hasWideRangeSelector()) {
-            auto tmp = std::move(rule->m_weekdaySelector);
-            rule->m_weekdaySelector = std::move(prevRule->m_weekdaySelector);
-            rule->m_weekSelector = std::move(prevRule->m_weekSelector);
-            rule->m_monthdaySelector = std::move(prevRule->m_monthdaySelector);
-            rule->m_yearSelector = std::move(prevRule->m_yearSelector);
-            rule->m_colonAfterWideRangeSelector = 
prevRule->m_colonAfterWideRangeSelector;
-            auto *selector = rule->m_weekdaySelector.get();
-            while (selector->rhsAndSelector)
-                selector = selector->rhsAndSelector.get();
-            appendSelector(selector, std::move(tmp));
-            rule->m_ruleType = prevRule->m_ruleType;
-            std::swap(*it, *std::prev(it));
-            it = std::prev(m_rules.erase(it));
-        }
-
-        // the current rule only has a time selector, so we append that to the 
previous rule
-        else if (curRuleSingleSelector && rule->m_timeSelector && 
prevRule->m_timeSelector) {
-            appendSelector(prevRule->m_timeSelector.get(), 
std::move(rule->m_timeSelector));
-            it = std::prev(m_rules.erase(it));
-        }
-
-        // previous is a single monthday selector
-        else if (rule->m_monthdaySelector && prevRuleSingleSelector && 
prevRule->m_monthdaySelector && !isWiderThan(prevRule, rule)) {
-            auto tmp = std::move(rule->m_monthdaySelector);
-            rule->m_monthdaySelector = std::move(prevRule->m_monthdaySelector);
-            appendSelector(rule->m_monthdaySelector.get(), std::move(tmp));
-            rule->m_ruleType = prevRule->m_ruleType;
-            std::swap(*it, *std::prev(it));
-            it = std::prev(m_rules.erase(it));
+        if (rule->m_ruleType == Rule::AdditionalRule) {
+            // the previous rule has no time selector, the current rule only 
has a weekday selector
+            // so we fold the two rules together
+            if (!prevRule->m_timeSelector && prevRule->m_weekdaySelector && 
rule->m_weekdaySelector && !rule->hasWideRangeSelector()) {
+                auto tmp = std::move(rule->m_weekdaySelector);
+                rule->m_weekdaySelector = 
std::move(prevRule->m_weekdaySelector);
+                rule->m_weekSelector = std::move(prevRule->m_weekSelector);
+                rule->m_monthdaySelector = 
std::move(prevRule->m_monthdaySelector);
+                rule->m_yearSelector = std::move(prevRule->m_yearSelector);
+                rule->m_colonAfterWideRangeSelector = 
prevRule->m_colonAfterWideRangeSelector;
+                auto *selector = rule->m_weekdaySelector.get();
+                while (selector->rhsAndSelector)
+                    selector = selector->rhsAndSelector.get();
+                appendSelector(selector, std::move(tmp));
+                rule->m_ruleType = prevRule->m_ruleType;
+                std::swap(*it, *std::prev(it));
+                it = std::prev(m_rules.erase(it));
+            }
+
+            // the current rule only has a time selector, so we append that to 
the previous rule
+            else if (curRuleSingleSelector && rule->m_timeSelector && 
prevRule->m_timeSelector) {
+                appendSelector(prevRule->m_timeSelector.get(), 
std::move(rule->m_timeSelector));
+                it = std::prev(m_rules.erase(it));
+            }
+
+            // previous is a single monthday selector
+            else if (rule->m_monthdaySelector && prevRuleSingleSelector && 
prevRule->m_monthdaySelector && !isWiderThan(prevRule, rule)) {
+                auto tmp = std::move(rule->m_monthdaySelector);
+                rule->m_monthdaySelector = 
std::move(prevRule->m_monthdaySelector);
+                appendSelector(rule->m_monthdaySelector.get(), std::move(tmp));
+                rule->m_ruleType = prevRule->m_ruleType;
+                std::swap(*it, *std::prev(it));
+                it = std::prev(m_rules.erase(it));
+            }
+        } else if (rule->m_ruleType == Rule::NormalRule) {
+            // Previous rule has time and other selectors
+            // Current rule is only a time selector
+            // "Mo-Sa 12:00-15:00; 18:00-24:00" => "Mo-Sa 
12:00-15:00,18:00-24:00"
+            if (curRuleSingleSelector && rule->m_timeSelector
+                    && prevRule->selectorCount() > 1 && 
prevRule->m_timeSelector) {
+                appendSelector(prevRule->m_timeSelector.get(), 
std::move(rule->m_timeSelector));
+                it = std::prev(m_rules.erase(it));
+            }
+
+            // Both rules have exactly the same selector apart from time
+            // Ex: "Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00" => "Mo-Sa 
12:00-15:00,18:00-24:00"
+            // Obviously a bug, it was overwriting the 12:00-15:00 range.
+            // For now this only supports weekday selectors, could be extended
+            else if (rule->selectorCount() == prevRule->selectorCount()
+                     && rule->m_timeSelector && prevRule->m_timeSelector
+                     && !rule->hasComment() && !prevRule->hasComment()
+                     && rule->selectorCount() == 2 && rule->m_weekdaySelector 
&& prevRule->m_weekdaySelector
+                     // slower than writing an operator==, but so much easier 
to write :)
+                     && rule->m_weekdaySelector->toExpression() == 
prevRule->m_weekdaySelector->toExpression()
+                     ) {
+                appendSelector(prevRule->m_timeSelector.get(), 
std::move(rule->m_timeSelector));
+                it = std::prev(m_rules.erase(it));
+            }
+        }
+    }
+}
+
+void OpeningHoursPrivate::simplify()
+{
+    if (m_error == OpeningHours::SyntaxError || m_rules.empty()) {
+        return;
+    }
+
+    for (auto it = std::next(m_rules.begin()); it != m_rules.end(); ++it) {
+        auto rule = (*it).get();
+        auto prevRule = (*(std::prev(it))).get();
+
+        if (rule->m_ruleType == Rule::AdditionalRule || rule->m_ruleType == 
Rule::NormalRule) {
+
+            auto hasNoHoliday = [](WeekdayRange *selector) {
+                return selector->holiday == WeekdayRange::NoHoliday
+                        && !selector->lhsAndSelector;
+            };
+            // Both rules have the same time and a different weekday selector
+            // Mo 08:00-13:00; Tu 08:00-13:00 => Mo,Tu 08:00-13:00
+            if (rule->selectorCount() == prevRule->selectorCount()
+                    && rule->m_timeSelector && prevRule->m_timeSelector
+                    && rule->selectorCount() == 2 && rule->m_weekdaySelector 
&& prevRule->m_weekdaySelector
+                    && hasNoHoliday(rule->m_weekdaySelector.get())
+                    && hasNoHoliday(prevRule->m_weekdaySelector.get())
+                    && *rule->m_timeSelector == *prevRule->m_timeSelector
+                    ) {
+                // We could of course also turn Mo,Tu,We,Th into Mo-Th...
+                appendSelector(prevRule->m_weekdaySelector.get(), 
std::move(rule->m_weekdaySelector));
+                it = std::prev(m_rules.erase(it));
+                continue;
+            }
+        }
+
+        if (rule->m_ruleType == Rule::AdditionalRule) {
+            // Both rules have exactly the same selector apart from time
+            // Ex: "Mo 12:00-15:00, Mo 18:00-24:00" => "Mo 
12:00-15:00,18:00-24:00"
+            // For now this only supports weekday selectors, could be extended
+            if (rule->selectorCount() == prevRule->selectorCount()
+                    && rule->m_timeSelector && prevRule->m_timeSelector
+                    && !rule->hasComment() && !prevRule->hasComment()
+                    && rule->selectorCount() == 2 && rule->m_weekdaySelector 
&& prevRule->m_weekdaySelector
+                    // slower than writing an operator==, but so much easier 
to write :)
+                    && rule->m_weekdaySelector->toExpression() == 
prevRule->m_weekdaySelector->toExpression()
+                    ) {
+                appendSelector(prevRule->m_timeSelector.get(), 
std::move(rule->m_timeSelector));
+                it = std::prev(m_rules.erase(it));
+            }
         }
     }
 
+    // Now try collapsing adjacent week days: Mo,Tu,We => Mo-We
+    for (auto it = m_rules.begin(); it != m_rules.end(); ++it) {
+        auto rule = (*it).get();
+        if (rule->m_weekdaySelector) {
+            rule->m_weekdaySelector->simplify();
+        }
+    }
 }
 
 void OpeningHoursPrivate::validate()
@@ -326,6 +409,12 @@
     return ret;
 }
 
+QByteArray OpeningHours::simplifiedExpression() const
+{
+    d->simplify();
+    return normalizedExpression();
+}
+
 QString OpeningHours::normalizedExpressionString() const
 {
     return QString::fromUtf8(normalizedExpression());
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/openinghours.h 
new/kopeninghours-21.08.0/src/lib/openinghours.h
--- old/kopeninghours-21.04.3/src/lib/openinghours.h    2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/openinghours.h    2021-08-04 
23:42:48.000000000 +0200
@@ -92,6 +92,12 @@
      */
     QByteArray normalizedExpression() const;
 
+    /** Returns a simplified OSM opening hours expression reconstructed from 
this object.
+     * In many cases it will be the same as normalizedExpression(), but further
+     * simplifications can happen, to make the expression shorter/simpler.
+     */
+    QByteArray simplifiedExpression() const;
+
     /** Geographic coordinate at which this expression should be evaluated.
      *  This is needed for expressions containing location-based variable time 
references,
      *  such as "sunset". If the expression requires a location, error() 
returns @c MissingLocation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/openinghours_p.h 
new/kopeninghours-21.08.0/src/lib/openinghours_p.h
--- old/kopeninghours-21.04.3/src/lib/openinghours_p.h  2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/openinghours_p.h  2021-08-04 
23:42:48.000000000 +0200
@@ -26,6 +26,7 @@
 public:
     void finalizeRecovery();
     void autocorrect();
+    void simplify();
     void validate();
     void addRule(Rule *parsedRule);
     void restartFrom(int pos, Rule::Type nextRuleType);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/openinghourslexer.l 
new/kopeninghours-21.08.0/src/lib/openinghourslexer.l
--- old/kopeninghours-21.04.3/src/lib/openinghourslexer.l       2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/openinghourslexer.l       2021-08-04 
23:42:48.000000000 +0200
@@ -71,6 +71,7 @@
 " days" { return T_KEYWORD_DAY; }
 "week" { return T_KEYWORD_WEEK; }
 "easter" { return T_EASTER; }
+"whitsun" { return T_WHITSUN; } // non-standard, will be turned into "easter 
+49 days"
 
   /* am/pm time format support, non-standard and has to appear before the 
generic number token. */
 [0-5]?[0-9](\ ?a\.?m\.?|a) { yylval->num = std::strtol(yytext, nullptr, 10); 
return T_ALT_TIME_AM; }
@@ -98,6 +99,14 @@
 Sat? { yylval->num = 6; return T_WEEKDAY; }
 Sun? { yylval->num = 7; return T_WEEKDAY; }
 
+Mo\. { yylval->num = 1; return T_WEEKDAY; }
+Tu\. { yylval->num = 2; return T_WEEKDAY; }
+We\. { yylval->num = 3; return T_WEEKDAY; }
+Th\. { yylval->num = 4; return T_WEEKDAY; }
+Fr\. { yylval->num = 5; return T_WEEKDAY; }
+Sa\. { yylval->num = 6; return T_WEEKDAY; }
+Su\. { yylval->num = 7; return T_WEEKDAY; }
+
  /* same for month names, technically those should be three letter English 
abbreviations */
 "January" { yylval->num = 1; return T_MONTH; }
 "February" { yylval->num = 2; return T_MONTH; }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/openinghoursparser.y 
new/kopeninghours-21.08.0/src/lib/openinghoursparser.y
--- old/kopeninghours-21.04.3/src/lib/openinghoursparser.y      2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/openinghoursparser.y      2021-08-04 
23:42:48.000000000 +0200
@@ -158,6 +158,7 @@
 %token T_KEYWORD_WEEK
 
 %token T_EASTER
+%token T_WHITSUN
 
 %token <num> T_WEEKDAY
 %token <num> T_MONTH
@@ -667,7 +668,7 @@
 | DateFrom[D] DateOffset[O] {
     $$ = new MonthdayRange;
     $$->begin = $D;
-    $$->begin.offset = $O;
+    $$->begin.offset += $O;
     $$->end = $$->begin;
   }
 | DateFrom[F] RangeSeparator DateTo[T] {
@@ -680,7 +681,7 @@
 | DateFrom[F] DateOffset[OF] RangeSeparator DateTo[T] {
     $$ = new MonthdayRange;
     $$->begin = $F;
-    $$->begin.offset = $OF;
+    $$->begin.offset += $OF;
     $$->end = $T;
     if ($$->end.year == 0) { $$->end.year = $$->begin.year; }
     if ($$->end.month == 0) { $$->end.month = $$->begin.month; }
@@ -691,7 +692,7 @@
     $$->end = $T;
     if ($$->end.year == 0) { $$->end.year = $$->begin.year; }
     if ($$->end.month == 0) { $$->end.month = $$->begin.month; }
-    $$->end.offset = $OT;
+    $$->end.offset += $OT;
   }
 | DateFrom[F] RangeSeparator T_MONTH[M] AltMonthdayOffset[O] {
     $$ = new MonthdayRange;
@@ -706,11 +707,11 @@
 | DateFrom[F] DateOffset[OF] RangeSeparator DateTo[T] DateOffset[OT] {
     $$ = new MonthdayRange;
     $$->begin = $F;
-    $$->begin.offset = $OF;
+    $$->begin.offset += $OF;
     $$->end = $T;
     if ($$->end.year == 0) { $$->end.year = $$->begin.year; }
     if ($$->end.month == 0) { $$->end.month = $$->begin.month; }
-    $$->end.offset = $OT;
+    $$->end.offset += $OT;
   }
 ;
 
@@ -737,6 +738,7 @@
 
 VariableDate:
   T_EASTER { $$ = { 0, 0, 0, Date::Easter, { 0, 0, 0 } }; }
+| T_WHITSUN { $$ = { 0, 0, 0, Date::Easter, { 49, 0, 0 } }; }
 ;
 
 AltMonthdayOffset:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/selectors.cpp 
new/kopeninghours-21.08.0/src/lib/selectors.cpp
--- old/kopeninghours-21.04.3/src/lib/selectors.cpp     2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/selectors.cpp     2021-08-04 
23:42:48.000000000 +0200
@@ -133,6 +133,16 @@
     return expr;
 }
 
+bool Timespan::operator==(Timespan &other) const
+{
+    return begin == other.begin &&
+            end == other.end &&
+            openEnd == other.openEnd &&
+            interval == other.interval &&
+            bool(next) == (bool)other.next &&
+            (!next || *next == *other.next);
+}
+
 int WeekdayRange::requiredCapabilities() const
 {
     // only ranges or nthMask are allowed, not both at the same time, enforced 
by parser
@@ -211,6 +221,104 @@
     return expr;
 }
 
+void WeekdayRange::simplify()
+{
+    QMap<int, WeekdayRange *> endToSelectorMap;
+    bool seenDays[8];
+    const int endIdx = sizeof(seenDays);
+    std::fill(std::begin(seenDays), std::end(seenDays), false);
+    for (WeekdayRange *selector = this; selector; selector = 
selector->next.get()) {
+        // Ensure it's all just week days, no other features
+        if (selector->nthMask || selector->lhsAndSelector || selector->holiday 
!= NoHoliday || selector->offset) {
+            return;
+        }
+        const bool wrap = selector->beginDay > selector->endDay;
+        for (int day = selector->beginDay; day <= selector->endDay + (wrap ? 7 
: 0); ++day) {
+            seenDays[(day - 1) % 7 + 1] = true;
+        }
+        endToSelectorMap.insert(selector->endDay, selector);
+    }
+
+    QString str;
+    for (int idx = 1; idx < endIdx; ++idx) {
+        str += QLatin1Char(seenDays[idx] ? '1' : '0');
+    }
+
+    // Clear everything and refill
+    next.reset(nullptr);
+
+    int startIdx = 1;
+
+    // -1 and +1 in a wrapping world
+    auto prevIdx = [&](int idx) {
+        Q_ASSERT(idx > 0 && idx < 8);
+        return idx == 1 ? 7 : (idx - 1);
+    };
+    auto nextIdx = [&](int idx) {
+        Q_ASSERT(idx > 0 && idx < 8);
+        return idx % 7 + 1;
+    };
+
+    // like std::find, but let's use indexes - and wrap at 8
+    auto find = [&](int idx, bool value) {
+        do {
+            if (seenDays[idx] == value)
+                return idx;
+            idx = nextIdx(idx);
+        } while(idx != startIdx);
+        return idx;
+    };
+    auto findPrev = [&](int idx, bool value) {
+        for (; idx > 0; --idx) {
+            if (seenDays[idx] == value)
+                return idx;
+        }
+        return 0;
+    };
+
+    WeekdayRange *prev = nullptr;
+    WeekdayRange *selector = this;
+
+    auto addRange = [&](int from, int to) {
+        if (prev) {
+            selector = new WeekdayRange;
+            prev->next.reset(selector);
+        }
+        selector->beginDay = from;
+        selector->endDay = to;
+        prev = selector;
+
+    };
+
+    int idx = 0;
+    if (seenDays[1]) {
+        // monday is set, try going further back
+        idx = findPrev(7, false);
+        if (idx) {
+            idx = nextIdx(idx);
+        }
+    }
+    if (idx == 0) {
+        // start at first day being set (Tu or more)
+        idx = find(1, true);
+    }
+    startIdx = idx;
+    Q_ASSERT(startIdx > 0);
+    do {
+        // find end of 'true' range
+        const int finishIdx = find(idx, false);
+        // if the range is only 2 items, prefer Mo,Tu over Mo-Tu
+        if (finishIdx == nextIdx(nextIdx(idx))) {
+            addRange(idx, idx);
+            const int n = nextIdx(idx);
+            addRange(n, n);
+        } else {
+            addRange(idx, prevIdx(finishIdx));
+        }
+        idx = find(finishIdx, true);
+    } while (idx != startIdx);
+}
+
 int Week::requiredCapabilities() const
 {
     if (endWeek < beginWeek) { // is this even officially allowed?
@@ -286,6 +394,18 @@
     return weekday == other.weekday && nthWeekday == other.nthWeekday && 
dayOffset == other.dayOffset;
 }
 
+DateOffset &DateOffset::operator+=(DateOffset other)
+{
+    // Only dayOffset really supports += (this is for whitsun)
+    dayOffset += other.dayOffset;
+    // The others can't possibly be set already
+    Q_ASSERT(weekday == 0);
+    Q_ASSERT(nthWeekday == 0);
+    weekday = other.weekday;
+    nthWeekday = other.nthWeekday;
+    return *this;
+}
+
 bool Date::operator==(Date other) const
 {
     if (variableDate != other.variableDate)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/kopeninghours-21.04.3/src/lib/selectors_p.h 
new/kopeninghours-21.08.0/src/lib/selectors_p.h
--- old/kopeninghours-21.04.3/src/lib/selectors_p.h     2021-07-05 
20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/lib/selectors_p.h     2021-08-04 
23:42:48.000000000 +0200
@@ -124,6 +124,7 @@
     bool isMultiDay(QDate date, OpeningHoursPrivate *context) const;
     SelectorResult nextInterval(const Interval &interval, const QDateTime &dt, 
OpeningHoursPrivate *context) const;
     QByteArray toExpression() const;
+    bool operator==(Timespan &other) const;
 
     Time begin = { Time::NoEvent, -1, -1 };
     Time end = { Time::NoEvent, -1, -1 };
@@ -140,8 +141,9 @@
     SelectorResult nextInterval(const Interval &interval, const QDateTime &dt, 
OpeningHoursPrivate *context) const;
     SelectorResult nextIntervalLocal(const Interval &interval, const QDateTime 
&dt, OpeningHoursPrivate *context) const;
     QByteArray toExpression() const;
+    void simplify();
 
-    uint8_t beginDay = 0;
+    uint8_t beginDay = 0; // Mo=1, Tu=2, ..., Su=7
     uint8_t endDay = 0;
     uint16_t nthMask = 0;
     int16_t offset = 0;
@@ -175,6 +177,7 @@
 {
 public:
     bool operator==(DateOffset other) const;
+    DateOffset &operator+=(DateOffset other);
 
     int16_t dayOffset;
     int8_t weekday;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kopeninghours-21.04.3/src/qml/kopeninghoursqmlplugin.cpp 
new/kopeninghours-21.08.0/src/qml/kopeninghoursqmlplugin.cpp
--- old/kopeninghours-21.04.3/src/qml/kopeninghoursqmlplugin.cpp        
2021-07-05 20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/src/qml/kopeninghoursqmlplugin.cpp        
2021-08-04 23:42:48.000000000 +0200
@@ -9,6 +9,7 @@
 #include <KOpeningHours/IntervalModel>
 #include <KOpeningHours/OpeningHours>
 
+#include <QCoreApplication>
 #include <QQmlContext>
 #include <QQmlEngine>
 #include <QQmlExtensionPlugin>
@@ -47,9 +48,12 @@
 
     qmlRegisterType<KOpeningHours::IntervalModel>("org.kde.kopeninghours", 1, 
0, "IntervalModel");
 
-    qmlRegisterSingletonType("org.kde.kopeninghours", 1, 0, 
"OpeningHoursParser", [](QQmlEngine*, QJSEngine *engine) -> QJSValue {
-        return engine->toScriptValue(KOpeningHours::OpeningHoursFactory());
-    });
+    // HACK qmlplugindump chokes on gadget singletons, to the point of 
breaking ecm_find_qmlmodule()
+    if (QCoreApplication::applicationName() != QLatin1String("qmlplugindump")) 
{
+        qmlRegisterSingletonType("org.kde.kopeninghours", 1, 0, 
"OpeningHoursParser", [](QQmlEngine*, QJSEngine *engine) -> QJSValue {
+            return engine->toScriptValue(KOpeningHours::OpeningHoursFactory());
+        });
+    }
 }
 
 #include "kopeninghoursqmlplugin.moc"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/kopeninghours-21.04.3/tests/osm-opening-hours-validate.cpp 
new/kopeninghours-21.08.0/tests/osm-opening-hours-validate.cpp
--- old/kopeninghours-21.04.3/tests/osm-opening-hours-validate.cpp      
2021-07-05 20:56:43.000000000 +0200
+++ new/kopeninghours-21.08.0/tests/osm-opening-hours-validate.cpp      
2021-08-04 23:42:48.000000000 +0200
@@ -34,6 +34,7 @@
         in.open(stdin, QFile::ReadOnly);
         int total = 0;
         int normalized = 0;
+        int simplified = 0;
         int errors = 0;
         char line[4096];
         while (!in.atEnd()) {
@@ -59,10 +60,18 @@
                     ++normalized;
                     std::cerr << "Expression " << QByteArray(line, 
size).constData() << " normalized to " << n.constData() << std::endl;
                 }
+                const auto simplifiedExpr = oh.simplifiedExpression();
+                if (n != simplifiedExpr) {
+                    ++simplified;
+                    std::cerr << "Expression " << n.constData() << " 
simplified to " << simplifiedExpr.constData() << std::endl;
+                }
             }
         }
 
-        std::cerr << total << " expressions checked, " << errors << " invalid, 
" << normalized << " not in normal form" << std::endl;
+        std::cerr << total << " expressions checked, "
+                  << errors << " invalid, "
+                  << normalized << " not in normal form, "
+                  << simplified << " can be simplified" << std::endl;
         return errors;
     } else {
         OpeningHours oh(parser.positionalArguments().at(0).toUtf8());

Reply via email to