Ticket #9157: 9157b.patch

File 9157b.patch, 68.2 KB (added by simon04, 11 years ago)
  • new file data/time_domain.js

    diff --git a/data/time_domain.js b/data/time_domain.js
    new file mode 100644
    index 0000000..53031f0
    - +  
     1//--------------------------------------------------------------------------------
     2//      $Id: time_domain.js,v 1.58 2013/08/13 17:48:45 wolf Exp wolf $
     3//--------------------------------------------------------------------------------
     4//      Erklärung:      http://www.netzwolf.info/kartografie/osm/time_domain/
     5//      Lizenz:         http://creativecommons.org/licenses/by-sa/3.0/de/
     6//--------------------------------------------------------------------------------
     7//      Fragen, Wuensche, Bedenken, Anregungen?
     8//      <openlayers(%40)netzwolf.info>
     9//--------------------------------------------------------------------------------
     10//      Explanation:    http://www.netzwolf.info/en/cartography/osm/time_domain/
     11//      Licence:        http://creativecommons.org/licenses/by-sa/3.0/
     12//--------------------------------------------------------------------------------
     13
     14var TimeDomain = {
     15
     16        //--------------------------------------------------------------
     17        //      evaluate "opening_hours"-expression
     18        //              for a certain time and a certain region
     19        //--------------------------------------------------------------
     20
     21        evaluateInTime: function (expression, checktime, option) {
     22
     23                if (!option) option = {};
     24                var request = this.createTimestamp (checktime, null, 0);
     25                var mintime = new Date(request.y, request.m-1, request.d, request.H, request.M, request.S);
     26                this.scan (request, expression);
     27                request.option = option;
     28                this.evaluateRequest (request);
     29
     30                //--------------------------------------------------------------
     31                //      no match → try daywrap
     32                //--------------------------------------------------------------
     33
     34                if (request.value!=true) {
     35                        var r24 = this.createTimestamp (checktime, null, 24);
     36                        r24.parsedExpression = request.parsedExpression;
     37                        r24.option = option;
     38                        this.evaluateRequest (r24);
     39
     40                        var times = r24.times.concat (request.times);
     41
     42                        if (r24.usedrule) r24.usedrule += ' (with daywrap)';
     43                        if (r24.value==true || r24.value==null && r24.comment!=null && request.comment==null) request=r24;
     44                        request.times = times;
     45                }
     46
     47                for (var i=0; i<request.times.length; i++) {
     48                        if (request.times[i].t<mintime) request.times.splice(i--,1);
     49                }
     50
     51                return request;
     52        },
     53
     54        //--------------------------------------------------------------
     55        //      evaluate "collection_time"-expression
     56        //              for a certain time and a certain region
     57        //--------------------------------------------------------------
     58
     59        evaluateNextTime: function (expression, checktime, option) {
     60
     61                if (!option) option = {};
     62                var request = this.createTimestamp (checktime, null, 0);
     63                this.scan (request, expression);
     64                request.times = [];
     65
     66                var mintime = new Date(request.y, request.m-1, request.d, request.H, request.M, request.S);
     67                //--------------------------------------------------------------
     68                //      loop
     69                //--------------------------------------------------------------
     70
     71                var errors;
     72                var messages;
     73
     74                for (offset=-1; offset<7; offset++) {
     75
     76                        var check = this.createTimestamp ({
     77                                        y: request.y,
     78                                        m: request.m,
     79                                        d: request.d+offset,
     80                                        H: 12});
     81
     82                        check.parsedExpression = request.parsedExpression;
     83                        check.option = option;
     84
     85                        this.evaluateRequest (check);
     86
     87                        //--------------------------------------------------------------
     88                        //      check and append to result
     89                        //--------------------------------------------------------------
     90
     91                        for (var i in check.times) {
     92                                if (check.times[i].t>=mintime) request.times.push (check.times[i]);
     93                        }
     94
     95                        errors  = check.errors;
     96                        messages= check.messages;
     97
     98                        if (request.times.length>=1) break;
     99                }
     100
     101                request.errors  = request.errors  ? request.errors  .concat(errors  ||[]) : errors;
     102                request.messages= request.messages? request.messages.concat(messages||[]) : messages;
     103
     104                return request;
     105        },
     106
     107        //--------------------------------------------------------------
     108        //      error handler
     109        //--------------------------------------------------------------
     110
     111        getRuleElements: function (request, start) {
     112                var out = [];
     113                for (var i=start; request.parsedExpression[i].t; i++) {
     114                        out.push (request.parsedExpression[i].s);
     115                }
     116                return out;
     117        },
     118
     119        log: function (request, msg, list) {
     120                var out = this.getRuleElements (request, request.rule_start);
     121                var pos = request.parsedExpression.length - request.tokens.length - request.rule_start;
     122                out.splice (Math.max (0, Math.min (out.length, pos)), 0, ['««»»']);
     123                list.push (msg + ' at "'+out.join(' ')+'"');
     124        },
     125
     126        scanError: function (request, msg) {
     127                if (!request.errors) request.errors=[];
     128                request.errors.push (msg);
     129        },
     130
     131        error: function (request, msg) {
     132                if (!request.errors) request.errors=[];
     133                this.log (request, msg, request.errors);
     134        },
     135
     136        warning: function (request, msg) {
     137                if (!request.warnings) request.warnings=[];
     138                this.log (request, msg, request.warnings);
     139        },
     140
     141        //--------------------------------------------------------------
     142        //      evaluate
     143        //--------------------------------------------------------------
     144
     145        evaluateRequest: function (request) {
     146
     147                request.range_match= true;
     148                request.value   = false;
     149                request.comment = null;
     150                request.start   = null;
     151                request.tokens  = request.parsedExpression.slice();
     152
     153                while (request.tokens.length>=1) {
     154
     155                        if (request.tokens[0].s=='||') {
     156                                request.tokens.shift();
     157                                if (request.value) request.skip = true;
     158                                if (request.skip) continue;
     159                                request.range_match= true;
     160                                request.value   = false;
     161                                request.comment = null;
     162                                continue;
     163                        }
     164
     165                        if (request.tokens[0].t==null) {
     166                                request.tokens.shift();
     167                                continue;
     168                        }
     169
     170                        //--------------------------------------------------------------
     171                        //      evaluate a single rule
     172                        //--------------------------------------------------------------
     173
     174                        request.rule_start = request.parsedExpression.length-request.tokens.length;
     175
     176                        var error = this.evaluateBasicRule (request);
     177
     178                        //--------------------------------------------------------------
     179                        //      error handler
     180                        //--------------------------------------------------------------
     181
     182                        if (typeof(error)=='string' || request.tokens[0].t) {
     183
     184                                if (!error) error = 'Syntax error';
     185
     186                                this.error (request, error);
     187
     188                                while (request.tokens[0].t) request.tokens.shift();
     189
     190                                if (request.skip) continue;
     191
     192                                request.value   = null;
     193                                request.comment = 'invalid specification';
     194                                request.start   = request.rule_start
     195
     196                                continue;
     197                        }
     198
     199                        //--------------------------------------------------------------
     200                        //      skip
     201                        //--------------------------------------------------------------
     202
     203                        if (request.skip) continue;
     204
     205                        //--------------------------------------------------------------
     206                        //      applicable
     207                        //--------------------------------------------------------------
     208
     209                        if (!request.range_match || !request.day_match) continue;
     210
     211                        //--------------------------------------------------------------
     212                        //      quick fix for informal dayrange specifications
     213                        //--------------------------------------------------------------
     214
     215                        if (typeof(request.range_match)=='string') {
     216
     217                                if (request.rule_value) request.rule_value = null;
     218
     219                                request.rule_comment = request.rule_comment ?
     220                                        request.range_match + " " + request.rule_comment :
     221                                        request.range_match;
     222                        }
     223
     224                        //--------------------------------------------------------------
     225                        //      store value
     226                        //--------------------------------------------------------------
     227
     228                        request.value   = request.rule_value;
     229                        request.comment = request.rule_comment;
     230                        request.start   = request.rule_start;
     231                        request.minutes = request.rule_minutes;
     232                }
     233
     234                if (request.start != null)
     235                request.usedrule = this.getRuleElements(request, request.start).join(' ');
     236
     237                //------------------------------------------------------------
     238                //      process minutes
     239                //------------------------------------------------------------
     240
     241                var minutes = [];
     242
     243                for (var i in request.minutes) {
     244                        entry = request.minutes[i];
     245                        if (entry.v==false) {
     246                                for (var i=0; i<minutes.length; i++) {
     247                                        if (entry.f<=minutes[i].t && minutes[i].t<=entry.l)
     248                                                minutes.splice (i--, 1);
     249                                }
     250                                continue;
     251                        }
     252                        if (entry.s>=1) {
     253                                for (var t=entry.f; t<=entry.l; t+=entry.s) {
     254                                        minutes.push({t:t, c:entry.c});
     255                                }
     256                                continue;
     257                        }
     258                        minutes.push ({t:entry.f, c:entry.c});
     259                }
     260
     261                minutes.sort (function(a,b) { return a.t-b.t; });
     262
     263                //--------------------------------------------------------------
     264                //      transform to times
     265                //--------------------------------------------------------------
     266
     267                var base = new Date (request.y, request.m-1, request.d, 12, 0, 0).getTime()
     268                        - 12 *3600*1000;
     269
     270                request.times = [];
     271                for (var i in minutes) {
     272                        request.times.push ({t:new Date (base + minutes[i].t*60*1000), c:minutes[i].c});
     273                }
     274        },
     275
     276        //----------------------------------------------------------------------------
     277        //      evaluate single rule:
     278        //      string: error
     279        //      null:   no error
     280        //----------------------------------------------------------------------------
     281
     282        evaluateBasicRule: function (request) {
     283
     284                //------------------------------------------------------------
     285                //      init
     286                //------------------------------------------------------------
     287
     288                request.day_match       = false;
     289                request.time_match      = false;
     290                request.rule_value      = false;
     291                request.rule_comment    = null;
     292                request.rule_minutes    = [];
     293
     294                //------------------------------------------------------------
     295                //      rule_sequence - calendar_ranges
     296                //------------------------------------------------------------
     297
     298                var error = this.evaluateCalendarRanges (request);
     299
     300                if (typeof(error)=='string') return error;
     301
     302                //------------------------------------------------------------
     303                //      Empty rule
     304                //------------------------------------------------------------
     305
     306                if (!request.tokens[0].t) return;
     307
     308                //------------------------------------------------------------
     309                //      Basic Rule
     310                //------------------------------------------------------------
     311
     312                for (;;) {
     313
     314                        //------------------------------------------------------------
     315                        //      Times for Days
     316                        //------------------------------------------------------------
     317
     318                        error = this.evaluateTimesForDays (request);
     319
     320                        if (typeof(error)=='string') return error;
     321
     322                        //-------------------------------------------------
     323                        //      { "," Times for Days }
     324                        //-------------------------------------------------
     325
     326                        if (this.C(request.tokens, ',')) continue;
     327
     328                        //-------------------------------------------------
     329                        //      error correction
     330                        //-------------------------------------------------
     331
     332                        switch (request.tokens[0].t) {
     333                        case 'week':    // daylist:     week
     334                        case 'd':       // daylist:     mday
     335                        case 'h':       // daylist:     holiday
     336                        case 'w':       // daylist:     weekday
     337                                this.error (request, 'Comma expected');
     338                                continue;
     339                        }
     340
     341                        return;
     342                }
     343        },
     344
     345        //----------------------------------------------------------------------------
     346        //      evaluate single Days+Times group
     347        //      string: if error
     348        //      null:   if no error
     349        //----------------------------------------------------------------------------
     350
     351        evaluateTimesForDays: function (request) {
     352
     353                //------------------------------------------------------------
     354                //      24/7
     355                //------------------------------------------------------------
     356                //      24/7 is a special case, which can be rendered easily
     357                //      to an offline or paper map, and should not be watered
     358                //      down by notations like “24/7; Mo 12:00-14:00 off”.
     359                //------------------------------------------------------------
     360
     361                if (this.C(request.tokens,'24/7')) {
     362                        if (request.H>=24) return;
     363                        request.day_match       = true;
     364                        request.time_match      = true;
     365                        request.rule_value      = true;
     366                        return;
     367                }
     368
     369                //------------------------------------------------------------
     370                //      open
     371                //------------------------------------------------------------
     372
     373                if (this.C(request.tokens,'open')) {
     374                        var comment = this.CG(request.tokens, '"');
     375                        if (request.H>=24) return;
     376                        request.day_match       = true;
     377                        request.time_match      = true;
     378                        request.rule_value      = true;
     379                        request.rule_comment    = comment;
     380                        return;
     381                }
     382
     383                //------------------------------------------------------------
     384                //      closed
     385                //------------------------------------------------------------
     386
     387                if (this.C(request.tokens,'closed') || this.C(request.tokens,'off')) {
     388                        var comment = this.CG(request.tokens, '"');
     389                        request.day_match       = true;
     390                        request.time_match      = true;
     391                        request.rule_value      = false;
     392                        request.rule_comment    = comment;
     393                        return;
     394                }
     395
     396                //------------------------------------------------------------
     397                //      unknown / "..."
     398                //------------------------------------------------------------
     399
     400                if (this.C(request.tokens,'unknown') || request.tokens[0].t=='"') {
     401                        var comment = this.CG(request.tokens, '"');
     402                        request.day_match       = true;
     403                        request.time_match      = true;
     404                        request.rule_value      = null;
     405                        request.rule_comment    = comment;
     406                        return;
     407                }
     408
     409                //------------------------------------------------------------
     410                //      <day_list>
     411                //              string: error
     412                //              null:   no daylist
     413                //              true:   in daylist
     414                //              false:  not in daylist
     415                //------------------------------------------------------------
     416
     417                var day_match = this.evaluateDayList (request);
     418
     419                if (typeof(day_match)=='string') return day_match;
     420
     421                if (day_match!=false) request.day_match = true;
     422
     423                //------------------------------------------------------------
     424                //      <day_list> "off"
     425                //------------------------------------------------------------
     426
     427                if (day_match != null &&
     428                        (this.C(request.tokens, 'off')||this.C(request.tokens, 'closed'))) {
     429                        var comment = this.CG(request.tokens, '"');
     430                        request.time_match      = true;
     431                        request.rule_value      = false;
     432                        request.rule_comment    = comment;
     433                        return;
     434                }
     435
     436                //------------------------------------------------------------
     437                //      Times
     438                //------------------------------------------------------------
     439
     440                var in_times = this.evaluateTimes (request, day_match!=null);
     441
     442                if (day_match==null) day_match = true;
     443
     444                if (typeof(in_times)=='string') return in_times;
     445
     446                //------------------------------------------------------------
     447                //      Status  (open, closed, unknown)
     448                //------------------------------------------------------------
     449
     450                var value;
     451                var comment;
     452
     453                if (this.C(request.tokens, 'open')) {
     454                        value   = true;
     455                        comment = this.CG(request.tokens, '"');
     456                } else if (this.C(request.tokens, 'closed')||this.C(request.tokens, 'off')) {
     457                        value   = false;
     458                        comment = this.CG(request.tokens, '"');
     459                } else if (this.C(request.tokens, 'unknown')) {
     460                        value   = null;
     461                        comment = this.CG(request.tokens, '"');
     462                } else if (request.tokens[0].t=='"') {
     463                        value   = null;
     464                        comment = this.G(request.tokens);
     465                } else {
     466                        value   = true;
     467                }
     468
     469                //------------------------------------------------------------
     470                //      If daymatch, save times for "NextTime"
     471                //------------------------------------------------------------
     472
     473                if (day_match==true) for (var i in request._times) {
     474                        var obj = request._times[i];
     475                        obj.v = value;
     476                        obj.c = comment;
     477                        request.rule_minutes.push (obj);
     478                }
     479
     480                //------------------------------------------------------------
     481                //      Rule definitely not applicable
     482                //------------------------------------------------------------
     483
     484                if (day_match==false || in_times==false) return;
     485
     486                //------------------------------------------------------------
     487                //      Rule definitely applicable
     488                //------------------------------------------------------------
     489
     490                if (day_match==true && in_times==true) {
     491                        request.rule_value      = value;
     492                        request.rule_comment    = comment;
     493                        request.time_match      = true;
     494                        return;
     495                }
     496
     497                //------------------------------------------------------------
     498                //      Error handling shortcut
     499                //------------------------------------------------------------
     500
     501                if (typeof(request.rule_value)=='boolean' && request.rule_value==value) return;
     502
     503                //------------------------------------------------------------
     504                //      Something went wrong
     505                //------------------------------------------------------------
     506
     507                request.rule_value   = null;
     508                request.rule_comment = null;
     509                request.time_match   = true;
     510                return;
     511        },
     512
     513        //============================================================================
     514        //
     515        //      CALENDAR RANGES
     516        //
     517        //============================================================================
     518
     519        //----------------------------------------------------------------------------
     520        //      evaluate ranges:
     521        //              r.new_range     - range provided?
     522        //              r.range_match   - request matches range provided?
     523        //      returns:
     524        //              string: error
     525        //              true:   range provided
     526        //              false:  no range provided
     527        //----------------------------------------------------------------------------
     528
     529        evaluateCalendarRanges: function (request) {
     530
     531                //--------------------------------------------------------
     532                //      [ calendar_ranges ]
     533                //--------------------------------------------------------
     534
     535                switch (request.tokens[0].t) {
     536                case '"':
     537                        if (request.tokens[1].t!=':') return false;
     538                case 'y':
     539                case 'm':
     540                case 'v':
     541                case 's':
     542                        break;
     543                default:
     544                        return false;
     545                }
     546
     547                //--------------------------------------------------------
     548                //      calendar_ranges
     549                //--------------------------------------------------------
     550
     551                request.range_match  = false;
     552
     553                //--------------------------------------------------------
     554                //      sticky values
     555                //--------------------------------------------------------
     556
     557                request.thisMonth = null;
     558                request.thisYear  = request.y;
     559                request.yearSet   = false;
     560
     561                //--------------------------------------------------------
     562                //      list of CalendarDays
     563                //--------------------------------------------------------
     564
     565                for (;;) {
     566
     567                        //-----------------------------------------
     568                        //      year
     569                        //-----------------------------------------
     570
     571                        if (request.tokens[0].t=='y') {
     572                                request.thisYear  = this.G(request.tokens);
     573                                request.yearSet   = true;
     574                                request.thisMonth = null;
     575                        }
     576
     577                        //--------------------------------------------------------
     578                        //      month [ - month ]
     579                        //--------------------------------------------------------
     580
     581                        if (request.tokens[0].t=='m' && request.tokens[1].t!='n' && request.tokens[1].t!='w') {
     582
     583                                var first = this.G(request.tokens);
     584                                var last  = first;
     585
     586                                if (this.C(request.tokens, '-')) {
     587                                        if (request.tokens[0].t!='m') return 'Month expected';
     588                                        last = this.G(request.tokens);
     589                                }
     590
     591                                if (first <= request.m && request.m <= last && request.y==request.thisYear)
     592                                        request.range_match=true;
     593
     594                                if (first>last && !request.yearSet && (request.m>=first || request.m<=last))
     595                                        request.range_match=true;
     596
     597                                if (first>last && request.yearSet) {
     598                                        if (request.y==request.thisYear   && request.m>=first) request.range_match=true;
     599                                        if (request.y==request.thisYear+1 && request.m<=last ) request.range_match=true;
     600                                }
     601
     602                        } else if (request.tokens[0].t=='s') {
     603
     604                                var season = this.G(request.tokens);
     605
     606                                if (!request.yearSet || request.thisYear==request.y) {
     607
     608                                        switch (season) {
     609                                        case 2:
     610                                                this.warning (request, 'Assuming "May-Oct" for "summer"');
     611                                                if (request.m>=5 && request.m<11) request.range_match=true;
     612                                                break;
     613                                        case 4:
     614                                                this.warning (request, 'Assuming "Nov-Apr" for "winter"');
     615                                                if (request.m<=4 || request.m>10) request.range_match=true;
     616                                                break;
     617                                        }
     618                                }
     619
     620                        } else if (request.tokens[0].t=='"') {
     621
     622                                //------------------------------------------------
     623                                //      text description
     624                                //------------------------------------------------
     625
     626                                var comment = this.G (request.tokens);
     627                                this.warning (request, 'Description "'+comment+'" is not evaluated.');
     628                                request.range_match = comment;
     629
     630                        } else {
     631
     632                                //------------------------------------------------
     633                                //      date_with_offset
     634                                //------------------------------------------------
     635
     636                                var first = this.evaluateDateWithOffset (request);
     637                                if (typeof(first) == 'string') return first;
     638
     639                                //------------------------------------------------
     640                                //      [ - date_with_offset ]
     641                                //------------------------------------------------
     642
     643                                var last = first;
     644                                if (this.C(request.tokens,'-')) {
     645                                        last = this.evaluateDateWithOffset (request);
     646                                        if (typeof(last) == 'string') return last;
     647                                } else if (this.C(request.tokens,'+')) {
     648                                        last = 999999999;
     649                                }
     650
     651                                //------------------------------------------------
     652                                //      check if date in interval
     653                                //      dates may be null
     654                                //------------------------------------------------
     655
     656                                if (first!=null && last!=null) {
     657
     658                                        if (first <= request.e && request.e <= last)
     659                                                request.range_match=true;
     660
     661                                        if (last<first && !request.yearSet && (request.e>=first || request.e<=last))
     662                                                request.range_match=true;
     663
     664                                        if (last<first && request.yearSet)
     665                                                this.warning (request, 'Empty date range');
     666
     667                                } else if (!request.range_match) {
     668
     669                                        request.range_match = null;
     670
     671                                }
     672                        }
     673
     674                        //--------------------------------------------------------
     675                        //      "," starts next range
     676                        //--------------------------------------------------------
     677
     678                        if (!this.C(request.tokens, ',')) break;
     679                }
     680
     681                if (!this.C(request.tokens, ':'))
     682                        this.warning (request, 'Missing ":" after range');
     683
     684                return true;
     685        },
     686
     687        //----------------------------------------------------------------------------
     688        //      evaluate date_with_offset
     689        //      string: any error
     690        //      null:   unknown date
     691        //      number: epoch value of given day
     692        //----------------------------------------------------------------------------
     693
     694        evaluateDateWithOffset: function (request) {
     695
     696                var day = this.evaluateDate (request);
     697
     698                if (typeof(day) == 'string') return day;
     699
     700                //------------------------------------------------------
     701                //      wday_offset
     702                //------------------------------------------------------
     703
     704                if (request.tokens[0].t=='+' && request.tokens[1].t=='w') {
     705                        if (day!=null) day += 7 - (this.W(day)-request.tokens[1].v+7)%7;
     706                        request.tokens.splice(0,2);
     707                } else if (request.tokens[0].t=='-' && request.tokens[1].t=='w') {
     708                        if (day!=null) day -= 7 - (request.tokens[1].v-this.W(day)+7)%7;
     709                        request.tokens.splice(0,2);
     710                }
     711
     712                //------------------------------------------------------
     713                //      ± <number> days
     714                //------------------------------------------------------
     715
     716                if (request.tokens[0].t=='+' && request.tokens[1].t=='n' && request.tokens[2].t=='days') {
     717                        if (day!=null) day += request.tokens[1].v;
     718                        request.tokens.splice(0,3);
     719                } else if (request.tokens[0].t=='-' && request.tokens[1].t=='n' && request.tokens[2].t=='days') {
     720                        if (day!=null) day -= request.tokens[1].v;
     721                        request.tokens.splice(0,3);
     722                }
     723
     724                return day;
     725        },
     726
     727        //----------------------------------------------------------------------------
     728        //      evaluate date
     729        //      string: any error
     730        //      null:   unknown date
     731        //      number: epoch value of given day
     732        //----------------------------------------------------------------------------
     733
     734        date2day: function (request, mday) {
     735
     736                if (request.thisMonth==2 && mday==29 && !request.yearSet)
     737                        return "Feb 29 only allowed if year specified";
     738
     739                var day  = this.E (request.thisYear, request.thisMonth, mday);
     740
     741                if (mday>=1 && day<=this.E (request.thisYear, request.thisMonth+1, 0))
     742                        return day;
     743
     744                this.error (request, 'Invalid day of month');
     745                return null;
     746        },
     747
     748        evaluateDate: function (request) {
     749
     750                //-----------------------------------------
     751                //      day without month
     752                //-----------------------------------------
     753
     754                if (request.tokens[0].t=='n' && request.thisMonth) {
     755                        return this.date2day (request, this.G(request.tokens));
     756                }
     757
     758                //-----------------------------------------
     759                //      year
     760                //-----------------------------------------
     761
     762                if (request.tokens[0].t=='y') {
     763                        request.thisYear  = this.G(request.tokens);
     764                        request.yearSet   = true;
     765                        request.thisMonth = null;
     766                }
     767
     768                //-----------------------------------------
     769                //      variable date
     770                //-----------------------------------------
     771
     772                if (request.tokens[0].t=='v')
     773                        return this.evaluateMovable(request, this.G(request.tokens));
     774
     775                //-----------------------------------------
     776                //      month
     777                //-----------------------------------------
     778
     779                if (request.tokens[0].t!='m') return 'Month expected';
     780                request.thisMonth = this.G(request.tokens);
     781
     782                //-----------------------------------------
     783                //      mday or wday ?
     784                //-----------------------------------------
     785
     786                switch (request.tokens[0].t) {
     787                case 'n':
     788                        return this.date2day (request, this.G(request.tokens));
     789                case 'w':
     790                        var wday=this.G(request.tokens);
     791                        if (!this.C(request.tokens, '[')) return '"[" expected';
     792
     793                        var d;
     794                        if (this.C(request.tokens,'-')) {
     795                                if (request.tokens[0].t != 'n') return "Weeknumber 1-5 expected";
     796                                d = this.E(request.thisYear, request.thisMonth+1, 1-7*this.G(request.tokens));
     797                        } else {
     798                                if (request.tokens[0].t != 'n') return "Weeknumber 1-5 expected";
     799                                d = this.E(request.thisYear, request.thisMonth, 7*this.G(request.tokens)-6);
     800                        }
     801                        if (!this.C(request.tokens, ']')) return '"]" expected';
     802                        return d + (wday - this.W(d) + 7) % 7;
     803                }
     804
     805                return 'Weekday or day of month expected';
     806        },
     807
     808        //----------------------------------------------------------------------------
     809        //      evaluate movable days
     810        //      null:   any error
     811        //      number: epoch for this day
     812        //----------------------------------------------------------------------------
     813
     814        evaluateMovable: function (request, name) {
     815
     816                switch (name) {
     817                case 'easter':
     818                        var a = request.thisYear % 19;
     819                        var b = request.thisYear % 4;
     820                        var c = request.thisYear % 7;
     821                        var k = Math.floor (request.thisYear / 100);
     822                        var p = Math.floor ((8*k + 13) / 25);
     823                        var q = Math.floor (k / 4);
     824                        var M = (15 + k - p - q) % 30;
     825                        var N = (4 + k - q) % 7;
     826                        var d = (19*a + M) % 30;
     827                        var e = (2*b + 4*c + 6*d + N) % 7;
     828                        var d = (22 + d + e);
     829                        return this.E (request.thisYear, 3, d<57 ? d : d-7);
     830                }
     831
     832                this.error (request, 'No date for movable "'+name+'" in year "'+request.thisYear+'"');
     833                return null;
     834        },
     835
     836        //============================================================================
     837        //
     838        //      DAY LIST  incl.  WEEKS
     839        //
     840        //============================================================================
     841
     842        //----------------------------------------------------------------------------
     843        //      string: error
     844        //      null:   empty
     845        //      true:   not empty, match
     846        //      false:  not empty, no match
     847        //----------------------------------------------------------------------------
     848
     849        evaluateDayList: function (request) {
     850
     851                switch (request.tokens[0].t) {
     852                case 'd':
     853                case 'w':
     854                case 'h':
     855                case 'week':
     856                        break;
     857                default:
     858                        return null;
     859                }
     860
     861                //------------------------------------------
     862                //      Days
     863                //------------------------------------------
     864
     865                var result = false;
     866
     867                for (;;) {
     868
     869                        var inweek = true;
     870
     871                        //------------------------------------------
     872                        //      Weeks n [ - n [ / n ]]
     873                        //------------------------------------------
     874
     875                        if (this.C(request.tokens, 'week')) {
     876
     877                                inweek = false;
     878
     879                                do {
     880                                        if (request.tokens[0].t!='n') return 'Weeknumber expected';
     881
     882                                        var first = this.G(request.tokens);
     883                                        var last  = first;
     884                                        var step;
     885
     886                                        if (this.C(request.tokens, '-')) {
     887
     888                                                //-------------------------------------
     889                                                //      week first - last
     890                                                //-------------------------------------
     891
     892                                                if (request.tokens[0].t!='n') return 'Weeknumber expected';
     893                                                last = this.G(request.tokens);
     894
     895                                                //-------------------------------------
     896                                                //      week first - last / step
     897                                                //-------------------------------------
     898
     899                                                if (this.C(request.tokens, '/')) {
     900                                                        if (request.tokens[0].t!='n') return 'Number expected';
     901                                                        step=this.G(request.tokens);
     902                                                }
     903                                        }
     904
     905                                        if (!step) step=1;
     906
     907                                        if (first <= request.yw && request.yw <= last &&
     908                                                (request.yw-first)%step==0) inweek = true;
     909                                } while (this.C(request.tokens, ','));
     910                        }
     911
     912                        //------------------------------------------
     913                        //      Holidays-Prefix
     914                        //------------------------------------------
     915
     916                        var cond = inweek;
     917
     918                        while (request.tokens[0].t=='h' &&
     919                                (request.tokens[1].t=='d'||request.tokens[1].t=='w'||request.tokens[1].t=='h')) {
     920                                if (!this.evaluateHoliday (request, this.G(request.tokens))) cond=false;
     921                        }
     922
     923                        //------------------------------------------
     924                        //      Monthday or Weekday or Holiday
     925                        //------------------------------------------
     926
     927                        switch (request.tokens[0].t) {
     928
     929                        //------------------------------------------
     930                        //      Monthdays
     931                        //------------------------------------------
     932
     933                        case 'd':
     934                                //------------------------------------------------
     935                                //      mday | mday - mday
     936                                //------------------------------------------------
     937
     938                                var first = this.G(request.tokens);
     939                                var last = first;
     940                                var step;
     941
     942                                if (this.C(request.tokens, '-')) {
     943                                        if (request.tokens[0].t!='d') return 'Day of month expected';
     944                                        last = this.G(request.tokens);
     945                                        //-------------------------------------
     946                                        //      /<step> (for odd/even))
     947                                        //-------------------------------------
     948                                        if (this.C(request.tokens, '/')) {
     949                                                if (request.tokens[0].t!='n') return 'Number expected';
     950                                                step=this.G(request.tokens);
     951                                        }
     952                                }
     953                                if (!step) step=1;
     954                                if (first <= request.d && request.d <= last &&
     955                                                (request.d-first)%step==0 && cond)
     956                                        result = true;
     957                                break;
     958
     959                        //------------------------------------------
     960                        //      Weekdays
     961                        //------------------------------------------
     962
     963                        case 'w':
     964                                var weekday = this.G(request.tokens);
     965
     966                                switch (request.tokens[0].t) {
     967
     968                                //----------------------------------
     969                                //      wday
     970                                //----------------------------------
     971
     972                                default:
     973                                        if (request.w==weekday && cond)
     974                                                result = true;
     975                                        break;
     976
     977                                //----------------------------------
     978                                //      wday - wday
     979                                //----------------------------------
     980
     981                                case '-':
     982                                        request.tokens.shift();
     983                                        if (request.tokens[0].t != 'w') return null;
     984                                        if ((request.w-weekday+7)%7 <= (this.G(request.tokens)-weekday+7)%7
     985                                                        && cond)
     986                                                result = true;
     987                                        break;
     988
     989                                //----------------------------------
     990                                //      wday [...]
     991                                //----------------------------------
     992
     993                                case '[':
     994                                        request.tokens.shift();
     995                                        var found=false;
     996                                        for (;;) {
     997                                                switch (request.tokens[0].t) {
     998                                                default:
     999                                                        return 'Number expected';
     1000                                                case 'n':
     1001                                                        var snth = this.G(request.tokens);
     1002                                                        var enth = snth;
     1003                                                        if (this.C(request.tokens,'-')) {
     1004                                                                if (request.tokens[0].t!='n')
     1005                                                                        return 'Number expected';
     1006                                                                var enth = this.G(request.tokens);
     1007                                                        }
     1008                                                        if (snth*7-6 <= request.d && request.d <= enth*7)
     1009                                                                found = true;
     1010                                                        break;
     1011
     1012                                                case '-':
     1013                                                        request.tokens.shift();
     1014                                                        if (request.tokens[0].t != 'n')
     1015                                                                return 'Number expected';
     1016                                                        var snth = this.G(request.tokens);
     1017                                                        if (snth<1 || snth>5)
     1018                                                                return 'Number between 1 and 5 expected';
     1019                                                        var start = request.l - snth*7;
     1020                                                        if (start <= request.d && request.d < start+7)
     1021                                                                found = true;
     1022                                                }
     1023
     1024                                                if (!this.C(request.tokens, ',')) break;
     1025                                        }
     1026
     1027                                        if (!this.C(request.tokens, ']')) return '"]" expected';
     1028
     1029                                        if (found && request.w==weekday && cond)
     1030                                                result = true;
     1031                                }
     1032
     1033                                break;
     1034
     1035                        //------------------------------------------
     1036                        //      Holidays
     1037                        //------------------------------------------
     1038
     1039                        case 'h':
     1040                                var type  = this.G(request.tokens);
     1041                                var offset= 0;
     1042                                if ((request.tokens[0].t=='+' || request.tokens[0].t=='-') && request.tokens[1].t=='n' && request.tokens[2].t=='days') {
     1043                                        offset = (request.tokens[0].t=='-' ? -1 : +1) * request.tokens[1].v;
     1044                                        request.tokens.splice (0,3);
     1045                                }
     1046                                var r = this.evaluateHoliday (request, type, offset);
     1047                                if (!result && cond) result = r;
     1048                                break;
     1049
     1050                        default:
     1051                                return 'Day of month, weekday or type of holiday expected';
     1052                        }
     1053
     1054                        //--------------------------------------------------------
     1055                        //      "," starts next selection
     1056                        //--------------------------------------------------------
     1057
     1058                        if (!this.C (request.tokens, ',')) return result;
     1059                }
     1060        },
     1061
     1062        //----------------------------------------------------------------------------
     1063        //      true:   parameter day is holiday
     1064        //      false:  parameter day is not holiday
     1065        //      null:   unknown
     1066        //----------------------------------------------------------------------------
     1067
     1068        evaluateHoliday: function (request, type, offset) {
     1069
     1070                if (!request.option.region) {
     1071                        this.warning (request, 'Assuming "'+this.defaultRegion+'" for region');
     1072                        request.option.region = this.defaultRegion;
     1073                }
     1074
     1075                var region = request.option.region.toLowerCase();
     1076                var checker = this['holidays_'+region];
     1077
     1078                if (!checker) {
     1079                        this.warning (request,'No holiday list for type "'+type+'" and region "'+region+'"');
     1080                        return false;
     1081                }
     1082
     1083                if (!this.holidayDates[region]) this.holidayDates[region]=[];
     1084                if (!this.holidayDates[region][type]) this.holidayDates[region][type]=[];
     1085
     1086                var date  = request.e - offset;
     1087                var years = [request.y-1, request.y, request.y+1];
     1088
     1089                for (var i in years) {
     1090
     1091                        var year = years[i];
     1092                        var list = this.holidayDates[region][type][year];
     1093
     1094                        if (!list) this.holidayDates[region][type][year] = list =
     1095                                this['holidays_'+region] (request, year, type);
     1096
     1097                        if (!list) {
     1098                                this.warning (request,
     1099                                        'Holiday type "'+type+'" for region "'+region+'" not configured');
     1100                                return false;
     1101                        }
     1102                               
     1103                        for (var holidayName in list) {
     1104                                if (list[holidayName]!=date) continue;
     1105                                this.warning (request,
     1106                                        'Found "'+holidayName+'" for holiday type "'+type+'"');
     1107                                return true;
     1108                        }
     1109                }
     1110                return false;
     1111        },
     1112
     1113        holidays_de: function (request, year, type) {
     1114
     1115                switch (type) {
     1116                case 'public':
     1117                        list = {};
     1118                        list['Neujahrstag']               = this.E (year,  1,  1);
     1119                        list['Tag der Arbeit']            = this.E (year,  5,  1);
     1120                        list['Tag der Deutschen Einheit'] = this.E (year, 10,  3);
     1121                        list['1. Weihnachtstag']          = this.E (year, 12, 25);
     1122                        list['2. Weihnachtstag']          = this.E (year, 12, 26);
     1123
     1124                        var easterDate = this.evaluateMovable (request, 'easter');
     1125
     1126                        if (easterDate) {
     1127                                list['Karfreitag']          = easterDate - 2;
     1128                                list['Ostermontag']         = easterDate + 1;
     1129                                list['Christi Himmelfahrt'] = easterDate +39;
     1130                                list['Pfingstmontag']       = easterDate +50;
     1131                        }
     1132                        return list;
     1133                }
     1134                return null;
     1135        },
     1136
     1137        holidays_at: function (request, year, type) {
     1138
     1139                switch (type) {
     1140                case 'public':
     1141                        list = {};
     1142                        list['Neujahr']                   = this.E (year,  1,  1);
     1143                        list['Heilige Drei Könige']       = this.E (year,  1,  6);
     1144                        list['Staatsfeiertag']            = this.E (year,  5,  1);
     1145                        list['Mariä Himmelfahrt']         = this.E (year,  8, 15);
     1146                        list['Nationalfeiertag']          = this.E (year, 10, 26);
     1147                        list['Allerheiligen']             = this.E (year, 11,  1);
     1148                        list['Mariä Empängnis']           = this.E (year, 12,  8);
     1149                        //list['Heiliger Abend']            = this.E (year, 12, 24);
     1150                        list['Christtag']                 = this.E (year, 12, 25);
     1151                        list['Stephanitag']               = this.E (year, 12, 26);
     1152                        //list['Silvester']                 = this.E (year, 12, 31);
     1153
     1154                        var easterDate = this.evaluateMovable (request, 'easter');
     1155
     1156                        if (easterDate) {
     1157                                //list['Karfreitag']          = easterDate - 2;
     1158                                list['Ostermontag']         = easterDate + 1;
     1159                                list['Christi Himmelfahrt'] = easterDate +39;
     1160                                list['Pfingstmontag']       = easterDate +50;
     1161                                list['Fronleichnam']        = easterDate +60;
     1162                        }
     1163                        return list;
     1164                }
     1165                return null;
     1166        },
     1167
     1168        //============================================================================
     1169        //
     1170        //      TIMES
     1171        //
     1172        //============================================================================
     1173
     1174        //----------------------------------------------------------------------------
     1175        //      true:   match
     1176        //      false:  no match
     1177        //      null:   unknown
     1178        //      default is 00:00-24:00 and Error message
     1179        //----------------------------------------------------------------------------
     1180
     1181        evaluateTimes: function (request, acceptEmpty) {
     1182
     1183                request._times = [];
     1184
     1185                switch (request.tokens[0].t) {
     1186                case 't':
     1187                case 'e':
     1188                        break;
     1189                case '-':
     1190                        if (request.tokens[1].t=='t' || request.tokens[1].t=='e') break;
     1191                default:
     1192                        if (!acceptEmpty) return "";
     1193                        this.warning (request, 'No time specification, assuming 0:00-24:00');
     1194                case '??':
     1195                        return request.H < 24
     1196                }
     1197
     1198                var time   = request.H * 60 + request.M;
     1199                var result = false;
     1200
     1201                for (;;) {
     1202
     1203                        //-------------------------------------
     1204                        //      [time]
     1205                        //-------------------------------------
     1206
     1207                        var first = -1;
     1208
     1209                        if (request.tokens[0].t!='-') {
     1210                                first = this.evaluateTime (request);
     1211                                if (typeof(first)=='string') return first;
     1212                        }
     1213
     1214                        var last = first;
     1215                        var step = 0;
     1216
     1217                        switch (request.tokens[0].t) {
     1218                        //-------------------------------------
     1219                        //      time - time
     1220                        //-------------------------------------
     1221                        case '-':
     1222                                request.tokens.shift();
     1223                                last = this.evaluateTime (request);
     1224                                if (typeof(last)=='string') return last;
     1225                                if (first<0) {
     1226                                        var h = last>6*60 ? 6 : 0;
     1227                                        first = h*60;
     1228                                        this.error (request, 'Missing starttime for timerange, assuming '+h+':00');
     1229                                }
     1230                                if (last<=first) last += 24*60;
     1231                                //-------------------------------------
     1232                                //      /<step> (for collection_times)
     1233                                //-------------------------------------
     1234                                if (this.C(request.tokens, '/')) {
     1235                                        switch (request.tokens[0].t) {
     1236                                        case 't':
     1237                                                step=this.G(request.tokens);
     1238                                                break;
     1239                                        case 'n':
     1240                                                step=this.G(request.tokens);
     1241                                                break;
     1242                                        default:
     1243                                                return 'H:MM or number of minutes expected';
     1244                                        }
     1245                                } else if (this.C(request.tokens,'+')) {
     1246                                        this.warning (request, 'Assuming '+
     1247                                                Math.floor(last/60)+':'+(last%60<10?'0':'')+last%60+
     1248                                                ' as endtime for "+" notation');
     1249                                }
     1250                                break;
     1251                        //-------------------------------------
     1252                        //      time+
     1253                        //-------------------------------------
     1254                        case '+':
     1255                                request.tokens.shift();
     1256                                last = first + 4 * 60;
     1257                                if (last>=24*60) last=24*60;
     1258                                this.warning (request, 'Assuming '+
     1259                                        Math.floor(last/60)+':'+(last%60<10?'0':'')+last%60+
     1260                                        ' as endtime for "+" notation');
     1261                                break;
     1262                        }
     1263
     1264                        //-------------------------------------
     1265                        //      eval
     1266                        //-------------------------------------
     1267
     1268                        if (first!=null && last!=null) {
     1269                                if (first <= time && time < last) result = true;
     1270                                request._times.push ({f:first,l:last,s:(step?step:last-first)});
     1271                        } else if (!result) {
     1272                                result = null;
     1273                        }
     1274
     1275                        //-------------------------------------
     1276                        //      …, nexttimerange
     1277                        //-------------------------------------
     1278
     1279                        if (request.tokens[0].t == ',' && (request.tokens[1].t == 't' || request.tokens[1].t == 'e')) {
     1280                                request.tokens.shift();
     1281                                continue;
     1282                        }
     1283
     1284                        //-------------------------------------------------
     1285                        //      error correction
     1286                        //-------------------------------------------------
     1287
     1288                        switch (request.tokens[0].t) {
     1289                        case 't':       // timespan:    time
     1290                        case 'e':       // timespan:    event
     1291                                var msg = 'Comma expected before "'+request.tokens[0].s+'" in "'+request.rule+'"';
     1292                                this.error (request, 'Comma expected');
     1293                                continue;
     1294                        }
     1295
     1296                        break;
     1297                }
     1298
     1299                return result;
     1300        },
     1301
     1302        //----------------------------------------------------------------------------
     1303        //      string: error
     1304        //      number: minutes since midnight
     1305        //----------------------------------------------------------------------------
     1306
     1307        evaluateTime: function (request, last) {
     1308
     1309                //--------------------------------------------
     1310                //      hh:mm
     1311                //--------------------------------------------
     1312
     1313                if (request.tokens[0].t=='t') return this.G(request.tokens);
     1314
     1315                //--------------------------------------------
     1316                //      event
     1317                //--------------------------------------------
     1318
     1319                if (request.tokens[0].t=='e') {
     1320
     1321                        var t = this.evaluateEvent (request, this.G(request.tokens));
     1322
     1323                        if (typeof(t)=='string') return t;
     1324
     1325                        if (request.tokens[0].t=='+' && request.tokens[1].t=='t' && request.tokens[2].t=='hours') {
     1326                                t += request.tokens[1].v;
     1327                                request.tokens.splice(0, 3);
     1328                                return t;
     1329                        }
     1330
     1331                        if (request.tokens[0].t=='-' && request.tokens[1].t=='t' && request.tokens[2].t=='hours') {
     1332                                t -= request.tokens[1].v;
     1333                                request.tokens.splice(0, 3);
     1334                                return t;
     1335                        }
     1336
     1337                        return t;
     1338                }
     1339
     1340                //--------------------------------------------
     1341                //      syntax error
     1342                //--------------------------------------------
     1343
     1344                return 'HH:MM or sunset/sunrise expected';
     1345        },
     1346
     1347        //----------------------------------------------------------------------------
     1348        //      number: minutes from midnight
     1349        //      null:   unknown
     1350        //----------------------------------------------------------------------------
     1351
     1352        evaluateEvent: function (request, name) {
     1353
     1354                switch (name) {
     1355
     1356                case 'sunrise':
     1357                        this.warning (request, 'Using 06:00 for "sunrise"');
     1358                        return  6 * 60 + 00;
     1359
     1360                case 'sunset':
     1361                        this.warning (request, 'Using 18:00 for "sunset"');
     1362                        return 18 * 60 + 00;
     1363                }
     1364
     1365                this.error (request, 'No time for event "'+name+'"');
     1366                return null;
     1367        },
     1368
     1369
     1370        //==============================================================================
     1371        //
     1372        //      Lexical analysis
     1373        //
     1374        //==============================================================================
     1375
     1376        //----------------------------------------------------------------------------
     1377        //      scans an expression
     1378        //              result to request.request.tokens
     1379        //----------------------------------------------------------------------------
     1380
     1381        scan: function (request, expression) {
     1382
     1383                request.parsedExpression = [];
     1384
     1385                var conflicts = expression.match (/[a-z_àè]+[0-9][a-z0-9_]*|[0-9]+[a-z_]\w*/gi);
     1386                if (conflicts) this.scanError (request,
     1387                        'Missing space at '+conflicts.join(', '));
     1388
     1389                conflicts = expression.match (/[0-9]+[.h][0-9]+/g);
     1390                if (conflicts) this.scanError (request,
     1391                        'Please use ":" as hour/minute-separator at '+conflicts.join(', '));
     1392
     1393                //--------------------------------------------------------------------
     1394                //
     1395                //      The time domain expression is parsed into tokens:
     1396                //
     1397                //      Words:          Jan  January  Mo  Monday  open  sunset
     1398                //      Always open:    24/7
     1399                //      Time:           06:30   20:00
     1400                //      Calendar day:   1.      2.      31.
     1401                //      Number:         1       2       3
     1402                //      String:         "on appointment"
     1403                //                      (strings may not contain '"' or ';')
     1404                //      Alternative:    ||
     1405                //      Garbage:        (sequence of characters which are neither
     1406                //                      alphanumeric nor blankspace)
     1407                //
     1408                //--------------------------------------------------------------------
     1409                //      The match method returns a list of tokens.
     1410                //--------------------------------------------------------------------
     1411
     1412                var symbols = expression.match (
     1413                        /[a-z_]+|24\/7|[0-9]+[.:][0-9]+|[0-9]+[.]|[0-9]+|"[^";]*"|\|\||[^\w\s]/gi);
     1414
     1415                sym:
     1416                for (i in symbols) {
     1417
     1418                        var token = symbols[i];
     1419
     1420                        //------------------------------------------------------------
     1421                        //      Integers...
     1422                        //      0..60           number
     1423                        //      1970..2069      year
     1424                        //------------------------------------------------------------
     1425
     1426                        var value = parseInt (token, 10);
     1427                        var lower = token.toLowerCase();
     1428
     1429                        //------------------------------------------------------------
     1430                        //      Token starts with digits
     1431                        //------------------------------------------------------------
     1432
     1433                        if (!isNaN(value)) {
     1434
     1435                                var l2 = token.split(token.match(/[.]/) ? '.' : ':')[1];
     1436                                var value2 = parseInt (l2);
     1437
     1438                                //----------------------------------------------------
     1439                                //      Time notation “HH:MM” or “HH.MM”
     1440                                //----------------------------------------------------
     1441
     1442                                if (!isNaN(value2)) {
     1443
     1444                                        //--------------------------------------------
     1445                                        //      Verify hours<36 and minutes<60?
     1446                                        //--------------------------------------------
     1447
     1448                                        if (token.length>5 || l2.length!=2 || value>36 || value2>=60)
     1449                                        this.scanError (request,
     1450                                                'Invalid time at "'+token+'" in rule "'+request.rule+'"');
     1451
     1452                                        request.parsedExpression.push ({t:'t', v:value*60+value2, s:token});
     1453                                        continue;
     1454                                }
     1455
     1456                                //----------------------------------------------------
     1457                                //      Calendar day “DD.”
     1458                                //----------------------------------------------------
     1459
     1460                                if (token.split('.').length>=2) {
     1461
     1462                                        //--------------------------------------------
     1463                                        //      Verify day in 1…31
     1464                                        //--------------------------------------------
     1465
     1466                                        if (value<1 || value>31)
     1467                                        this.scanError (request,
     1468                                                'Invalid day at "'+token+'" in rule "'+request.rule+'"');
     1469
     1470                                        request.parsedExpression.push ({t:'d', v:value, s:token});
     1471                                        continue;
     1472                                }
     1473
     1474                                //----------------------------------------------------
     1475                                //      Always notation “24/7”
     1476                                //----------------------------------------------------
     1477
     1478                                if (token=='24/7') {
     1479                                        request.parsedExpression.push ({t:token, s:token});
     1480                                        continue;
     1481                                }
     1482
     1483                                //----------------------------------------------------
     1484                                //      Number less or equal 365: day of year
     1485                                //----------------------------------------------------
     1486
     1487                                if (value<=365) {
     1488                                        request.parsedExpression.push ({t:'n', v:value, s:token});
     1489                                        continue;
     1490                                }
     1491
     1492                                //----------------------------------------------------
     1493                                //      Number in “1970”…“2070”: year
     1494                                //----------------------------------------------------
     1495
     1496                                if (value>=1970 && value<2070) {
     1497                                        request.parsedExpression.push ({t:'y', v:value, s:token});
     1498                                        continue;
     1499                                }
     1500                        }
     1501
     1502                        //------------------------------------------------------------
     1503                        //      wrong words
     1504                        //------------------------------------------------------------
     1505
     1506                        for (var msg in this.wrong_words) {
     1507
     1508                                //----------------------------------------------------
     1509                                //      get replacement for bad word
     1510                                //----------------------------------------------------
     1511
     1512                                replacement = this.wrong_words[msg][lower];
     1513                                if (replacement==null) continue;
     1514
     1515                                //----------------------------------------------------
     1516                                //      log replacement
     1517                                //----------------------------------------------------
     1518
     1519                                this.scanError (request,
     1520                                        msg.replace(/<ko>/,token).replace(/<ok>/,replacement));
     1521
     1522                                //----------------------------------------------------
     1523                                //      if replacement by empty string, discard token
     1524                                //----------------------------------------------------
     1525
     1526                                lower=replacement.toLowerCase();
     1527                                if (lower=='') continue sym;
     1528
     1529                                //----------------------------------------------------
     1530                                //      replacement completed
     1531                                //----------------------------------------------------
     1532
     1533                                break;
     1534                        }
     1535
     1536                        //------------------------------------------------------------
     1537                        //      Symbols
     1538                        //------------------------------------------------------------
     1539
     1540                        switch (lower) {
     1541                        case '+':
     1542                        case ',':
     1543                        case '-':
     1544                        case '/':
     1545                        case ':':
     1546                        case '[':
     1547                        case ']':
     1548                                request.parsedExpression.push ({t:lower, s:token});
     1549                                continue;
     1550                        case '–':
     1551                                this.scanError (request,
     1552                                        'Use a minus sign "-" instead of a dash "–".');
     1553                                request.parsedExpression.push ({t:'-', s:token});
     1554                                continue;
     1555                        case ';':
     1556                                request.parsedExpression.push ({s:token});
     1557                                continue;
     1558                        case '||':
     1559                                request.parsedExpression.push ({s:token});
     1560                                continue;
     1561                        }
     1562
     1563                        //------------------------------------------------------------
     1564                        //      Words...
     1565                        //      - name of month
     1566                        //      - name of weekday
     1567                        //      - name of holiday
     1568                        //      - other
     1569                        //------------------------------------------------------------
     1570
     1571                        if ((value=this.seasons[lower]) != null) {
     1572                                request.parsedExpression.push ({t:'s', v:value, s:token});
     1573                                continue;
     1574                        }
     1575                        if ((value=this.months[lower]) != null) {
     1576                                request.parsedExpression.push ({t:'m', v:value, s:token});
     1577                                continue;
     1578                        }
     1579                        if ((value=this.weekdays[lower]) != null) {
     1580                                request.parsedExpression.push ({t:'w', v:value, s:token});
     1581                                continue;
     1582                        }
     1583                        if ((value=this.movables[lower]) != null) {
     1584                                request.parsedExpression.push ({t:'v', v:value, s:token});
     1585                                continue;
     1586                        }
     1587                        if ((value=this.holidays[lower]) != null) {
     1588                                request.parsedExpression.push ({t:'h', v:value, s:token});
     1589                                continue;
     1590                        }
     1591                        if ((value=this.events[lower]) != null) {
     1592                                request.parsedExpression.push ({t:'e', v:value, s:token});
     1593                                continue;
     1594                        }
     1595
     1596                        switch (lower) {
     1597                        case 'closed':
     1598                        case 'days':
     1599                        case 'hours':
     1600                        case 'off':
     1601                        case 'open':
     1602                        case 'unknown':
     1603                        case 'week':
     1604                                request.parsedExpression.push ({t:lower, s:token});
     1605                                continue;
     1606                        }
     1607
     1608                        //------------------------------------------------------------
     1609                        //      Comment
     1610                        //------------------------------------------------------------
     1611
     1612                        if (token.substr(0,1)=='"') {
     1613                                request.parsedExpression.push ({t:'"', v:this.T(token.substr(1,token.length-2)), s:token});
     1614                                continue;
     1615                        }
     1616
     1617                        //------------------------------------------------------------
     1618                        //      Error correction
     1619                        //------------------------------------------------------------
     1620
     1621                        for (var msg in this.wrong_weekdays) {
     1622                                value = this.wrong_weekdays[msg][lower];
     1623                                if (value==null) continue;
     1624                                var ok = ['Su','Mo','Tu','We','Th','Fr','Sa'][value];
     1625                                this.scanError (request,
     1626                                        msg.replace(/<ko>/,token).replace(/<ok>/,ok));
     1627                                request.parsedExpression.push ({t:'w', v:value, s:token});
     1628                                continue sym;
     1629                        }
     1630
     1631                        for (var msg in this.wrong_months) {
     1632                                value = this.wrong_months[msg][lower];
     1633                                if (value==null) continue;
     1634                                var ok = [null,'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'][value];
     1635                                this.scanError (request,
     1636                                        msg.replace(/<ko>/,token).replace(/<ok>/,ok));
     1637                                request.parsedExpression.push ({t:'m', v:value, s:token});
     1638                                continue sym;
     1639                        }
     1640
     1641                        for (var msg in this.wrong_seasons) {
     1642                                value = this.wrong_seasons[msg][lower];
     1643                                if (value==null) continue;
     1644                                var ok = [null,'Spring','Summer','Autumn','Winter'][value];
     1645                                this.scanError (request,
     1646                                        msg.replace(/<ko>/,token).replace(/<ok>/,ok));
     1647                                request.parsedExpression.push ({t:'s', v:value, s:token});
     1648                                continue sym;
     1649                        }
     1650
     1651                        //------------------------------------------------------------
     1652                        //      Symbols
     1653                        //------------------------------------------------------------
     1654
     1655                        request.parsedExpression.push ({t:'??', s:token});
     1656                }
     1657
     1658                //------------------------------------------------------------
     1659                //      eof marker
     1660                //------------------------------------------------------------
     1661
     1662                request.parsedExpression.push ({});
     1663        },
     1664
     1665        //----------------------------------------------------------------------------
     1666        //      little helper functions
     1667        //----------------------------------------------------------------------------
     1668
     1669        T: function (s) {
     1670                return s.toString().replace (/^\s+/, '').replace (/\s+$/, '');
     1671        },
     1672
     1673        G: function (tokens) {
     1674                var v = tokens[0].v;
     1675                tokens.shift();
     1676                return v;
     1677        },
     1678
     1679        CG: function (tokens, tag) {
     1680                if (tokens[0].t!=tag) return null;
     1681                var v = tokens[0].v;
     1682                tokens.shift();
     1683                return v;
     1684        },
     1685
     1686        C: function (tokens, tag) {
     1687                if (tokens[0].t!=tag) return false;
     1688                tokens.shift();
     1689                return true;
     1690        },
     1691
     1692        //----------------------------------------------------------------------------
     1693        //      initialized arrays
     1694        //----------------------------------------------------------------------------
     1695
     1696        seasons: {
     1697                        summer: 2, winter: 4
     1698                },
     1699
     1700        months: {
     1701                        jan:  1, feb:  2, mar:  3, apr:  4, may:  5, jun:  6,
     1702                        jul:  7, aug:  8, sep:  9, oct: 10, nov: 11, dec: 12
     1703                },
     1704
     1705        weekdays: {
     1706                        su: 0, mo: 1, tu: 2, we: 3, th: 4, fr: 5, sa: 6
     1707                },
     1708
     1709        movables: {
     1710                        easter: 'easter'
     1711                },
     1712
     1713        holidays: {
     1714                        sh: 'school', ph: 'public'
     1715                },
     1716
     1717        events: {
     1718                        sunrise: 'sunrise', sunset: 'sunset'
     1719                },
     1720
     1721        holidayDates: {},
     1722
     1723        defaultRegion: 'de',
     1724
     1725        //----------------------------------------------------------------------------
     1726        //      error correction
     1727        //----------------------------------------------------------------------------
     1728
     1729        wrong_words: {
     1730                'Please ommit "<ko>".': {
     1731                        h:              ''
     1732                }, 'Please use notation "<ok>" for "<ko>".': {
     1733                        to:             '-'
     1734                }, 'Bitte verzichte auf "<ko>".': {
     1735                        uhr:            ''
     1736                }, 'Bitte benutze die Schreibweise "<ok>" für "<ko>".': {
     1737                        und:            ','
     1738                }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
     1739                        feiertag:       'PH',
     1740                        feiertage:      'PH',
     1741                        feiertagen:     'PH'
     1742                }, 'S\'il vous plaît utiliser "<ok>" pour "<ko>".': {
     1743                        'fermé':        'off',
     1744                        'et':           ',',
     1745                        'à':            '-'
     1746                }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
     1747                        feestdag:       'PH',
     1748                        feestdagen:     'PH'
     1749                }},
     1750
     1751        wrong_seasons: {
     1752                'Bitte benutze die englische Schreibweise "<ok>" für "<ko>".': {
     1753                        sommer:         2
     1754                }},
     1755
     1756        wrong_months: {
     1757                'Please use the englisch abbreviation "<ok>" for "<ko>".': {
     1758                        january:        1,
     1759                        february:       2,
     1760                        march:          3,
     1761                        april:          4,
     1762                        may:            5,
     1763                        june:           6,
     1764                        july:           7,
     1765                        august:         8,
     1766                        september:      9,
     1767                        october:        10,
     1768                        november:       11,
     1769                        december:       12
     1770                }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
     1771                        januar:         1,
     1772                        februar:        2,
     1773                        märz:           3,
     1774                        maerz:          3,
     1775                        mai:            5,
     1776                        juni:           6,
     1777                        juli:           7,
     1778                        okt:            10,
     1779                        oktober:        10,
     1780                        dez:            12,
     1781                        dezember:       12
     1782                }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
     1783                        janvier:        1,
     1784                        février:        2,
     1785                        fév:            2,
     1786                        mars:           3,
     1787                        avril:          4,
     1788                        avr:            4,
     1789                        mai:            5,
     1790                        juin:           6,
     1791                        juillet:        7,
     1792                        août:           8,
     1793                        aoû:            8,
     1794                        septembre:      9,
     1795                        octobre:        10,
     1796                        novembre:       11,
     1797                        décembre:       12
     1798                }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
     1799                        januari:        1,
     1800                        februari:       2,
     1801                        maart:          3,
     1802                        mei:            5,
     1803                        augustus:       8
     1804                }},
     1805
     1806        wrong_weekdays: {
     1807                'Please use the abbreviation "<ok>" for "<ko>".': {
     1808                        sun:            0,
     1809                        sunday:         0,
     1810                        mon:            1,
     1811                        monday:         1,
     1812                        tue:            2,
     1813                        tuesday:        2,
     1814                        wed:            3,
     1815                        wednesday:      3,
     1816                        thu:            4,
     1817                        thursday:       4,
     1818                        fri:            5,
     1819                        friday:         5,
     1820                        sat:            6,
     1821                        saturday:       6
     1822                }, 'Bitte benutze die englische Abkürzung "<ok>" für "<ko>".': {
     1823                        so:             0,
     1824                        son:            0,
     1825                        sonntag:        0,
     1826                        montag:         1,
     1827                        di:             2,
     1828                        die:            2,
     1829                        dienstag:       2,
     1830                        mi:             3,
     1831                        mit:            3,
     1832                        mittwoch:       3,
     1833                        'do':           4,
     1834                        don:            4,
     1835                        donnerstag:     4,
     1836                        fre:            5,
     1837                        freitag:        5,
     1838                        sam:            6,
     1839                        samstag:        6
     1840                }, 'S\'il vous plaît utiliser l\'abréviation "<ok>" pour "<ko>".': {
     1841                        dim:            0,
     1842                        dimanche:       0,
     1843                        lu:             1,
     1844                        lun:            1,
     1845                        lundi:          1,
     1846                        mardi:          2,
     1847                        mer:            3,
     1848                        mercredi:       3,
     1849                        je:             4,
     1850                        jeu:            4,
     1851                        jeudi:          4,
     1852                        ve:             5,
     1853                        ven:            5,
     1854                        vendredi:       5,
     1855                        samedi:         6
     1856                }, 'Neem de engelse afkorting "<ok>" voor "<ko>" alstublieft.': {
     1857                        zo:             0,
     1858                        zon:            0,
     1859                        zontag:         0,
     1860                        maandag:        1,
     1861                        din:            2,
     1862                        dinsdag:        2,
     1863                        wo:             3,
     1864                        woe:            3,
     1865                        woensdag:       3,
     1866                        donderdag:      4,
     1867                        vr:             5,
     1868                        vri:            5,
     1869                        vrijdag:        5,
     1870                        za:             6,
     1871                        zat:            6,
     1872                        zaterdag:       6
     1873                }},
     1874
     1875        //==============================================================================
     1876        //
     1877        //      DATE / TIME
     1878        //
     1879        //==============================================================================
     1880
     1881        //----------------------------------------------------------------------------
     1882        //      create timestamp from Data()-object or component-object
     1883        //----------------------------------------------------------------------------
     1884
     1885        createTimestamp: function (timestamp, offset, shift) {
     1886
     1887                var result = {};
     1888                var now    = new Date();
     1889
     1890                if (timestamp==null) timestamp = now;
     1891
     1892                if (timestamp.getSeconds) {
     1893                        //--------------------------------------------
     1894                        //      components from JS Date-object
     1895                        //--------------------------------------------
     1896                        result.S = timestamp.getSeconds();
     1897                        result.M = timestamp.getMinutes();
     1898                        result.H = timestamp.getHours();
     1899                        result.d = timestamp.getDate();
     1900                        result.m = timestamp.getMonth()+1;
     1901                        result.y = timestamp.getFullYear();
     1902                } else {
     1903                        //--------------------------------------------
     1904                        //      parse components from input argument
     1905                        //--------------------------------------------
     1906                        result.S = parseInt(timestamp.S,10); if (isNaN(result.S)) result.S=0;
     1907                        result.M = parseInt(timestamp.M,10); if (isNaN(result.M)) result.M=0;
     1908                        result.H = parseInt(timestamp.H,10); if (isNaN(result.H)) result.H=0;
     1909                        result.d = parseInt(timestamp.d,10); if (isNaN(result.d)) result.d=1;
     1910                        result.m = parseInt(timestamp.m,10); if (isNaN(result.m)) result.m=1;
     1911                        result.y = isNaN (parseInt (timestamp.y, 10)) ?
     1912                                now.getFullYear() : (parseInt (timestamp.y, 10)+30)%100+1970;
     1913                }
     1914
     1915                //-----------------------------------------------------------
     1916                //      offset is a real offset to the input argument time
     1917                //-----------------------------------------------------------
     1918
     1919                if (offset) result.S += offset;
     1920
     1921                //-----------------------------------------------------------
     1922                //      shift forces the hour to be >= 24
     1923                //      used to implement day wrap
     1924                //-----------------------------------------------------------
     1925
     1926                if (shift ) result.H -= shift;
     1927
     1928                //-----------------------------------------------------------
     1929                //      normalize time
     1930                //-----------------------------------------------------------
     1931
     1932                var carry;
     1933                carry = Math.floor (result.S / 60); result.S -= carry * 60; result.M += carry;
     1934                carry = Math.floor (result.M / 60); result.M -= carry * 60; result.H += carry;
     1935                carry = Math.floor (result.H / 24); result.H -= carry * 24; result.d += carry;
     1936
     1937                //-----------------------------------------------------------
     1938                //      normalize date
     1939                //-----------------------------------------------------------
     1940
     1941                if (result.d<1) {
     1942                        result.m--;
     1943                        result.d=this.E(result.y,result.m+1,1)-this.E(result.y,result.m,1);
     1944                }
     1945                if (this.E(result.y,result.m,result.d)>=this.E(result.y,result.m+1,1)) {
     1946                        result.m++; result.d=1;
     1947                }
     1948                if (result.m< 1) {
     1949                        result.y--;
     1950                        result.m=12;
     1951                }
     1952                if (result.m>12) {
     1953                        result.y++;
     1954                        result.m= 1;
     1955                }
     1956
     1957                //-----------------------------------------------------------
     1958                //      shift forces the hour to be >= 24
     1959                //      used to implement day wrap
     1960                //-----------------------------------------------------------
     1961
     1962                if (shift) result.H += shift;
     1963
     1964                //-----------------------------------------------------------
     1965                //      compute absolute day number and weekday
     1966                //-----------------------------------------------------------
     1967
     1968                result.s = this.E (result.y,1,1);
     1969                result.e = this.E (result.y,result.m,result.d);
     1970                result.w = this.W (result.e);
     1971                result.l = this.L (result)+1;
     1972                result.wy= result.y; var s = this.S (result.wy); if (result.e<s) s=this.S(--result.wy);
     1973                result.yw= Math.floor((result.e-s)/7)+1;
     1974                return result;
     1975        },
     1976
     1977        //----------------------------------------------------------------------------
     1978        //      little helper functions
     1979        //----------------------------------------------------------------------------
     1980
     1981        E: function (y,m,d) {
     1982                return 367*y - Math.floor (1.75 * (y + Math.floor ((m+9)/12))) +Math.floor(275*m/9) +d - 719574;
     1983        },
     1984
     1985        L: function (request) {
     1986                return this.E(request.y, request.m+1, 1) - this.E(request.y, request.m, 1);
     1987        },
     1988
     1989        W: function (d) {
     1990                return (d+4) % 7;
     1991        },
     1992
     1993        S: function (y) {
     1994                var s = this.E (y, 1, 1);
     1995                return s + (11-this.W(s))%7-3;
     1996        },
     1997
     1998        YW: function (y, d) {
     1999                var s = this.S (y);
     2000                if (d<s) s = this.S(--y);
     2001                return {y: y, w: Math.floor((d-s)/7)+1};
     2002        },
     2003
     2004        //==============================================================================
     2005        //
     2006        //      Debug
     2007        //
     2008        //==============================================================================
     2009
     2010        alertRule: function (tokens, name) {
     2011                var result = [];
     2012                for (var i in request.tokens) {
     2013                        result.push (tokens[i].t ? '{'+tokens[i].s+'}' : ';');
     2014                }
     2015                alert ((name||'')+'['+tokens.length+']:\n'+result.join(' '));
     2016        },
     2017
     2018        alertTokens: function (o) {
     2019                var result = [];
     2020                for (var tag in o) {
     2021                        result.push (o[tag].s);
     2022                }
     2023                alert (result.join(' '));
     2024        },
     2025
     2026        text: function (obj, name) {
     2027                if (obj==null) return name+': Ø';
     2028                var type = typeof(obj);
     2029                if (type != 'object') return name+': '+obj+' ['+type+']';
     2030                if (obj.getTime) return name+': '+obj.toLocaleString()+' [Date]';
     2031                var result = [];
     2032                result.push (name+': object');
     2033                for (var tag in obj) {
     2034                        result.push (this.text(obj[tag], name+'.'+tag));
     2035                }
     2036                return result.join('\n');
     2037        },
     2038
     2039        alertObject: function (obj, name) {
     2040                if (name==null) name='*';
     2041                alert (this.text(obj, name));
     2042        }
     2043};
     2044
     2045//--------------------------------------------------------------------------------
     2046//      $Id: time_domain.js,v 1.58 2013/08/13 17:48:45 wolf Exp wolf $
     2047//--------------------------------------------------------------------------------
  • src/org/openstreetmap/josm/data/validation/OsmValidator.java

    diff --git a/src/org/openstreetmap/josm/data/validation/OsmValidator.java b/src/org/openstreetmap/josm/data/validation/OsmValidator.java
    index c1a7a76..9dc1192 100644
    a b import org.openstreetmap.josm.data.validation.tests.MultipolygonTest;  
    3838import org.openstreetmap.josm.data.validation.tests.NameMismatch;
    3939import org.openstreetmap.josm.data.validation.tests.NodesDuplicatingWayTags;
    4040import org.openstreetmap.josm.data.validation.tests.NodesWithSameName;
     41import org.openstreetmap.josm.data.validation.tests.OpeningHourTest;
    4142import org.openstreetmap.josm.data.validation.tests.OverlappingAreas;
    4243import org.openstreetmap.josm.data.validation.tests.OverlappingWays;
    4344import org.openstreetmap.josm.data.validation.tests.PowerLines;
    public class OsmValidator implements LayerChangeListener {  
    113114        Addresses.class, // ID 2601 .. 2699
    114115        Highways.class, // ID 2701 .. 2799
    115116        BarriersEntrances.class, // ID 2801 .. 2899
     117        OpeningHourTest.class // 2901 .. 2999
    116118    };
    117119
    118120    /**
  • new file src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java

    diff --git a/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java b/src/org/openstreetmap/josm/data/validation/tests/OpeningHourTest.java
    new file mode 100644
    index 0000000..6beafde
    - +  
     1package org.openstreetmap.josm.data.validation.tests;
     2
     3import org.openstreetmap.josm.data.osm.Node;
     4import org.openstreetmap.josm.data.osm.OsmPrimitive;
     5import org.openstreetmap.josm.data.osm.Relation;
     6import org.openstreetmap.josm.data.osm.Way;
     7import org.openstreetmap.josm.data.validation.Severity;
     8import org.openstreetmap.josm.data.validation.Test;
     9import org.openstreetmap.josm.data.validation.TestError;
     10import org.openstreetmap.josm.io.MirroredInputStream;
     11import sun.org.mozilla.javascript.NativeArray;
     12
     13import javax.script.Invocable;
     14import javax.script.ScriptEngine;
     15import javax.script.ScriptEngineManager;
     16import javax.script.ScriptException;
     17import java.io.InputStreamReader;
     18import java.text.SimpleDateFormat;
     19import java.util.ArrayList;
     20import java.util.Arrays;
     21import java.util.Collections;
     22import java.util.Date;
     23import java.util.List;
     24import java.util.Map;
     25
     26import static org.openstreetmap.josm.tools.I18n.tr;
     27
     28/**
     29 * Tests the correct usage of the opening hour syntax of the tags
     30 * {@code opening_hours}, {@code collection_times}, {@code service_times} according to
     31 * <a href="http://www.netzwolf.info/en/cartography/osm/time_domain/">time_domain</a>.
     32 *
     33 * @author frsantos
     34 */
     35public class OpeningHourTest extends Test {
     36
     37    public static final ScriptEngine ENGINE = new ScriptEngineManager().getEngineByName("rhino");
     38
     39    /**
     40     * Constructs a new {@code OpeningHourTest}.
     41     */
     42    public OpeningHourTest() {
     43        super(tr("Opening hours syntax"),
     44                tr("This plugin checks for correct usage of opening hours syntax."));
     45    }
     46
     47    @Override
     48    public void initialize() throws Exception {
     49        super.initialize();
     50        ENGINE.eval(new InputStreamReader(new MirroredInputStream("resource://data/time_domain.js")));
     51    }
     52
     53    @SuppressWarnings("unchecked")
     54    protected Map<String, Object> parse(String value, Date reference) throws ScriptException, NoSuchMethodException {
     55        final Object timeDomain = ENGINE.get("TimeDomain");
     56        return (Map<String, Object>) ((Invocable) ENGINE).invokeMethod(
     57                timeDomain,
     58                "evaluateInTime", value, new SimpleDateFormat("yyyy-MM-dd HH:mm").format(reference));
     59
     60    }
     61
     62    protected List<Object> getList(Object obj) {
     63        if (obj == null) {
     64            return Arrays.asList();
     65        } else if (obj instanceof NativeArray) {
     66            return Arrays.asList(((NativeArray) obj).toArray(new Object[((NativeArray) obj).size()]));
     67        } else {
     68            throw new IllegalArgumentException();
     69        }
     70    }
     71
     72    /**
     73     * Checks for a correct usage of the opening hour syntax of the {@code value} given according to
     74     * <a href="http://www.netzwolf.info/en/cartography/osm/time_domain/">time_domain</a> and returns a list containing
     75     * validation errors or an empty list. Null values result in an empty list.
     76     * @param value the opening hour value to be checked.
     77     * @return a list of {@link TestError} or an empty list
     78     */
     79    public List<TestError> checkOpeningHourSyntax(final String value) {
     80        if (value == null || value.trim().isEmpty()) {
     81            return Collections.emptyList();
     82        }
     83        try {
     84            final Map<String, Object> r = parse(value, new Date());
     85            final List<TestError> errors = new ArrayList<TestError>();
     86            for (final Object i : getList(r.get("errors"))) {
     87                errors.add(new TestError(this, Severity.ERROR, i.toString(), 2901, Collections.<OsmPrimitive>emptyList()));
     88            }
     89            for (final Object i : getList(r.get("warnings"))) {
     90                errors.add(new TestError(this, Severity.WARNING, i.toString(), 2901, Collections.<OsmPrimitive>emptyList()));
     91            }
     92            return errors;
     93        } catch (final Exception ex) {
     94            throw new RuntimeException(ex);
     95        }
     96    }
     97
     98    protected void check(final OsmPrimitive p, final String tagValue) {
     99        for (TestError e : checkOpeningHourSyntax(tagValue)) {
     100            e.setPrimitives(Collections.singletonList(p));
     101            errors.add(e);
     102        }
     103    }
     104
     105    protected void check(final OsmPrimitive p) {
     106        check(p, p.get("opening_hours"));
     107        check(p, p.get("collection_times"));
     108        check(p, p.get("service_times"));
     109    }
     110
     111    @Override
     112    public void visit(final Node n) {
     113        check(n);
     114    }
     115
     116    @Override
     117    public void visit(final Relation r) {
     118        check(r);
     119    }
     120
     121    @Override
     122    public void visit(final Way w) {
     123        check(w);
     124    }
     125}
  • new file test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java

    diff --git a/test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java b/test/unit/org/openstreetmap/josm/data/validation/tests/OpeningHourTestTest.java
    new file mode 100644
    index 0000000..4933a1c
    - +  
     1package org.openstreetmap.josm.data.validation.tests;
     2
     3import org.junit.Before;
     4import org.junit.Test;
     5import org.openstreetmap.josm.Main;
     6import org.openstreetmap.josm.data.Preferences;
     7import org.openstreetmap.josm.data.validation.Severity;
     8import org.openstreetmap.josm.data.validation.TestError;
     9
     10import java.util.List;
     11
     12import static org.hamcrest.CoreMatchers.is;
     13import static org.junit.Assert.assertThat;
     14
     15public class OpeningHourTestTest {
     16
     17    private static final OpeningHourTest OPENING_HOUR_TEST = new OpeningHourTest();
     18
     19    @Before
     20    public void setUp() throws Exception {
     21        Main.pref = new Preferences();
     22        OPENING_HOUR_TEST.initialize();
     23    }
     24
     25    @Test
     26    public void testCheckOpeningHourSyntax1() throws Exception {
     27        assertThat(OPENING_HOUR_TEST.checkOpeningHourSyntax("Mo-Fr 8:00-16:00").isEmpty(), is(true));
     28        OPENING_HOUR_TEST.checkOpeningHourSyntax("Mo-Tue");
     29    }
     30
     31    @Test
     32    public void testCheckOpeningHourSyntax2() throws Exception {
     33        final List<TestError> errors = OPENING_HOUR_TEST.checkOpeningHourSyntax("Mo-Tue");
     34        assertThat(errors.size(), is(2));
     35        assertThat(errors.get(0).getMessage(), is("Please use the abbreviation \"Tu\" for \"Tue\"."));
     36        assertThat(errors.get(0).getSeverity(), is(Severity.ERROR));
     37        assertThat(errors.get(1).getMessage(), is("No time specification, assuming 0:00-24:00 at \"Mo - Tue ««»»\""));
     38        assertThat(errors.get(1).getSeverity(), is(Severity.WARNING));
     39    }
     40
     41    @Test
     42    public void testCheckOpeningHourSyntax3() throws Exception {
     43        final List<TestError> errors = OPENING_HOUR_TEST.checkOpeningHourSyntax("Sa-Su 10.00-20.00");
     44        assertThat(errors.size(), is(1));
     45        assertThat(errors.get(0).getMessage(), is("Please use \":\" as hour/minute-separator at 10.00, 20.00"));
     46        assertThat(errors.get(0).getSeverity(), is(Severity.ERROR));
     47    }
     48
     49    @Test
     50    public void testCheckOpeningHourSyntax4() throws Exception {
     51        assertThat(OPENING_HOUR_TEST.checkOpeningHourSyntax(null).isEmpty(), is(true));
     52        assertThat(OPENING_HOUR_TEST.checkOpeningHourSyntax("").isEmpty(), is(true));
     53        assertThat(OPENING_HOUR_TEST.checkOpeningHourSyntax(" ").isEmpty(), is(true));
     54    }
     55}