period.py

This is an early version of a basic time period checking libary for Python. It is in part inspired by perl's Time::Period module and the time class mechanism of Cfengine.

There was a major change at version 0.5 in how the code works. Previous to that you couldn't do any grouping. In order to add grouping, I had to completely change how the parser and the logic of the system works. All tests show that both versions give the same answers, at least for the test cases.

The purpose of this library is simply to allow you to specify a time period and check to see if some time is within that time period. By default, it checks against the current computer time. You can also check to see if a time is on a holiday.

Time Period Specifications

The time period specifications are reasonably straightforward, but there are a few tricky bits. It understands the following time units:

Now if you had some Python script you didn't ever want to run on a weekend, you could do this:

from period import in_period
import sys

if in_period('Weekend'):
    sys.exit(0)       # Only run on weekdays!

You can negate a time unit with a bang (!), so you could also say this, a bit cryptically:

from period import in_period
import sys

if in_period('!Weekday'):
    sys.exit(0)       # Don't run on weekends!

You can combine time units into more complex period specifications with a dot (it's confusing to call it a "period" in this context). In this case, everything in the period specification must be true for in_period to return true. So, April.Weekend will be true for every weekend in April.

In addition to these simple period units, you can express ranges of times for the ones that use digits. Ranges are indicated with a '-' dash. So, Hr07-Hr21 is true from 7am to 9pm. You can easily specify your normal working hours, or at something least very close to them: Weekday.Hr07-Hr16.

Please note very carefully: time ranges are inclusive. So, Hr01-Hr03 includes all of 1:00-3:59. If you only want to specify 1:00 to 3:00, then you need instead to say Hr01-Hr02 which includes 1:00-2:59.

This is inclusive range behavior is true for the minute, day, year and week ranges as well.

You can also use ranges for days of the week and months. These will wrap around, so if you do November-March, you'll get what you expect. Same for day ranges: Friday-Tuesday will match on Friday, Saturday, Sunday, Monday and Tuesday.

If you want, you can omit the unit part from the second part of a range. So, Hr07-Hr16 and Hr07-16 are effectively the same.

If you want leave your lunch hour out of the period above you have to use a slightly different notation. If you said Weekday.Hr07-12.Hr13-16 this will fail, because one or the other of the Hr periods will fail. So, to indicate several ranges of these, you use a comma. The correct period is Weekday.Hr07-11,13-16. Note that Hr12 would everything between 12:00 and 12:59, so we need to get that entire hour out of the time spec. Hr07-11,12-16 is exactly the same as Hr07-16.

In addition to logical "and" you can join periods with "or" with the | character (that's a pipe): Weekday|Weekend.Hr10-22 will be true if it's a weekday or between 10am and 10pm on a weekend. Note that the "and" operation (the dot) has higher precedence than the "or" (the pipe).

Finally, you may do some grouping with parentheses, which work as you'd expect: (Monday|Friday).Hr11-14 or more complex expressions with nested sub-expressions: (Monday|(Friday.January-March)).Yr2002 You can also negate groups by prefixing the bang: !(Monday|Friday).Hr11-14.

If you wish to check some time other than the current computer time, just pass that time as a second argument to in_period:

from period import in_period
import sys, time

# 86400 == 60 * 60 * 24, a day of seconds
if in_period('Weekend', time.time() + 86400):
    sys.exit(0)       # Don't run if tomorrow is a weekend.

The time specifications Always and Never always return true and false respectively.

Holidays

There is another function in the period library, is_holiday. This will attempt to consult a SYSV accounting holidays file. By default, it checks in /etc/acct/holidays, but you can specify other locations.

The format of the holidays file is fairly strict. All comment lines must begin with a star, *. The first non-comment line must be the year, prime-time start and prime-time end. All following non-comment lines take the format "month/day name-of-holiday" where only the single month/day token is significant. So, here's our example:

* BCG Holidays
*
* Curr	Prime	Non-Prime
* Year	Start	Start
*
  2002	0700	1800
*
* only the first column (month/day) is significiant.
*
* month/day	Holiday name or comment.
*
1/1		New Years Day
* 1/2             A holiday for testing.
1/15		Martin Luther King, Jr. Day
7/4		Independence Day
11/24		Thanksgiving Day
12/25		Christmas

If you happen to have turned on SYSV accounting on a system, it will start griping several days before the new year to remind you to update the holidays file. So, if you aren't using SYSV accounting, that file may be several years out of date. The is_holiday function will ignore an out of date holidays file, and will always report that a day is not a holiday until the file is fixed.

If you don't use a system that has SYSV accounting, you can specify the location of a central holidays file in that format just fine:

from period import is_holiday, in_period

if in_period('Weekend') or is_holiday(holidays="/export/etc/holidays"):
    sys.exit(0)       

You can also specify some other time to check:

from period import in_period
import sys, time

# 86400 == 60 * 60 * 24, a day of seconds
tomorrow = time.time() + 86400
if in_period('Weekend', tomorrow) or is_holiday(now=tomorrow):
    sys.exit(0)     # Don't run if tomorrow is a weekend or a holiday.

BUGS

Everything is case sensitive.

Doesn't use distutils to install.

Send questions and problems to William S. Annis.