Parsing time ranges with PyParsing
1. Bugs


Supposing that I want to specify an interval of time spanning midnight, for example starting at 23:00 on Monday and ending at 01:00 on Tuesday, how do I do it? The best I can do seems to be:

2300-2359 (mon); 0000-0100 (tue)


but this leaves the minute between 23:59 and midnight on Monday uncovered! One way to fix this would be to allow the input "2400" to represent the midnight at the end of the day.
datetime.now() is called several times during the validation. This could lead to an incorrect result if the code is called at the wrong moment. For example, suppose that my specification is:

1200-1300 (mon); 1100-1200 (mon)


and the function is called just before 12:00 on Monday. When the first window is checked, datetime.now() returns 11:59:59.999, and so the window does not match. But when the second window is checked, datetime.now() returns 12:00:00.001 and so the second window does not match either! But these two windows ought to cover the whole interval from 11:00 to 13:00 on Monday.

If you were to fix the midnight problem in §1.1 above, then there would be a similar race condition in the day handling logic.

So it is necessary to call datetime.now() exactly once and remember the result. Or better still, change the function so that it takes the time to be tested as a parameter.
If the day specification consists of a single day then the code tests for a matching day like this:

datetime.now().strftime('%a').lower() == day[0]


However, strftime is affected by the current locale:

&gt;&gt;&gt; import datetime, locale
&gt;&gt;&gt; locale.setlocale(locale.LC_TIME, 'fr_FR')
&gt;&gt;&gt; datetime.datetime.now().strftime('%a')
'Sam'


so this will go wrong if the locale is not English. It would be more reliable to use:

now.weekday() == days.index(day)



2. Review


The Python style guide (PEP8) recommends four spaces per indentation level, and a maximum of 79 characters per line. You're not obliged to follow this guide, but it'll make it easier to collaborate with other Python programmers if you do. (For example, we wouldn't have to scroll the code horizontally to read it here at Code Review.)
In the pp.delimitedList call, the keyword argument delim defaults to ',' so this can be omitted in the definition of dayspec.
The ParserElement class has an abbreviated syntax for the setResultsName method:


  You can also set results names using the abbreviated syntax, expr("name") in place of expr.setResultsName("name")


Using this would help make the grammar more readable.
The correspondence between grammatical elements and names is inexact. It seems to me that it would make more sense for the hour element to be named 'hour', like this:

hour = pp.Regex('[01]\d|2[0-3]')('hour')
minute = pp.Regex('[0-5]\d')('minute')
time = hour + minute

Assigning a few more grammar elements would make things clearer and help to keep the code within 79 columns, for example:

windows = pp.delimitedList(pp.Group(window), delim=';')
spec = pp.StringStart() + windows + pp.StringEnd()