File indexing completed on 2024-04-14 08:15:16

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         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Simple_examples
0116         T("Mo-Fr 08:00-17:30");
0117         T("Mo-Fr 08:00-12:00,13:00-17:30");
0118         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00");
0119         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00; PH off");
0120         T("Mo-Fr 08:00-12:00,13:00-17:30; Sa 08:00-12:00; PH 09:00-12:00");
0121 
0122         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Examples
0123         T3("Sa-Su 00:00-24:00", nullptr, "Sa,Su 00:00-24:00");
0124         T("Mo-Fr 08:30-20:00");
0125         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");
0126         T("Mo-Su 08:00-18:00; Apr 10-15 off; Jun 08:00-14:00; Aug off; Dec 25 off");
0127         T("Mo-Sa 10:00-20:00; Tu off");
0128         T("Mo-Sa 10:00-20:00; Tu 10:00-14:00");
0129         T("sunrise-sunset");
0130         T("Su 10:00+");
0131         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");
0132         T("Mo-Sa 08:00-13:00,14:00-17:00 || \"by appointment\"");
0133         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");
0134         T("Mo-Su,PH 15:00-03:00; easter -2 days off");
0135 
0136         // from https://openingh.openstreetmap.de/evaluation_tool/
0137         T("Mo-Fr 10:00-20:00; PH off");
0138         T("Mo,Tu,Th,Fr 12:00-18:00; Sa,PH 12:00-17:00; Th[3],Th[-1] off");
0139         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");
0140         T3("Fr-Sa 18:00-06:00; PH off", nullptr, "Fr,Sa 18:00-06:00; PH off");
0141         T("Mo 10:00-12:00,12:30-15:00");
0142         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");
0143         T("\"only after registration\"; PH off");
0144         T("22:00-23:00; PH off");
0145         T("08:00-11:00; PH off");
0146         T("open; Mo 15:00-16:00 off; PH off");
0147         T("Mo-Su 22:00-23:00; We,PH off");
0148         T("We-Fr 10:00-24:00 open \"it is open\" || \"please call\"; PH off");
0149         T("Mo-Fr 08:00-11:00 || Tu-Th,PH open \"Emergency only\"");
0150         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
0151         T("Mo 12:00-14:00 open \"female only\", Mo 14:00-16:00 open \"male only\"; PH off");
0152         T("Apr: 22:00-23:00; PH off");
0153         T("Jul-Jan: 22:00-23:00; PH off");
0154         T("Jan-Jul: 22:00-23:00; PH off");
0155         T2("Jul 23-Jan 3: \"needs reservation by phone\"; PH off", "Jul 23-Jan 03: \"needs reservation by phone\"; PH off");
0156         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");
0157         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");
0158         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");
0159         T("Mar Su[-1]-Dec Su[1] -2 days: 22:00-23:00; PH off");
0160         T("Sa[1],Sa[1] +1 day 10:00-12:00 open \"first weekend in the month\"; PH off");
0161         T("Sa[-1],Sa[-1] +1 day 10:00-12:00 open \"last weekend in the month\"; PH off");
0162         T3("Sa-Su 00:00-24:00; PH off", nullptr, "Sa,Su 00:00-24:00; PH off");
0163         T("Mo-Fr 00:00-24:00; PH off");
0164         T("sunrise-sunset open \"Beware of sunburn!\"; PH off");
0165         T("sunset-sunrise open \"Beware of vampires!\"; PH off");
0166         T("(sunrise-00:30)-(sunrise+00:30)");
0167         T("(sunset+01:00)-24:00 || closed \"No drink before sunset!\"; PH off");
0168         T("22:00+; PH off");
0169         T("Tu,PH 23:59-22:59");
0170         T("We-Mo,PH 23:59-22:59");
0171         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");
0172         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");
0173         T("2012 easter -2 days-2012 easter +2 days: open \"Around easter\"; PH off");
0174         T("24/7 closed \"always closed\"");
0175         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");
0176         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");
0177         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");
0178         T("Mo-Fr 08:00-12:00, We 14:00-18:00; Su,PH off"); // open We morning too
0179         T("Mo-Fr 08:00-12:00; We 14:00-18:00; Su,PH off"); // closed We morning
0180         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");
0181 
0182         T("We; PH off");
0183         T("PH");
0184         T("PH Mo-Fr");
0185         T("PH -1 day");
0186         T("SH");
0187         T("SH,PH");
0188         T("PH,SH");
0189         T("We[1-3]");
0190         T("We[3-5]");
0191         T("Sa");
0192         T("Sa[1]");
0193         T("Sa[1-3]");
0194         T("Su[-1,1]");
0195         T("Tu-Th");
0196         T("Fr-Mo");
0197         T("Mo-Su; We \"only after registration\"");
0198         T("Oct: We[1]");
0199 
0200         // from https://github.com/dfaure/DataNovaImportScripts/blob/master/saved_opening_hours
0201         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",
0202            "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",
0203            "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");
0204 
0205         // real-world tests from Osmose that we were handling wrongly
0206         T("Tu-Fr 11:30-14:30 open, 14:30-18:00 open \"pickup only\", 18:00-22:00 open");
0207         T("SH Tu,Th 10:00-19:00");
0208         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");
0209         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");
0210         T("Mo-Sa 09:00-20:00; Su[-2,-1] 12:30-18:00");
0211         T("Su off, Mo-Fr 08:30-13:00; Mo-Th 08:30-13:30,16:00-20:00");
0212         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,
0213            "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");
0214         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
0215 
0216         // weekday autocorrect
0217         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");
0218         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",
0219            "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");
0220         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",
0221            "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");
0222         T2("2020-2021 Mo, Tu-Fr, Sa [-1] 09:00-14:00", "2020-2021 Mo,Tu-Fr,Sa[-1] 09:00-14:00");
0223         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");
0224         T2("Mo Fr 09:30-12:30 13:30-18:30", "Mo,Fr 09:30-12:30,13:30-18:30");
0225         T2("Mo Fr 09:30-12:30, 13:30-18:30 off", "Mo,Fr 09:30-12:30,13:30-18:30 off");
0226         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");
0227         T2("Lunes a sábado, 9:30 AM-5:30 PM", "Mo-Sa 09:30-17:30"); // bug 445784
0228 
0229         // technically wrong but often found content in OSM for which we have error recovery
0230         T2("So", "Su");
0231         T2("Ph", "PH");
0232         T2("9:00-12:00", "09:00-12:00");
0233         T2("Mo-Fr 09:00-18:30;Sa 09:00-17:00", "Mo-Fr 09:00-18:30; Sa 09:00-17:00");
0234         T2("08:00-12:00;", "08:00-12:00");
0235         T2("14:00-20:00,", "14:00-20:00");
0236         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\"");
0237         T2("Mo-Fr 06:30-12:00, 13:00-18:00", "Mo-Fr 06:30-12:00,13:00-18:00"); // see autocorrect()
0238         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");
0239         T2("01:00-23:00; ", "01:00-23:00");
0240         T2("02:00-22:00,\n", "02:00-22:00");
0241         T2("Friday 08:00-12:00", "Fr 08:00-12:00");
0242         T2("Sat", "Sa");
0243         T2("december", "Dec");
0244         T2("Dec 24,25,26, Jan 1,6 off", "Dec 24,25,26,Jan 01,06 off");
0245         T2("Dec 24,25,26 open, Jan 1,6 off", "Dec 24,25,26 open, Jan 01,06 off");
0246         T2("Dec 6,4", "Dec 06,04");
0247         T2("Dec 3,2,1", "Dec 03,02,01");
0248         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",
0249            "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");
0250         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");
0251 
0252         // Tolerance for incorrect casing
0253         T2("mo-fr 10:00-20:00", "Mo-Fr 10:00-20:00");
0254         T2("jan-feb 10:00-20:00", "Jan-Feb 10:00-20:00");
0255         T2("jan-feb,aug 10:00-20:00", "Jan-Feb,Aug 10:00-20:00");
0256         T2("SUNRISE-SUNSET", "sunrise-sunset");
0257         T2("(SUNrISE-01:00)-(SUnsET+01:00)", "(sunrise-01:00)-(sunset+01:00)");
0258         T2("su,sh off", "Su,SH off");
0259         T2("mo-fr CLOSED", "Mo-Fr closed");
0260 
0261         // Time correction
0262         T2("9h00-12h00", "09:00-12:00");
0263         T2("9h-12h", "09:00-12:00");
0264         T2("5H", "05:00");
0265         T2("06:00am", "06:00");
0266         T2("06:30pm", "18:30");
0267         T2("07:00 am", "07:00");
0268         T2("07:00 pm", "19:00");
0269         T2("5:00AM", "05:00");
0270         T2("5:02 PM", "17:02");
0271         T2("10a", "10:00");
0272         T2("10p", "22:00");
0273         T2("12:00 am", "00:00");
0274         T2("12:00pm", "12:00");
0275         T2("1 a.m", "01:00");
0276         T2("3p.m", "15:00");
0277         T2("12:01a.m.", "00:01");
0278         T2("12:01p.m.", "12:01");
0279         T2("11:59a", "11:59");
0280         T2("11:59p", "23:59");
0281         T2("9h00-12h00,14:00-17:00", "09:00-12:00,14:00-17:00");
0282         T2("9:00 am - 12:00 am", "09:00-24:00");
0283         T2("9 am - 12 am", "09:00-24:00");
0284         T2("11:00 am - 11:00 pm", "11:00-23:00");
0285         T2("09 : 00 - 12 : 00 , 13 : 00 - 19 : 00", "09:00-12:00,13:00-19:00");
0286         T2("10.30am - 4.30pm", "10:30-16:30");
0287         T2("17時00分~23時30分", "17:00-23:30");
0288 
0289         // alternative range separators
0290         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");
0291         T2("Monday to Friday 8:00AM to 4:30PM", "Mo-Fr 08:00-16:30");
0292         T2("1pm-3pm and 7pm-11pm", "13:00-15:00,19:00-23:00");
0293         T2("8h00 à 12h00 et 13h30 à 18h00", "08:00-12:00,13:30-18:00");
0294         T2("Samedi et Dimanche 5h30 - 12h30 Lundi 13h45 - 15h15", "Sa,Su 05:30-12:30; Mo 13:45-15:15");
0295         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");
0296         T2("11:30-14:00、16:30-22:00", "11:30-14:00,16:30-22:00");
0297         T2("Lunedì al Venerdì 08:00-13:00", "Mo-Fr 08:00-13:00");
0298         T2("Monday through Friday 09:00 - 17:00", "Mo-Fr 09:00-17:00");
0299 
0300         // (mis)use of colon as a small-range selector separator
0301         T2("Fr: 17:00-19:00", "Fr 17:00-19:00");
0302         T2("Fr: closed", "Fr closed");
0303         T2("Tu-Su:07:00-00:00", "Tu-Su 07:00-24:00");
0304         T2("Du lundi au vendredi : 9:00-18:00", "Mo-Fr 09:00-18:00");
0305 
0306         // Unicode symbols
0307         T3("Mo–Tu", "Mo-Tu", "Mo,Tu");
0308         T2("13:41", "13:41");
0309         T2("10:00〜19:00", "10:00-19:00");
0310         T2("10:00-17:00", "10:00-17:00");
0311         T2("11:00−23:00", "11:00-23:00");
0312         T2("11:00ー15:00", "11:00-15:00");
0313         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
0314         T2("LUNDI 08:30 – 17:00", "Mo 08:30-17:00");
0315         T3("月,木,金,土,日 11:00-19:00", "Mo,Th,Fr,Sa,Su 11:00-19:00", "Th-Mo 11:00-19:00");
0316         T2("月-土 09:00-18:00", "Mo-Sa 09:00-18:00");
0317         T2("水曜日~土曜日10:00~19:00", "We-Sa 10:00-19:00");
0318         T2("月~土  17:00~23:00", "Mo-Sa 17:00-23:00");
0319         T2("Mo-Fr 08:00-17:00 “Visa Applications\"", "Mo-Fr 08:00-17:00 \"Visa Applications\"");
0320         T2("„nach Vereinbarung“", "\"nach Vereinbarung\"");
0321         T("\"„Termine nach Vereinbarung“\"");
0322 
0323         // non-English
0324         T2("Lundi au Vendredi 8h - 17h en continu", "Mo-Fr 08:00-17:00");
0325         T2("Lun-Ven 08:00-13:00", "Mo-Fr 08:00-13:00");
0326         T2("Lu-Sa 08:00-13:00", "Mo-Sa 08:00-13:00");
0327         T2("Domingo de 9: 00 am. a 1:00 pm", "Su 09:00-13:00");
0328         T2("Gio, Sab e Dom: 9.00-12.30", "Th,Sa,Su 09:00-12:30");
0329         T2("Segunda a Sexta 08:00h a 16:00h", "Mo-Fr 08:00-16:00");
0330         T2("lunedi,mercoledi,venerdi,sabato 09:00-20:00", "Mo,We,Fr,Sa 09:00-20:00");
0331         T2("Mi 08:00-18:00", "We 08:00-18:00");
0332         T2("Du Mardi au Vendredi 11h00-13h30 Le samedi 10h-19h", "Tu-Fr 11:00-13:30; Sa 10:00-19:00");
0333         T2("Senin-Sabtu 09:00-16:00, Minggu 09:00-18:00", "Mo-Sa 09:00-16:00, Su 09:00-18:00");
0334         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");
0335         T2("De février à novembre", "Feb-Nov");
0336         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");
0337         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");
0338         T2("Montag & Dienstag Ruhetag", "Mo,Tu closed");
0339         T2("Понедельник - Суббота 09:00 - 21:00", "Mo-Sa 09:00-21:00");
0340         T2("Пон-Пт 08:00-20:00, Суб 08:00-19:00", "Mo-Fr 08:00-20:00, Sa 08:00-19:00");
0341         T2("Ноябрь-Март", "Nov-Mar");
0342         T2("с 08:30 до 23:00", "08:30-23:00");
0343         T2("рассвет - сумерки", "dawn-dusk");
0344         T2("от рассвета до сумерек", "dawn-dusk");
0345         T2("восход - закат", "sunrise-sunset");
0346         T2("с восхода пo закат", "sunrise-sunset");
0347         T2("от восхода дo заката", "sunrise-sunset");
0348         T2("Среда открыто; Пятница закрыто; Суббота неизвестно", "We open; Fr closed; Sa unknown");
0349         T2("Вт Выходной", "Tu closed");
0350         T2("Mo Cerrado; Tu Abierto, Fr libre", "Mo closed; Tu open, Fr closed");
0351 
0352         // recovery from wrong rule separators
0353         T2("Fr,Sa 10:00-02:00,Su 10:00-20:00", "Fr,Sa 10:00-02:00, Su 10:00-20:00");
0354         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");
0355         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");
0356         T2("Mo-Fr 09:00-17:00 Sa 09:00-14:00", "Mo-Fr 09:00-17:00; Sa 09:00-14:00");
0357         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");
0358         T2("Mo-Sa 12:00-15:00; 18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
0359         T2("Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00", "Mo-Sa 12:00-15:00,18:00-24:00");
0360         T2("Mo 12:00-15:00; Mo 18:00-24:00", "Mo 12:00-15:00,18:00-24:00");
0361         T("Mo-Sa 12:00-15:00 off; 18:00-24:00");
0362         T("Mo-Sa 12:00-15:00; Mo-Sa 18:00-24:00 closed");
0363         T("Mo 12:00-15:00; Mo 18:00-24:00 \"comment\"");
0364         T("Sa 08:00-12:00; 11:30-13:00 off");
0365 
0366         // recovery from wrong time selector separators
0367         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",
0368            "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",
0369            "Su closed; Mo-Fr 08:00-12:30,14:00-19:00; Sa 08:00-12:30,14:30-18:00");
0370 
0371         // recovery from slashes abused as rule or timespan separators
0372         T2("09:00-12:00/13:00-19:00", "09:00-12:00,13:00-19:00");
0373         T2("10:00 - 13:30 / 17:00 - 20:30", "10:00-13:30,17:00-20:30");
0374         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");
0375 
0376         // Simplification of the output
0377         T3("Mo 08:00-13:00; Tu 08:00-13:00", nullptr, "Mo,Tu 08:00-13:00");
0378         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");
0379         T("easter +1 day 08:00-13:00; Tu,Sa,Su 08:00-13:00"); // does not simplify
0380         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");
0381         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");
0382         T3("Mo-We,Fr,Su 08:00-13:00", "Mo-We,Fr,Su 08:00-13:00", "Su-We,Fr 08:00-13:00");
0383         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
0384         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
0385         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
0386         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
0387         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
0388         T3("Feb 1-Feb 29 Mo-Su 10:30-20:30; Aug 1-Aug 31 Mo-Fr 10:30-12:00; PH closed",
0389            "Feb 01-29 Mo-Su 10:30-20:30; Aug 01-31 Mo-Fr 10:30-12:00; PH closed",
0390            "Feb Mo-Su 10:30-20:30; Aug Mo-Fr 10:30-12:00; PH closed");
0391 
0392         // complex or creative 24/7 use
0393         T("06:00-01:00 open \"Dining in\" || 24/7 \"Drive-through\"");
0394         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
0395         // 24/7 as standalone small range selector not supported yet
0396         //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");
0397 
0398         // 4 digit times
0399         T2("0600-1800", "06:00-18:00");
0400         T2("0700-2000", "07:00-20:00");
0401         T2("Tu-Th 8:30-17:30, Fr 8:30-1700", "Tu-Th 08:30-17:30, Fr 08:30-17:00");
0402 #undef T
0403 #undef T2
0404 #undef T3
0405         // clang-format on
0406     }
0407 
0408     void testSuccess()
0409     {
0410         QFETCH(QByteArray, input);
0411         QFETCH(QByteArray, expectedOutput);
0412         QFETCH(QByteArray, expectedSimplifiedOutput);
0413         OpeningHours oh(input);
0414         QVERIFY(oh.error() != OpeningHours::SyntaxError);
0415         QCOMPARE(oh.normalizedExpression(), expectedOutput);
0416         QCOMPARE(oh.simplifiedExpression(), expectedSimplifiedOutput);
0417         // verify that simplifiedExpression() doesn't alter `oh`
0418         QCOMPARE(oh.normalizedExpression(), expectedOutput);
0419 
0420         // verify the expressions we generate are parsed correctly as well
0421         OpeningHours oh2(oh.normalizedExpression());
0422         QVERIFY(oh2.error() != OpeningHours::SyntaxError);
0423         QCOMPARE(oh2.normalizedExpression(), oh.normalizedExpression());
0424 
0425         OpeningHours oh3(oh.simplifiedExpression());
0426         QVERIFY(oh3.error() != OpeningHours::SyntaxError);
0427         QCOMPARE(oh3.normalizedExpression(), oh.simplifiedExpression());
0428     }
0429 
0430     void testFail_data()
0431     {
0432         QTest::addColumn<QByteArray>("input");
0433         QTest::addColumn<OpeningHours::Error>("error");
0434 
0435 #define T(x) QTest::newRow(x) << QByteArray(x) << OpeningHours::SyntaxError
0436         T("23/7");
0437         T("24/7 geöffnet");
0438         T("2020-2000");
0439         T("Jan-Apr 1");
0440         T("Feb-2020 Apr 1");
0441         T("Apr 1-Nov");
0442         T("Su[0]");
0443         T("Mo[6]");
0444         T("Mo[-0]");
0445         T("Tu[-6]");
0446         T("Mo[0-5]");
0447         T("We[4-2]");
0448         T("49:00");
0449         T("12:61");
0450         T("60p");
0451 
0452         T("Dec 24-Jan 1,6");
0453         T("Mo, 1:100");
0454 
0455         // from https://wiki.openstreetmap.org/wiki/Key:opening_hours#Common_mistakes
0456         T("7/8-23");
0457         T("07;00-2;00pm");
0458         T("08.00-16.00, public room till 03.00 a.m");
0459         T("09:00-21:00 TEL/072(360)3200");
0460         T("Dining in: 6am to 11pm; Drive thru: 24/7");
0461         T("MWThF: 1200-1800; SaSu: 1200-1700");
0462         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");
0463 
0464         // from https://openingh.openstreetmap.de/evaluation_tool/
0465         T("00:00-24:00 week 6 Mo-Su Feb; PH off");
0466         T("monday, Tu, wE, TH 12:00 - 20:00 ; 14:00-16:00 Off ; closed public Holiday");
0467 
0468         // from Osmose, should fail rather than silently drop the last part
0469         T("Mo-Th, Su 17:00-01:00, Fr-Sa 1700:0300");
0470 
0471         // creative uses found in the wild
0472         T("May 01-Jun 15,Sept 01-30 Mo-Fr 10:00-18:00 Sa-Su 09:00-19:00");
0473         T("Jul 22-Aug 18 2019: Tu-Su 10:00-13:00");
0474 #undef T
0475     }
0476 
0477     void testFail()
0478     {
0479         QFETCH(QByteArray, input);
0480         QFETCH(OpeningHours::Error, error);
0481         OpeningHours oh(input);
0482         QCOMPARE(oh.error(), error);
0483     }
0484 
0485     void testValidation_data()
0486     {
0487         QTest::addColumn<QByteArray>("expression");
0488         QTest::addColumn<OpeningHours::Error>("error");
0489 
0490         QTest::newRow("location") << QByteArray("sunrise-sunset") << OpeningHours::MissingLocation;
0491 #ifdef KOPENINGHOURS_VALIDATOR_ONLY
0492         QTest::newRow("PH") << QByteArray("PH off") << OpeningHours::NoError;
0493 #else
0494         QTest::newRow("PH") << QByteArray("PH off") << OpeningHours::MissingRegion;
0495 #endif
0496         QTest::newRow("SH") << QByteArray("SH off") << OpeningHours::UnsupportedFeature;
0497         QTest::newRow("time interval") << QByteArray("10:00-16:00/90") << OpeningHours::IncompatibleMode;
0498         QTest::newRow("time interval 2") << QByteArray("10:00-16:00/1:30") << OpeningHours::IncompatibleMode;
0499         QTest::newRow("week wrap") << QByteArray("week 45-13") << OpeningHours::UnsupportedFeature;
0500         QTest::newRow("single timepoint") << QByteArray("10:00") << OpeningHours::IncompatibleMode;
0501         QTest::newRow("month timepoint") << QByteArray("Dec 08:00") << OpeningHours::IncompatibleMode;
0502         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;
0503 
0504         QTest::newRow("empty") << QByteArray("") << OpeningHours::Null;
0505         QTest::newRow("empty comment") << QByteArray("\"\"") << OpeningHours::Null;
0506     }
0507 
0508     void testValidation()
0509     {
0510         QFETCH(QByteArray, expression);
0511         QFETCH(OpeningHours::Error, error);
0512 
0513         OpeningHours oh(expression);
0514         QCOMPARE(oh.error(), error);
0515         (void)oh.normalizedExpression(); // don't crash
0516         (void)oh.simplifiedExpression(); // don't crash
0517     }
0518 };
0519 
0520 QTEST_GUILESS_MAIN(ParserTest)
0521 
0522 #include "parsertest.moc"