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
-
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.
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.
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
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
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))
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))
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) ))
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
- DSL Writeup
- Git Clone
-
git://github.com/jimweirich/texp.git
- Issues/Bugs