Skip to content

jimweirich/texp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Temporal Expressions for Ruby

TExp is a temporal expression library for Ruby with a modular, extensible expression serialization language.

Temporal expressions are a way of concisely expressing recuring events. For example, Christmas falls on the 25th of December every year. A temporal expression that represented the recurring event called “Christmas” would return true for any date that falls on the 25th of December.

dec25 = Date.parse("Dec 25, 2008")
christmas_texp.includes?(dec25)      # => true
christmas_texp.includes?(dec25 + 1)  # => false

Temporal expressions can be combined using operators. For example, if I have a temporal expression for Christmas and one for Thanksgiving, I can combine them into a single holiday expression thusly:

holidays_texp = christmas_texp + thanksgiving_texp

TExp Features

  • Basic Expressions for:

    • Day of Week (see DayOfWeek)

    • Day of Month (see DayOfMonth)

    • Week of Month (See Week)

    • Specific Month (See Month)

    • Specific Year (See Year)

  • Composite Expressions for:

    • Logical AND (date must be included by all sub-expressions)

    • Logical OR (date is included by any sub-expression)

    • Logical NOT (include any date not included in the sub-expression)

    • Windows (specify a windows of days around any date matching the sub-expression)

  • Human readable descriptions via inspect

  • Compact, parsable encoding via to_s

  • User expandable with custom temporal expression types, that interoperates with the compact, parsable encoding.

  • A DSL that allows the easy construction of Temporal Expressions.

Examples

The examples below use the TExp DSL for easy creation of temporal expressions. The DSL methods are not globally available by default. To use them, do one of the following:

  • Use the global texp method with a block:

    te = texp { day(1) * month("Jan") }
    
  • Include the TExp::DSL module into your class:

    include TExp::DSL
    te = day(1) * month("Jan")
    

See the TExp::DSL module for a complete description of all the DSL commands.

Match any Monday

te = dow(:monday)
te.includes?(Date.parse("Mar 3, 2008"))    # => true
te.includes?(Date.parse("Mar 4, 2008"))    # => false
te.includes?(Date.parse("Mar 10, 2008"))   # => true

# Equivalent to:
te = TExp::DayOfWeek.new(Date::DAYNAMES.index("Monday"))

# Variants:
dow("Mon", "Tues")     # on Monday or Tuesday

Match any date in March

te = month(:march)
te.includes?(Date.parse("Mar 1, 2008"))    # => true
te.includes?(Date.parse("Mar 31, 2008"))   # => true
te.includes?(Date.parse("Mar 15, 1995"))   # => true
te.includes?(Date.parse("Feb 28, 2008"))   # => false

# Equivalent to:
te = TExp::Month.new(3)

# Variants:
month(1,2,3)         # the month is January, February or March
month("Nov", "Dec")  # the month is November or December

Match Valentine’s day (any year)

If you think of a temporal expression as a set of dates, then we can combine those sets in interesting ways. For example, the “*” operator will create an intersection of two sets of dates, matching only dates that match both expressions.

te = day(14) * month("Feb")

te.includes?(Date.parse("Feb 14, 2008"))  # => true
te.includes?(Date.parse("Feb 14, 2007"))  # => true

# Equivalent to:
te = TExp::And.new(
       TExp::DayOfMonth.new(14),
       TExp::Month.new(2))

Match Valentine’s day in 2008

te = day(14) * month("Feb") * year(2008)

te.includes?(Date.parse("Feb 14, 2008"))  # => true
te.includes?(Date.parse("Feb 14, 2007"))  # => false

# Equivalent to:
te = TExp::And.new(
       TExp::DayOfMonth.new(14),
       TExp::Month.new(2),
       TExp::Year.new(2008))

Match Multiple Days

In addition to intersection, we can also ask for the union of the dates of two temporal expressions. Here we build a temporal expression that will match Christmas or New Year’s day.

christmas = day(25) * month("Dec")
new_years = day(1) * month("Jan")

holidays = christmas + new_years

holidays.includes?(Date.parse("Jan 1, 2008"))   # => true
holidays.includes?(Date.parse("Dec 25, 2008"))  # => true
holidays.includes?(Date.parse("Jan 1, 2009"))   # => true

holidays.includes?(Date.parse("Feb 14, 2008"))  # => false

# Equivalent to:
holidays = TExp::Or.new(
  TExp::And.new( TExp::DayOfMonth.new(25), TExp::Month(12) ),
  TExp::And.new( TExp::DayOfMonth.new(1),  TExp::Month(1)  ))

Excluding Days

Suppose Casual Friday happens every Friday, except for the last Friday of the month. We can represent it like so:

fridays = dow(:fri)
last_friday = week(:last) * dow(:fri)
casual_fridays = fridays - last_friday

casual_fridays.includes?(Date.new(2013, 6, 21))  # => true
casual_fridays.includes?(Date.new(2013, 6, 28))  # => false
Documents

github.com/jimweirich/texp

DSL Writeup

github.com/jimweirich/texp/blob/master/lib/texp/dsl.rb

Git Clone

git://github.com/jimweirich/texp.git

Issues/Bugs

github.com/jimweirich/texp/issues