XSL Date & Time Library
XSL Date & Time Library is an XSL stylesheet which provides various date and time functions for use within your own XSL stylesheets.
Downloads for the software described here are available on the downloads page.
UPDATE (26-Jan-2008): Version 1.09 is now released. This version has tons of bugfixes and new features over the 1.01 release, including an important bug fix to
iso-from-unix which only occurs in the last few days of each year, a bug fix to
dst-adjust and the ability to generate RSS-compliant dates from UNIX or ISO timestamps, plus new regression tests. I’ve also added information in the article about how to use the lookup tables.
In July 2006 I was working on a new show scheduling system for our radio station, Deviant Audio. The system is XML-based and uses a source XML file storing a list of single event broadcasts and recurring event broadcasts (such as regular radio shows). This is then transformed with XSL into a filtered list of forthcoming shows, either for a specific “content authority” (the person responsible for controlling a particular broadcast segment) to provide DJs and administrators with a list of forthcoming broadcasts they control; or all shows coming up until a certain date (used to display show schedules on the web site and in our chat room).
We have DJs in various timezones and so to make direct XML source editing easy, broadcasts can be specified in any timezone. Recurring events have associated metadata describing how often the event recurs (which may be a fixed number of days or a non-constant time span such as the first Saturday of every month). The times and dates in the source XML are always in local time, however the show automation system always works in UTC so the transformed times must be adjusted for Daylight Savings (DST). The DST rules used in Europe and US/Canada are different, so to ensure DJs who broadcast at “8pm local time” are always broadcasting at 8pm in their timezone – and not 7pm or 9pm – DST offsets must be calculated for each broadcast depending on the region that broadcast originates from (so show times in different regions will shift at slightly different times of the year). In addition, the US is extending its DST period from 2007-2015, so new rules are in effect from 2007 henceforth.
XSL is unaware of time and has no native date or time functions. EXSLT is a non-portable library of extensions for XSL implemented by some XSLT processors, which provides date and time functions. However, some XSLT processors – including versions of PHP 5 linked against libxslt – do not support all EXSLT extensions, in particular libxslt at the time of writing does not support all the date-time functions in EXSLT.
I have therefore created an XSL date-time library, which you can view or download above. The library is implemented as a series of first-order XPath functions created with the EXSLT/func extension namespace (which is fortunately supported by libxslt). To use:
- Define the namespace www.intelligentstreaming.com/xsl/date-time in your code (the library uses “is-date” as the namespace prefix):
<xsl:stylesheet .... xmlns:is-date="http://www.intelligentstreaming.com/xsl/date-time">
- Import the library into your stylesheet:
<xsl:import href="date-time.xsl" />
- Call the desired function from any XPath expression, eg:
<xsl:value-of select="is-date:unix-from-iso('2006-07-19T00:46:00+02:00')" />
The functions have been regression tested against PHP 5.1.4′s and PHP 5.2.4′s date-time functions including leap year, millennium and DST switchover edge cases using the supplied PHP test code. You can read more about my PHP test chassis and how to use it in XSL if you’re interested.
I have documented the code with JavaDoc-style comments so it should be fairly self-explanatory, but here is a quick summary of the functions provided:
unix-from-iso (v1.00) – converts an ISO8601-formatted date to a UNIX timestamp
iso-from-unix (v1.00) – converts a UNIX timestamp to an ISO8601-formatted date (with +00:00 timezone modifier)
day-of-week (v1.00) – calculates the day of the week (Mon-Sun) from a year, month and date (useful for creating calendar displays)
day-of-week-from-iso (v1.09) – same as
day-of-week but takes an ISO timestamp instead of year, month, date numbers. The timezone is ignored.
day-of-week-utc-from-iso (v1.09) – same as
day-of-week but takes an ISO timestamp instead of year, month, date number. The day is adjusted to the day in UTC time before calculating the day of the week (ie. if the supplied timestamp is 2008-01-25T23:00:00-05:00 – a Friday in EST – the function will return Saturday, since it is 4am in UTC). This is handy for when you need everything to be localised to UTC regardless of the timezones you’re supplying.
date-of-first-day (v1.00) – calculates the day (Mon-Sun) of the first day (1st) in the month, given the day (Mon-Sun) of 1st of the month (you can use this to calculate the first Tuesday etc. in the month; by adding 7, 14 or 21 to the result you can calculate the 2nd, 3rd and 4th Tuesdays etc. in the month)
date-of-last-day (v1.00) – calculates the day (Mon-Sun) of the last day (28th-31st) in the month, given the day (Mon-Sun) of the last day of the month and the date (28th-31st) of the last day in the month (you can use this to calculate the last Tuesday etc. in the month; by subtracting 7, 14 or 21 from the result you can calculate the 2nd, 3rd and 4th last Tuesdays etc. in the month)
dst-offset (v1.00) – calculates in seconds the DST offset of a supplied local time in ISO format, given the DST ruleset to use (European or American). For example in summertime date-times the offset will be 3600 seconds (1 hour), and in wintertime date-times it will be 0 (zero) (you can subtract the result from a local time to deal with a single generic DST-adjusted timezone throughout the year, eg. “Eastern Time” rather than EST and EDT; if you further add the timezone modifier (eg. +5 hours for ET) to a date-time after subtracting the DST offset, you get a time in UTC that is correct regardless of the time of year or DST rules in force in the region of the original local time)
dst-adjust (v1.01) – takes a (UTC) UNIX timestamp and DST ruleset and adjusts the time for DST in the specified region. If the specified ruleset indicates summertime for the region, typically 1 hour will be subtracted from the timestamp, otherwise it will be returned unchanged. This is useful when you must do time manipulation in UTC but ensure that times spread over a year are consistent with the DST rules in the region specified. For example if you want to know when 8pm Mountain is every day of the year (UTC-7 in wintertime, UTC-6 in summertime), you must subtract 1 hour from the UTC time during summertime (8pm MT is 3am UTC in wintertime and 2am UTC in summertime). This function makes just such an adjustment.
dst-adjust-from-iso (v1.09) – same as
dst-adjust but takes an ISO timestamp instead of a UNIX timestamp
add-interval (v1.01) – adds a number of possibly uneven time intervals to an ISO timestamp. Specifying a number as the interval length adds the specified number of days; specifying a number followed by ‘md’ specifies a number of months, however the relative day of the month will be kept the same. For example if the supplied timestamp falls on the 2nd Saturday of the month, an interval of 1md will return the date-time for the 2nd Saturday of the following month. Behaviour on 29th, 30th and 31st of months with more than 28 days is undefined (and not included in the regression tests).
Breaking change in v1.09:
add-interval now returns an ISO timestamp with the original timezone preserved instead of a UNIX timestamp. Explanation from ChangeLog: This is because adding a monthly period then converting to UNIX causes the timezone information to be lost: a 1st Tuesday of the month could become a 2nd Tuesday of the month if
add-interval is called again when the first Tuesday was the 7th in the local timezone but after midnight on 8th in UTC.
week-number-in-month-from-iso (v1.09) – calculates the week number in the given month from an ISO timestamp. The timezone is ignored.
week-number-in-month-utc-from-iso (v1.09) – calculates the week number in the given month from an ISO timestamp. The timestamp is converted to UTC first.
time-ampm (v1.09) – convert a 24-hour time into 12-hour am/pm format. Takes a 4-digit argument 0000-2359 as the input, outputs eg. “12:47pm” for “1247″ or “3:01am” for “0301″. Outputs “midnight” for “0000″ and “midday” for “1200″.
time-ampm-from-iso (v1.09) – same as
time-ampm but takes an ISO timestamp as the input.
rfc822-from-iso (v1.09) – takes an ISO timestamp and converts it into an RFC-822 timestamp, suitable for the
<pubDate> attribute in RSS feed generation. Outputs a timestamp such as “Thu, 24 Jan 2008 16:12:28 GMT”.
The library also has a number of look-up tables you can use to convert numerical date-time information into English text. These tables are:
days – days of the week starting at Sunday (Sunday, Monday…) and 3-digit days (Sun, Mon…)
months – months of the year (January, February…)
abbr-months – 3-letter months of the year (Jan, Feb…)
dates – dates of the month with ordinator (1st, 2nd, 3rd…)
dstlesstz – list of timezones (PST, MST, CST…)
For example, to get the name of the month from an ISO timestamp:
<xsl:value-of select="$is-date:months[@month=substring($yourTimestamp, 6, 2)]/@name" />
This selects the 6th and 7th characters of
$yourTimestamp – which correspond to the month number from 01 to 12 – then finds the element in the
months table which matches, and prints its
If you want to group a lot of date-indexed information by day, for example at our radio station, displaying all Monday’s shows, then all Tuesday’s shows etc., you can do something like this:
<xsl:for-each select="$is-date:days"> <xsl:variable name="currentDay" select="@day" /> <xsl:for-each select="$dataToGroup[is-date:day-of-week-from-iso(@date) = $currentDay]"> /* Put your processing code here */ </xsl:for-each> </xsl:for-each>
This assumes that
$dataToGroup contains the data you want to group, one element per item, with an attribute called
date which contains an ISO timestamp.
I hope you find the code useful!
Please send feedback via the contact page or leave a comment below.