
                                   period.py
                                       
   This is an early version of a basic time period checking libary for
   Python. It is in part inspired by perl's [1]Time::Period module and
   the time class mechanism of [2]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 [3]holiday.
   
  Time Period Specifications
  
   The time period specifications are reasonably straightforward, but
   there are a few tricky bits. It understands the following time units:
     * Hr00 - Hr23
     * Min00 - Min59
     * Yr1999, Yr2000, etc
     * January, February, etc. and ranges February-September
     * Monday, Tuesday, etc. and ranges Wednesday-Saturday
     * Day01 - Day31
     * Week00 - Week52
     * Weekday, Weekend
     * Always, Never
       
   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 [4]William S. Annis.

References

   1. http://www.perl.com/CPAN-local/modules/by-module/Time/
   2. http://www.iu.hio.no/cfengine/
   3. file://localhost/u/u01/a/annis/code/python/lib/period/period.html#holidays
   4. mailto:annis@biostat.wisc.edu
