File indexing completed on 2024-03-03 04:52:00

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <KOpeningHours/OpeningHours>
0008 
0009 #include <QTest>
0010 
0011 using namespace KOpeningHours;
0012 
0013 class ParserTest : public QObject
0014 {
0015     Q_OBJECT
0016 private Q_SLOTS:
0017     void testSuccess_data()
0018     {
0019         QTest::addColumn<QByteArray>("input");
0020         QTest::addColumn<QByteArray>("expectedOutput");
0021         QTest::addColumn<QByteArray>("expectedSimplifiedOutput");
0022 
0023         // clang-format off
0024 #define T(x) QTest::newRow(x) << QByteArray(x) << QByteArray(x) << QByteArray(x)
0025 #define T2(x, y) QTest::newRow(x) << QByteArray(x) << QByteArray(y) << QByteArray(y)
0026 #define T3(x, y, z) QTest::newRow(x) << QByteArray(x) << QByteArray(y ? y : x) << QByteArray(z)
0027         T("24/7");
0028         T("24/7 \"comment\"");
0029         T("24/7 closed");
0030         T("24/7 unknown \"comment\"");
0031         T("unknown \"comment\"");
0032         T("off");
0033         T("Dec off");
0034         T("Dec 25 off");
0035 
0036         // Don't repeat the month when it's the same (#446224)
0037         T("Dec 25-26 off");
0038         T("Dec 24-26,31 off");
0039         T2("Jan 1,6 off", "Jan 01,06 off");
0040         T("Dec 24,25,26");
0041         T("Jan 03,Dec 04,24 off");
0042         T2("Jan 03, Dec 04, 24 off", "Jan 03,Dec 04,24 off");
0043         T2("07:30-20:00; Jan 03,13,23,Dec 04,14,24 off", "07:30-20:00; Jan 03,13,23,Dec 04,14,24 off");
0044 
0045         // But do repeat the year and the month, when a year is specified
0046         T2("2023 Apr 26,28 08:00-09:00; PH off", "2023 Apr 26,2023 Apr 28 08:00-09:00; PH off"); // input was incorrect, the year and month must be repeated
0047         T2("2021 Dec 26-28,30-31,2022 Jan 02-03 off", "2021 Dec 26-28,2021 Dec 30-31,2022 Jan 02-03 off");
0048         T2("2021 Dec 22, 26-28, 29, 31, 2022 Jan 02-03 off", "2021 Dec 22,2021 Dec 26-28,2021 Dec 29,2021 Dec 31,2022 Jan 02-03 off");
0049         T("2021 Dec 26-28, Dec 30-31,2022 Jan 02-03 off"); // note that this means Dec 30-31 every year, not just in 2021 (says https://openingh.openstreetmap.de/evaluation_tool/)
0050         T("2023 Apr 26, Apr 28 09:00-10:00; PH off"); // note that this means Apr 28 every year, not just in 2023 (says https://openingh.openstreetmap.de/evaluation_tool/)
0051 
0052         T("Dec 08:00");
0053         T("Dec 08:00-14:00");
0054         T("easter off");
0055         T("easter +1 day off");
0056         T("easter -2 days off");
0057         T2("whitsun off", "easter +49 days off");
0058         T2("whitsun +1 day off", "easter +50 days off");
0059         T("2020");
0060         T("2020-2021");
0061         T("1970-2022/2");
0062         T("2020+");
0063         T("2010,2020,2030");
0064         T("2010-2015,2020-2025,2030");
0065         T("2020-2022 Dec");
0066         T("2020 Dec-2022 Dec");
0067         T("2020-2022 Dec 24-26");
0068         T("2020 Dec 24-26");
0069         T("2021 10:00-20:00");
0070         T("PH off || open"); // https://openingh.openstreetmap.de/evaluation_tool/ says this means always open... bug in opening.js?
0071         T("PH off || unknown \"foo\"");
0072         T("2020 Jan-Apr");
0073         T("1980-2030/4");
0074         T("\"comment\"");
0075         T("PH off || 2020 open");
0076         T("Mo[1-2,4]");
0077         T2("We[-1] + 2 days", "We[-1] +2 days");
0078         T("10:00-16:00/15");
0079         T2("10:00-16:00/90", "10:00-16:00/01:30");
0080         T2("10:00-16:00/1:30", "10:00-16:00/01:30");
0081         T("10:00-10:00");
0082         T("PH off || open || unknown");
0083         T("10:00-12:00+");
0084         T("Jun 15-Aug 15 Mo-Fr 10:00-12:30");
0085         T2("Dec 01 +Su", "Dec 01 Su[1]");
0086         T2("Dec 01 -Su 08:00-12:00", "Dec 01 Su[-1] 08:00-12:00");
0087         T("Aug Mo[1]-Aug Sa[-1] closed");
0088         T("2020/2");
0089         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");
0090         T("PH Mo-Th 14:00-23:00");
0091         T2("Mo-Th PH 14:00-23:00", "PH Mo-Th 14:00-23:00");
0092         T("PH Fr,SH Fr 11:30-02:00");
0093         T2("PH Fr, SH Fr 11:30-02:00", "PH Fr,SH Fr 11:30-02:00");
0094         T2("PH Th,Fr, SH Tu,Fr 11:30-02:00", "PH Th,Fr,SH Tu,Fr 11:30-02:00");
0095         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",
0096            "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",
0097            "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");
0098         T("Sa,Su,PH,Mo");
0099         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");
0100         T2("Mo,We,Th PH off", "PH Mo,We,Th off");
0101         T2("PH closed \"\"", "PH closed");
0102         T("Mar 01-Mar Su[-1] 17:00-18:00; PH off");
0103         T("Mar 01-Mar Su[-2] Mo 17:00-18:00; PH off");
0104         T("Oct Su[-1]-Dec 31 08:00-18:00");
0105         T("Oct Su[-1]-Dec 31 Su 08:00-18:00");
0106         T("Mar Su[1]-Oct Su[1]: 11:00-20:00; PH 11:00-20:00");
0107         T2("Mo 20:00-26:00", "Mo 20:00-26:00"); // https://github.com/osm-fr/osmose-backend/issues/1344
0108 
0109         // https://bugs.kde.org/show_bug.cgi?id=445963
0110         T("Th[1-2] 09:30-11:45");
0111         T("Th[1,2] 09:30-11:45");
0112         T("Mo[1,3,-1]"); // order as desired, -1 means last
0113         T("Mo[1,3,2]"); // currently not normalized
0114 
0115         // https://bugs.kde.org/show_bug.cgi?id=452236
0116         T("2022 Mar 31-2022 Apr 16");
0117         T2("2022 Mar 31-Apr 16", "2022 Mar 31-2022 Apr 16");
0118 
0119         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Simple_examples
0120         T("Mo-Fr 08:00-17:30");
0121         T("Mo-Fr 08:00-12:00,13:00-17:30");
0122         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00");
0123         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00; PH off");
0124         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00; PH 09:00-12:00");
0125 
0126         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Examples
0127         T3("Sa-Su 00:00-24:00", nullptr, "Sa,Su 00:00-24:00");
0128         T("Mo-Fr 08:30-20:00");
0129         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");
0130         T("Mo-Su 08:00-18:00; Apr 10-15 off; Jun 08:00-14:00; Aug off; Dec 25 off");
0131         T("Mo-Sa 10:00-20:00; Tu off");
0132         T("Mo-Sa 10:00-20:00; Tu 10:00-14:00");
0133         T("sunrise-sunset");
0134         T("Su 10:00+");
0135         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");
0136         T("Mo-Sa 08:00-13:00,14:00-17:00 || \"by appointment\"");
0137         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");
0138         T("Mo-Su,PH 15:00-03:00; easter -2 days off");
0139 
0140         // from https://openingh.openstreetmap.de/evaluation_tool/
0141         T("Mo-Fr 10:00-20:00; PH off");
0142         T("Mo,Tu,Th,Fr 12:00-18:00; Sa,PH 12:00-17:00; Th[3],Th[-1] off");
0143         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");
0144         T3("Fr-Sa 18:00-06:00; PH off", nullptr, "Fr,Sa 18:00-06:00; PH off");
0145         T("Mo 10:00-12:00,12:30-15:00");
0146         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");
0147         T("\"only after registration\"; PH off");
0148         T("22:00-23:00; PH off");
0149         T("08:00-11:00; PH off");
0150         T("open; Mo 15:00-16:00 off; PH off");
0151         T("Mo-Su 22:00-23:00; We,PH off");
0152         T("We-Fr 10:00-24:00 open \"it is open\" || \"please call\"; PH off");
0153         T("Mo-Fr 08:00-11:00 || Tu-Th,PH open \"Emergency only\"");
0154         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
0155         T("Mo 12:00-14:00 open \"female only\", Mo 14:00-16:00 open \"male only\"; PH off");
0156         T("Apr: 22:00-23:00; PH off");
0157         T("Jul-Jan: 22:00-23:00; PH off");
0158         T("Jan-Jul: 22:00-23:00; PH off");
0159         T2("Jul 23-Jan 3: \"needs reservation by phone\"; PH off", "Jul 23-Jan 03: \"needs reservation by phone\"; PH off");
0160         T2("Jul 23-Jan 3: 22:00-23:00 \"Please make a reservation by phone.\"; PH off", "Jul 23-Jan 03: 22:00-23:00 \"Please make a reservation by phone.\"; PH off");
0161         T2("Jul 23-Jan 3: 08:00-11:00 \"Please make a reservation by phone.\"; PH off", "Jul 23-Jan 03: 08:00-11:00 \"Please make a reservation by phone.\"; PH off");
0162         T2("Jan 23-Jul 3: 22:00-23:00 \"Please make a reservation by phone.\"; PH off", "Jan 23-Jul 03: 22:00-23:00 \"Please make a reservation by phone.\"; PH off");
0163         T("Mar Su[-1]-Dec Su[1] -2 days: 22:00-23:00; PH off");
0164         T("Sa[1],Sa[1] +1 day 10:00-12:00 open \"first weekend in the month\"; PH off");
0165         T("Sa[-1],Sa[-1] +1 day 10:00-12:00 open \"last weekend in the month\"; PH off");
0166         T3("Sa-Su 00:00-24:00; PH off", nullptr, "Sa,Su 00:00-24:00; PH off");
0167         T("Mo-Fr 00:00-24:00; PH off");
0168         T("sunrise-sunset open \"Beware of sunburn!\"; PH off");
0169         T("sunset-sunrise open \"Beware of vampires!\"; PH off");
0170         T("(sunrise-00:30)-(sunrise+00:30)");
0171         T("(sunset+01:00)-24:00 || closed \"No drink before sunset!\"; PH off");
0172         T("22:00+; PH off");
0173         T("Tu,PH 23:59-22:59");
0174         T("We-Mo,PH 23:59-22:59");
0175         T2("week 2-52/2 We 00:00-24:00; week 1-53/2 Sa 00:00-24:00; PH off", "week 02-52/2 We 00:00-24:00; week 01-53/2 Sa 00:00-24:00; PH off");
0176         T2("week 4-16 We 00:00-24:00; week 38-42 Sa 00:00-24:00; PH off", "week 04-16 We 00:00-24:00; week 38-42 Sa 00:00-24:00; PH off");
0177         T("2012 easter -2 days-2012 easter +2 days: open \"Around easter\"; PH off");
0178         T("24/7 closed \"always closed\"");
0179         T2("2013,2015,2050-2053,2055/2,2020-2029/3,2060+ Jan 1", "2013,2015,2050-2053,2055/2,2020-2029/3,2060+ Jan 01");
0180         T2("Jan 23-Feb 11,Feb 12 00:00-24:00; PH off", "Jan 23-Feb 11,Feb 12 00:00-24:00; PH off");
0181         T("Apr-Oct Su[2] 14:00-18:00; Aug Su[-1] -1 day 10:00-18:00; Aug Su[-1] 10:00-18:00; PH off");
0182         T("Mo-Fr 08:00-12:00, We 14:00-18:00; Su,PH off"); // open We morning too
0183         T("Mo-Fr 08:00-12:00; We 14:00-18:00; Su,PH off"); // closed We morning
0184         T2("April-September; Mo-Fr 09:00-13:00, 14:00-18:00, Sa 10:00-13:00", "Apr-Sep; Mo-Fr 09:00-13:00,14:00-18:00, Sa 10:00-13:00");
0185 
0186         T("We; PH off");
0187         T("PH");
0188         T("PH Mo-Fr");
0189         T("PH -1 day");
0190         T("SH");
0191         T("SH,PH");
0192         T("PH,SH");
0193         T("We[1-3]");
0194         T("We[3-5]");
0195         T("Sa");
0196         T("Sa[1]");
0197         T("Sa[1-3]");
0198         T("Su[-1,1]");
0199         T("Tu-Th");
0200         T("Fr-Mo");
0201         T("Mo-Su; We \"only after registration\"");
0202         T("Oct: We[1]");
0203 
0204         // from https://github.com/dfaure/DataNovaImportScripts/blob/master/saved_opening_hours
0205         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",
0206            "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",
0207            "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");
0208 
0209         // real-world tests from Osmose that we were handling wrongly
0210         T("Tu-Fr 11:30-14:30 open, 14:30-18:00 open \"pickup only\", 18:00-22:00 open");
0211         T("SH Tu,Th 10:00-19:00");
0212         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");
0213         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");
0214         T("Mo-Sa 09:00-20:00; Su[-2,-1] 12:30-18:00");
0215         T("Su off, Mo-Fr 08:30-13:00; Mo-Th 08:30-13:30,16:00-20:00");
0216         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,
0217            "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");
0218         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
0219 
0220         // weekday autocorrect
0221         T2("Tu, Th 13:30-19:00; SH Tu, Th 10:00-19:00; Fr 13:30-18:00; SH Fr 10:00-18:00; We, Sa 10:00-18:00; SH We, Sa 10:00-18:00", "Tu,Th 13:30-19:00; SH Tu,Th 10:00-19:00; Fr 13:30-18:00; SH Fr 10:00-18:00; We,Sa 10:00-18:00; SH We,Sa 10:00-18:00");
0222         T2("Mar Su[-1] - Oct Su[-1] - 1 days: Th 09:00-18:00, Tu 15:00-18:00; Sa 09:00-12:00; Oct Su[-1] - Mar Su[-1] - 1 days: Tu 09:00-17:00, Th 09:00-17:00, Sa 09:00-12:00; Mo, We, Fr, Su, PH Off",
0223            "Mar Su[-1]-Oct Su[-1] -1 day: Th 09:00-18:00, Tu 15:00-18:00; Sa 09:00-12:00; Oct Su[-1]-Mar Su[-1] -1 day: Tu 09:00-17:00, Th 09:00-17:00, Sa 09:00-12:00; Mo,We,Fr,Su,PH off");
0224         T2("Sep 16-Jun 15: Tu-Fr, Sa, Su [1,3] 09:00-14:00; Jun 16-Sep 15: Sa, Su [1,3] 13:00-19:00; Mo off, Su [2,4] off",
0225            "Sep 16-Jun 15: Tu-Fr,Sa,Su[1,3] 09:00-14:00; Jun 16-Sep 15: Sa,Su[1,3] 13:00-19:00; Mo off, Su[2,4] off");
0226         T2("2020-2021 Mo, Tu-Fr, Sa [-1] 09:00-14:00", "2020-2021 Mo,Tu-Fr,Sa[-1] 09:00-14:00");
0227         T2("week 1-3 Mo[2], Tu-Fr, Sa [-1] 09:00-14:00", "week 01-03 Mo[2],Tu-Fr,Sa[-1] 09:00-14:00");
0228         T2("Mo Fr 09:30-12:30 13:30-18:30", "Mo,Fr 09:30-12:30,13:30-18:30");
0229         T2("Mo Fr 09:30-12:30, 13:30-18:30 off", "Mo,Fr 09:30-12:30,13:30-18:30 off");
0230         T2("Mo, We, Fr 06:30-21:30; Tu, Th 09:00-21:30; Sa 09:00-17:00; Su 09:00-14:00", "Mo,We,Fr 06:30-21:30; Tu,Th 09:00-21:30; Sa 09:00-17:00; Su 09:00-14:00");
0231         T2("Lunes a sábado, 9:30 AM-5:30 PM", "Mo-Sa 09:30-17:30"); // bug 445784
0232 
0233         // technically wrong but often found content in OSM for which we have error recovery
0234         T2("So", "Su");
0235         T2("Ph", "PH");
0236         T2("9:00-12:00", "09:00-12:00");
0237         T2("Mo-Fr 09:00-18:30;Sa 09:00-17:00", "Mo-Fr 09:00-18:30; Sa 09:00-17:00");
0238         T2("08:00-12:00;", "08:00-12:00");
0239         T2("14:00-20:00,", "14:00-20:00");
0240         T2("Mo 14:00-21:00; Tu-Th 10:00-21:00; Fr 10:00-18:00;Su, PH off|| \"Samstag zweimal im Monat, Details siehe Webseite\"", "Mo 14:00-21:00; Tu-Th 10:00-21:00; Fr 10:00-18:00; Su,PH off || \"Samstag zweimal im Monat, Details siehe Webseite\"");
0241         T2("Mo-Fr 06:30-12:00, 13:00-18:00", "Mo-Fr 06:30-12:00,13:00-18:00"); // see autocorrect()
0242         T2("we-mo 11:30-14:00, 17:30-22:00; tu off", "We-Mo 11:30-14:00,17:30-22:00; Tu off");
0243         T2("01:00-23:00; ", "01:00-23:00");
0244         T2("02:00-22:00,\n", "02:00-22:00");
0245         T2("Friday 08:00-12:00", "Fr 08:00-12:00");
0246         T2("Sat", "Sa");
0247         T2("december", "Dec");
0248         T2("Dec 24,25,26, Jan 1,6 off", "Dec 24,25,26,Jan 01,06 off");
0249         T2("Dec 24,25,26 open, Jan 1,6 off", "Dec 24,25,26 open, Jan 01,06 off");
0250         T2("Dec 6,4", "Dec 06,04");
0251         T2("Dec 3,2,1", "Dec 03,02,01");
0252         T2("07:30-20:00; Jan 03, 13, 23, Feb 03, 13, 23, Mar 03, 13, 23, Apr 03, 13, 23, Jun 03, 13, 23, Jul 03, 13, 23, Aug 03, 13, 23, Sep 03, 13, 23, Oct 03, 13, 23, Nov 03, 13, 23, Dec 03, 13, 23 off",
0253            "07:30-20:00; Jan 03,13,23,Feb 03,13,23,Mar 03,13,23,Apr 03,13,23,Jun 03,13,23,Jul 03,13,23,Aug 03,13,23,Sep 03,13,23,Oct 03,13,23,Nov 03,13,23,Dec 03,13,23 off");
0254         T2("Apr, May, Oct, Nov, Dec: Mo-Su, 10:00-19:00; Jun-Sep: Mo-Su:10:00-20:00", "Apr,May,Oct,Nov,Dec: Mo-Su, 10:00-19:00; Jun-Sep: Mo-Su 10:00-20:00");
0255 
0256         // Tolerance for incorrect casing
0257         T2("mo-fr 10:00-20:00", "Mo-Fr 10:00-20:00");
0258         T2("jan-feb 10:00-20:00", "Jan-Feb 10:00-20:00");
0259         T2("jan-feb,aug 10:00-20:00", "Jan-Feb,Aug 10:00-20:00");
0260         T2("SUNRISE-SUNSET", "sunrise-sunset");
0261         T2("(SUNrISE-01:00)-(SUnsET+01:00)", "(sunrise-01:00)-(sunset+01:00)");
0262         T2("su,sh off", "Su,SH off");
0263         T2("mo-fr CLOSED", "Mo-Fr closed");
0264 
0265         // Time correction
0266         T2("9h00-12h00", "09:00-12:00");
0267         T2("9h-12h", "09:00-12:00");
0268         T2("5H", "05:00");
0269         T2("06:00am", "06:00");
0270         T2("06:30pm", "18:30");
0271         T2("07:00 am", "07:00");
0272         T2("07:00 pm", "19:00");
0273         T2("5:00AM", "05:00");
0274         T2("5:02 PM", "17:02");
0275         T2("10a", "10:00");
0276         T2("10p", "22:00");
0277         T2("12:00 am", "00:00");
0278         T2("12:00pm", "12:00");
0279         T2("1 a.m", "01:00");
0280         T2("3p.m", "15:00");
0281         T2("12:01a.m.", "00:01");
0282         T2("12:01p.m.", "12:01");
0283         T2("11:59a", "11:59");
0284         T2("11:59p", "23:59");
0285         T2("9h00-12h00,14:00-17:00", "09:00-12:00,14:00-17:00");
0286         T2("9:00 am - 12:00 am", "09:00-24:00");
0287         T2("9 am - 12 am", "09:00-24:00");
0288         T2("11:00 am - 11:00 pm", "11:00-23:00");
0289         T2("09 : 00 - 12 : 00 , 13 : 00 - 19 : 00", "09:00-12:00,13:00-19:00");
0290         T2("10.30am - 4.30pm", "10:30-16:30");
0291         T2("17時00分~23時30分", "17:00-23:30");
0292 
0293         // alternative range separators
0294         T2("Mo-Fri 10am to 7pm, Saturday 11am to 6pm, Sun 11am to 4pm", "Mo-Fr 10:00-19:00, Sa 11:00-18:00, Su 11:00-16:00");
0295         T2("Monday to Friday 8:00AM to 4:30PM", "Mo-Fr 08:00-16:30");
0296         T2("1pm-3pm and 7pm-11pm", "13:00-15:00,19:00-23:00");
0297         T2("8h00 à 12h00 et 13h30 à 18h00", "08:00-12:00,13:30-18:00");
0298         T2("Samedi et Dimanche 5h30 - 12h30 Lundi 13h45 - 15h15", "Sa,Su 05:30-12:30; Mo 13:45-15:15");
0299         T2("Mo-Th 11:00-20:00 Friday & Saturday 11:00-21:00 Sunday 12:00-19:00", "Mo-Th 11:00-20:00; Fr,Sa 11:00-21:00; Su 12:00-19:00");
0300         T2("11:30-14:00、16:30-22:00", "11:30-14:00,16:30-22:00");
0301         T2("Lunedì al Venerdì 08:00-13:00", "Mo-Fr 08:00-13:00");
0302         T2("Monday through Friday 09:00 - 17:00", "Mo-Fr 09:00-17:00");
0303 
0304         // (mis)use of colon as a small-range selector separator
0305         T2("Fr: 17:00-19:00", "Fr 17:00-19:00");
0306         T2("Fr: closed", "Fr closed");
0307         T2("Tu-Su:07:00-00:00", "Tu-Su 07:00-24:00");
0308         T2("Du lundi au vendredi : 9:00-18:00", "Mo-Fr 09:00-18:00");
0309 
0310         // Unicode symbols
0311         T3("Mo–Tu", "Mo-Tu", "Mo,Tu");
0312         T2("13:41", "13:41");
0313         T2("10:00〜19:00", "10:00-19:00");
0314         T2("10:00-17:00", "10:00-17:00");
0315         T2("11:00−23:00", "11:00-23:00");
0316         T2("11:00ー15:00", "11:00-15:00");
0317         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
0318         T2("LUNDI 08:30 – 17:00", "Mo 08:30-17:00");
0319         T3("月,木,金,土,日 11:00-19:00", "Mo,Th,Fr,Sa,Su 11:00-19:00", "Th-Mo 11:00-19:00");
0320         T2("月-土 09:00-18:00", "Mo-Sa 09:00-18:00");
0321         T2("水曜日~土曜日10:00~19:00", "We-Sa 10:00-19:00");
0322         T2("月~土  17:00~23:00", "Mo-Sa 17:00-23:00");
0323         T2("Mo-Fr 08:00-17:00 “Visa Applications\"", "Mo-Fr 08:00-17:00 \"Visa Applications\"");
0324         T2("„nach Vereinbarung“", "\"nach Vereinbarung\"");
0325         T("\"„Termine nach Vereinbarung“\"");
0326 
0327         // non-English
0328         T2("Lundi au Vendredi 8h - 17h en continu", "Mo-Fr 08:00-17:00");
0329         T2("Lun-Ven 08:00-13:00", "Mo-Fr 08:00-13:00");
0330         T2("Lu-Sa 08:00-13:00", "Mo-Sa 08:00-13:00");
0331         T2("Domingo de 9: 00 am. a 1:00 pm", "Su 09:00-13:00");
0332         T2("Gio, Sab e Dom: 9.00-12.30", "Th,Sa,Su 09:00-12:30");
0333         T2("Segunda a Sexta 08:00h a 16:00h", "Mo-Fr 08:00-16:00");
0334         T2("lunedi,mercoledi,venerdi,sabato 09:00-20:00", "Mo,We,Fr,Sa 09:00-20:00");
0335         T2("Mi 08:00-18:00", "We 08:00-18:00");
0336         T2("Du Mardi au Vendredi 11h00-13h30 Le samedi 10h-19h", "Tu-Fr 11:00-13:30; Sa 10:00-19:00");
0337         T2("Senin-Sabtu 09:00-16:00, Minggu 09:00-18:00", "Mo-Sa 09:00-16:00, Su 09:00-18:00");
0338         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");
0339         T2("De février à novembre", "Feb-Nov");
0340         T3("Me,Je,Ve 8h-12h30, 14h-19h; Sa 8h-12h30, 14h-18h", "We,Th,Fr 08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:00-18:00", "We-Fr 08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:00-18:00");
0341         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");
0342         T2("Montag & Dienstag Ruhetag", "Mo,Tu closed");
0343         T2("Понедельник - Суббота 09:00 - 21:00", "Mo-Sa 09:00-21:00");
0344         T2("Пон-Пт 08:00-20:00, Суб 08:00-19:00", "Mo-Fr 08:00-20:00, Sa 08:00-19:00");
0345         T2("Ноябрь-Март", "Nov-Mar");
0346         T2("с 08:30 до 23:00", "08:30-23:00");
0347         T2("рассвет - сумерки", "dawn-dusk");
0348         T2("от рассвета до сумерек", "dawn-dusk");
0349         T2("восход - закат", "sunrise-sunset");
0350         T2("с восхода пo закат", "sunrise-sunset");
0351         T2("от восхода дo заката", "sunrise-sunset");
0352         T2("Среда открыто; Пятница закрыто; Суббота неизвестно", "We open; Fr closed; Sa unknown");
0353         T2("Вт Выходной", "Tu closed");
0354         T2("Mo Cerrado; Tu Abierto, Fr libre", "Mo closed; Tu open, Fr closed");
0355 
0356         // recovery from wrong rule separators
0357         T2("Fr,Sa 10:00-02:00,Su 10:00-20:00", "Fr,Sa 10:00-02:00, Su 10:00-20:00");
0358         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");
0359         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");
0360         T2("Mo-Fr 09:00-17:00 Sa 09:00-14:00", "Mo-Fr 09:00-17:00; Sa 09:00-14:00");
0361         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");
0362         T2("Mo-Sa 12:00-15:00; 18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
0363         T2("Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
0364         T2("Mo 12:00-15:00; Mo 18:00-24:00", "Mo 12:00-15:00,18:00-24:00");
0365         T("Mo-Sa 12:00-15:00 off; 18:00-24:00");
0366         T("Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00 closed");
0367         T("Mo 12:00-15:00; Mo 18:00-24:00 \"comment\"");
0368         T("Sa 08:00-12:00; 11:30-13:00 off");
0369 
0370         // recovery from wrong time selector separators
0371         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",
0372            "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",
0373            "Su closed; Mo-Fr 08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:30-18:00");
0374 
0375         // recovery from slashes abused as rule or timespan separators
0376         T2("09:00-12:00/13:00-19:00", "09:00-12:00,13:00-19:00");
0377         T2("10:00 - 13:30 / 17:00 - 20:30", "10:00-13:30,17:00-20:30");
0378         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");
0379 
0380         // Simplification of the output
0381         T3("Mo 08:00-13:00; Tu 08:00-13:00", nullptr, "Mo,Tu 08:00-13:00");
0382         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");
0383         T("easter +1 day 08:00-13:00; Tu,Sa,Su 08:00-13:00"); // does not simplify
0384         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");
0385         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");
0386         T3("Mo-We,Fr,Su 08:00-13:00", "Mo-We,Fr,Su 08:00-13:00", "Su-We,Fr 08:00-13:00");
0387         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
0388         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
0389         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
0390         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
0391         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
0392         T3("Feb 1-Feb 29 Mo-Su 10:30-20:30; Aug 1-Aug 31 Mo-Fr 10:30-12:00; PH closed",
0393            "Feb 01-29 Mo-Su 10:30-20:30; Aug 01-31 Mo-Fr 10:30-12:00; PH closed",
0394            "Feb Mo-Su 10:30-20:30; Aug Mo-Fr 10:30-12:00; PH closed");
0395 
0396         // complex or creative 24/7 use
0397         T("06:00-01:00 open \"Dining in\" || 24/7 \"Drive-through\"");
0398         T2("Friday and Saturday 24/7 Sunday-Thursday 4:00 am to 12:00 am", "Fr,Sa 00:00-24:00; Su-Th 04:00-24:00"); // bug 445962
0399         // 24/7 as standalone small range selector not supported yet
0400         //T2("Apr-Oct: 24/7; Nov-Mar: Mo-Su 06:00-22:00", "Apr-Oct: 00:00-24:00; Nov-Mar: Mo-Su 06:00-22:00");
0401 
0402         // 4 digit times
0403         T2("0600-1800", "06:00-18:00");
0404         T2("0700-2000", "07:00-20:00");
0405         T2("Tu-Th 8:30-17:30, Fr 8:30-1700", "Tu-Th 08:30-17:30, Fr 08:30-17:00");
0406 #undef T
0407 #undef T2
0408 #undef T3
0409         // clang-format on
0410     }
0411 
0412     void testSuccess()
0413     {
0414         QFETCH(QByteArray, input);
0415         QFETCH(QByteArray, expectedOutput);
0416         QFETCH(QByteArray, expectedSimplifiedOutput);
0417         OpeningHours oh(input);
0418         QVERIFY(oh.error() != OpeningHours::SyntaxError);
0419         QCOMPARE(oh.normalizedExpression(), expectedOutput);
0420         QCOMPARE(oh.simplifiedExpression(), expectedSimplifiedOutput);
0421         // verify that simplifiedExpression() doesn't alter `oh`
0422         QCOMPARE(oh.normalizedExpression(), expectedOutput);
0423 
0424         // verify the expressions we generate are parsed correctly as well
0425         OpeningHours oh2(oh.normalizedExpression());
0426         QVERIFY(oh2.error() != OpeningHours::SyntaxError);
0427         QCOMPARE(oh2.normalizedExpression(), oh.normalizedExpression());
0428 
0429         OpeningHours oh3(oh.simplifiedExpression());
0430         QVERIFY(oh3.error() != OpeningHours::SyntaxError);
0431         QCOMPARE(oh3.normalizedExpression(), oh.simplifiedExpression());
0432     }
0433 
0434     void testFail_data()
0435     {
0436         QTest::addColumn<QByteArray>("input");
0437         QTest::addColumn<OpeningHours::Error>("error");
0438 
0439 #define T(x) QTest::newRow(x) << QByteArray(x) << OpeningHours::SyntaxError
0440         T("23/7");
0441         T("24/7 geöffnet");
0442         T("2020-2000");
0443         T("Jan-Apr 1");
0444         T("Feb-2020 Apr 1");
0445         T("Apr 1-Nov");
0446         T("Su[0]");
0447         T("Mo[6]");
0448         T("Mo[-0]");
0449         T("Tu[-6]");
0450         T("Mo[0-5]");
0451         T("We[4-2]");
0452         T("49:00");
0453         T("12:61");
0454         T("60p");
0455 
0456         T("Dec 24-Jan 1,6");
0457         T("Mo, 1:100");
0458 
0459         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Common_mistakes
0460         T("7/8-23");
0461         T("07;00-2;00pm");
0462         T("08.00-16.00, public room till 03.00 a.m");
0463         T("09:00-21:00 TEL/072(360)3200");
0464         T("Dining in: 6am to 11pm; Drive thru: 24/7");
0465         T("MWThF: 1200-1800; SaSu: 1200-1700");
0466         T("BAR: Su-Mo 18:00-02:00; Tu-Th 18:00-03:00; Fr-Sa 18:00-04:00; CLUB: Tu-Th 20:00-03:00; Fr-Sa 20:00-04:00");
0467 
0468         // from https://openingh.openstreetmap.de/evaluation_tool/
0469         T("00:00-24:00 week 6 Mo-Su Feb; PH off");
0470         T("monday, Tu, wE, TH 12:00 - 20:00 ; 14:00-16:00 Off ; closed public Holiday");
0471 
0472         // from Osmose, should fail rather than silently drop the last part
0473         T("Mo-Th, Su 17:00-01:00, Fr-Sa 1700:0300");
0474 
0475         // creative uses found in the wild
0476         T("May 01-Jun 15,Sept 01-30 Mo-Fr 10:00-18:00 Sa-Su 09:00-19:00");
0477         T("Jul 22-Aug 18 2019: Tu-Su 10:00-13:00");
0478 #undef T
0479     }
0480 
0481     void testFail()
0482     {
0483         QFETCH(QByteArray, input);
0484         QFETCH(OpeningHours::Error, error);
0485         OpeningHours oh(input);
0486         QCOMPARE(oh.error(), error);
0487     }
0488 
0489     void testValidation_data()
0490     {
0491         QTest::addColumn<QByteArray>("expression");
0492         QTest::addColumn<OpeningHours::Error>("error");
0493 
0494         QTest::newRow("location") << QByteArray("sunrise-sunset") << OpeningHours::MissingLocation;
0495 #ifdef KOPENINGHOURS_VALIDATOR_ONLY
0496         QTest::newRow("PH") << QByteArray("PH off") << OpeningHours::NoError;
0497 #else
0498         QTest::newRow("PH") << QByteArray("PH off") << OpeningHours::MissingRegion;
0499 #endif
0500         QTest::newRow("SH") << QByteArray("SH off") << OpeningHours::UnsupportedFeature;
0501         QTest::newRow("time interval") << QByteArray("10:00-16:00/90") << OpeningHours::IncompatibleMode;
0502         QTest::newRow("time interval 2") << QByteArray("10:00-16:00/1:30") << OpeningHours::IncompatibleMode;
0503         QTest::newRow("week wrap") << QByteArray("week 45-13") << OpeningHours::UnsupportedFeature;
0504         QTest::newRow("single timepoint") << QByteArray("10:00") << OpeningHours::IncompatibleMode;
0505         QTest::newRow("month timepoint") << QByteArray("Dec 08:00") << OpeningHours::IncompatibleMode;
0506         QTest::newRow("wide range selector comment") << QByteArray("\"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;") << OpeningHours::UnsupportedFeature;
0507 
0508         QTest::newRow("empty") << QByteArray("") << OpeningHours::Null;
0509         QTest::newRow("empty comment") << QByteArray("\"\"") << OpeningHours::Null;
0510     }
0511 
0512     void testValidation()
0513     {
0514         QFETCH(QByteArray, expression);
0515         QFETCH(OpeningHours::Error, error);
0516 
0517         OpeningHours oh(expression);
0518         QCOMPARE(oh.error(), error);
0519         (void)oh.normalizedExpression(); // don't crash
0520         (void)oh.simplifiedExpression(); // don't crash
0521     }
0522 };
0523 
0524 QTEST_GUILESS_MAIN(ParserTest)
0525 
0526 #include "parsertest.moc"