Calendar Customization

The photo calendar program has a built in database of date notations. You can customize your calendars by extending the database to include your own date notations.

Notation Database

The notation database has four component files that are automatically loaded by the calendar page. An examination of these files will give you a feel for how the notation database is constructed. Date specifications have a flexible syntax that is also very readable. The database constructors are explained in detail in a subsequent section.


The file holidays.js contains dates for government and secular holidays. There are examples of fixed dates (e.g. "1 April" ), positional dates (e.g. "2nd Sunday in May") and relative dates (e.g. "Tuesday on_or_after 2 November").


The file astronomical.js contains precalculated dates for events tied to the motion of celestial bodies. Note that the dates are specific to a particular time zone.


The file religious.js contains dates for selected religious holidays. It includes a good example of a table of dates for an event that does not have a simple recurring date. It also has examples of dates relative to another date.


The file personal.js is a place where you can insert personal dates such as birthdays and anniversaries. The example given can be easily modified.

Notation Plugins

In addition to the standard notations, the calendar page can accept runtime plugins by specifying one or more file names as query parameters in the URL. For example, the URL "calendar.html?plugin=friends.js,family.js" will load two plugin files named friends.js and family.js. This provides a mechanism to customize the notation database in different ways on each activation of the calendar.html page.

Calander Selection Page

The selection.html page is a demonstration of how to use the plugin mechanism to customize your calendar notations. It dynamically builds a list of calendar selections from a database specified in the selection.js file. Take a moment to look at the file contents. Each call to add_selection adds another calendar selection to the page.

add_selection( "label", "file.js", ... )

The arguments specify a label for the selection followed by one or more notation plugin files. When you click on the corresponding label in the selection.html page, the calendar.html page is activated with the plugins that you specified. It is a simple matter to customize the selection.js file for any notation plugins that you build yourself.

The selection page also performs another function that is not readily apparent. The enable/disable state for the notation database is maintained in a browser cookie for the duration of the current browser session. When you modify the contents of the notation database by using a different set of plugins, the old state values are no longer valid. The selection page corrects this by removing the old browser cookie before activating the calendar page. For this reason, I recommend using the selection page to activate the calendar page whenever you are switching from one set of plugins to another.

Notation Database Construction

If you have made it this far you probably understand enough about software to construct your own notation plugins. The notation database is built by calls to two constructor functions.

Notation.add_date( "date spec", "label", enable/disable )

The add_date constructor adds a new notation to the database. The first argument is a date specification string (see Date Specification Syntax). The second argument is the label that appears on the calendar. The third argument is the initial enable/disable state for the notation. Notations are enabled by default so you can omit this argument in most cases. Specify Notation.Disable if you want the notation initially disabled. This is useful for optional notations that the user can select as desired. The calendar date cells become crowded if too many notations fall on the same day.

Notation.append( "date spec" )

The append constructor is used to construct a table of notations. Use the add_date constructor for the first table entry followed by the append constructor for each subsequent table entry.

A table is used to enter date specifications that do not follow a regular pattern for the day/month/year of occurrence. All the entries have a common label and share the same enable/disable state. During date resolution the table is searched sequentially until a date matching the current month and year is found. A table behaves as if the entries were connected by logical "or" clauses.

Date Specification Syntax

The general form of a date specification is "Day Month Year". Each of these components is given a detailed explanation in the sections that follow. Because the Day specification has many variations, we will save it for last. Don't be intimidated by the apparent complexity. The most common usage is covered by the simplest forms. Study examples in the notation database files to see typical usage.


The year can be specified with either a global selector or an explicit selector.

Global Year Selectors
yearly matches all years
This is the default when no year is specified.
even matches even years
odd matches odd years
leap matches leap years

Explicit Year Selectors
YYYY matches the specified year where YYYY is a number
example: 2004
XXXX thru YYYY matches the specified range of years
example: 2004 thru 2008
N mod M matches all  N = year mod M where N and M are integers
example: 4 mod 5  matches   2004 and 2009 and 2014 ...
A and B concatenate any of the explicit year selectors
example: 2004 and 2006 thru 2008 and 2010

The majority of date specifications will use the yearly selector. Since this is the default, a year specification is rarely necessary. The explicit year selectors are mainly used in tables where the day and/or month components change from one year to the next. Combinations of the mod selector can generate any simply repeating pattern of years.

A specification like "XXXX thru YYYY thru ZZZZ" is syntactically incorrect. The parsing algorithm automatically collapses it to the equivalent "XXXX thru ZZZZ".


The month can be specified with either a global selector or an explicit selector.

Global Month Selector
monthly matches all months
This is the default when no month is specified.

Explicit Month Selectors
MMM matches the specified month where MMM is
a month name: January ... December
or its three letter abbreviation: Jan ... Dec
MMM thru NNN matches the specified range of months
example: May thru August
A and B concatenate any of the explicit month selectors
example: Dec thru Feb and June

Explicit single month specifications seem to handle all cases of practical interest. The other options are there if you need them. A range specification treats the months cyclically with January as the successor to December.


The day can be specified with either a global selector, an explicit selector, a positional selector or a relative selector. There is no default day selector. You must supply a value.

Global Day Selector
daily selects all days in the month

Explicit Day Selectors
DD selects the specified day where DD is a number
example: 14
DD thru EE selects the specified range of days
example: 14 thru 18
N mod M selects all  N = day mod M where N and M are integers
example: 4 mod 7  selects   4 and 11 and 18 and 25
A and B concatenate any of the explicit day selectors
example: 4 and 6 and 8 thru 10

Positional Day Selectors
Nth DAY in selects a day by name and position in the month
where Nth is first, second, third, fourth, last
or the abbreviation 1st, 2nd, 3rd, 4th
and DAY is a day name: Sunday ... Saturday
or its three letter abbreviation: Sun ... Sat
example: 4th Wednesday in
last day in selects the last day in the month

The preceeding day selectors are terminal elements. A relative day selector is a recursive element. It specifies a day relative to another day. The process repeats until a terminal element is reached.

Relative Day Selectors
DAY before XX
DAY after XX
DAY on_or_before XX
DAY on_or_after XX
selects the specified day relative to another day
where DAY is a day name: Sunday ... Saturday
or its three letter abbreviation: Sun ... Sat
weekday before XX
weekday after XX
weekday on_or_before XX
weekday on_or_after XX
selects a Monday ... Friday relative to another day
The reference day XX may be any of the following.
DD an explicit day where DD is a number (terminal)
Nth DAY in
last day in
a positional day selector (terminal)
DAY before XX
DAY after XX
weekday before XX
weekday after XX
a relative day selector (recursive reference)
LABEL a reference to another date (recursive or terminal)
Wednesday before last Saturday in April
weekday on_or_after 15 April
Sunday after Labor Day

The last form of the relative day selector must specify the label of a previously defined notation. Due to the way that tokens are parsed, the label cannot have any redundant blanks (i.e. "Extra   Spaces"). You only need to specify enough of the target label to insure a correct match (i.e. "Big Long" will match "Big Long Rambling Label").

A relative date reference has additional peculiarities that require special handling. Notice that only the Day field is present in the date specification. The month and year must be determined from the referenced date. This brings up the topic of Date Resolution, which is covered in the next section.

Date Resolution

When the user selects a new year or month, the notation database is searched to find all notations that are applicable. First the year specification is checked for a match to the current year. Next the month specification is checked for a match to the current month. A notation that passes both tests is added to the list of current selections. The final step is to resolve the day specification and create an array of notations that are applicable for each day of the month. A resolved day that falls outside of the current month is ignored. This can only occur for relative day specifications. For example, Wednesday before 5 August could fall outside the month of August! You should beware of a relative day specification that falls near a monthly boundary. A positional day specification is preferable because it will always resolve to a day within the current month.

A relative day specification that references another date does not have its own year and month specification. To determine whether the notation is applicable for the current month and year it is necessary to first resolve the day reference. This means extra processing overhead when scanning through the notation database. However, a relative date reference that crosses a monthly boundary will be correctly resolved. For example, Sunday before Easter could fall in either March or April depending on the date of Easter for the current year. The date will be fully resolved against the current date for Easter and the notation will appear on whichever month is applicable. Relative date references are particularly useful when referring to a date that is defined by a table of values.

Note that a referenced date cannot have a compound month specification (e.g. March thru May). The date resolution algorithm is not capable of examining multiple choices for the month selection. For a compound day specification, only the first day is resolved. The others are ignored. The case of multiple year choices is handled by selecting the current year. However, as a consequence of this simplification, a relative date reference can never be selected across a year boundary. The resolved year will never match the current year. To do this successfully, the algorithm would have to be smart enough to examine either the preceding or following year selection. My design choice was to keep the date resolution procedure simple and not pursue rarely needed features. It is capable of following recursive date references, that is, a reference to a date that references yet another date.

Additional Information

For further details on the internal organization of the calendar program, refer to the software design page.