Chapter 4. Dates and Times
Does anyone really know what time it is? Does anyone really care?
Chicago
Introduction
Native XSLT 1.0 does not know what time it is and does not seem to care. However, dates and times are a necessary aspect of everyday life. The need to manipulate them arises frequently in computing, especially in web development. Therefore, it was surprising and unfortunate that standard XSLT 1.0 did not have any built-in date and time support.
The situation in XSLT 2.0 has improved substantially. XPath 2.0 has
numerous functions for manipulating dates, times, and durations. In
fact, the only thing 2.0 leaves us wanting for, with respect to date
and time, is functions with shorter names! I am told names like
subtract-dates-yielding-yearMonthDuration()
were
provided partly to appease the XQuery database weenies. The
alternation between dashes and camelcase follows from the differing
conventions adopted by XPath and XML Schema committees.
The examples in this section can help compensate for XSLT
1.0’s lack of support for dates and times.
Unfortunately, one of the most crucial date and time capabilities
cannot be implemented in XSLT 1.0—that is, getting the current
date and time. For that, you need to call out to another language
whose library supports interacting with the
hardware’s real-time clock. Both Java and JavaScript
have this capability. If your application just needs to format dates
and times that already exist in a document, then the routines here
should cover most needs. In 2.0, use current-date()
, current-time()
, and
current-dateTime()
.
Doing date and time manipulation and conversion without native support in the language can be tricky, but it is almost purely an exercise in intricate integer arithmetic involving what are essentially base conversions in a mixed radix system. Working with non-Gregorian calendars and determining holidays also requires quite a bit of historical, religious, and cultural knowledge. Readers with no application for date and time routines may wish to skip this chapter because little by way of XSLT technique is unique to these algorithms. Those who are curious about the theory behind these calculations should definitely look at the papers cited in the See Also section, next.
I am grateful to Jason Diamond for graciously contributing many of the templates dealing with Gregorian time. The XSLT code for dealing with non-Gregorian calendars was adapted from Edward M. Reingold’s public domain Lisp implementation. Some of the algorithms were adapted to better suit XSLT and build upon the existing foundation provided by Jason’s code.
Do not be confused by the technique used to pass a date into most templates. It is designed for maximum convenience. You can pass in the date in two ways: by using a string formatted using ISO date-time rules and by using the individual parameters for the year, month, and day. The following example should clarify usage:
<xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="date-time" select="'2002-01-01T01:00:00'"/> </xsl:call-template> <xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="date" select="'2002-01-01'"/> </xsl:call-template> <xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="year" select="2002"/> <xsl:with-param name="month" select="01"/> <xsl:with-param name="day" select="01"/> </xsl:call-template>
Each of the calls evaluates the day of the week for January 1, 2002. The first two variations are convenient when a date is already ISO formatted. The last variation is convenient when the components of the date are stored separately. You can also override parts of an ISO date string as follows:
<xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="date" select="'2002-01-01'"/> <xsl:with-parm name="day" select="25"/> </xsl:call-template>
In all templates, unless otherwise stated, dates are Gregorian by default. This civil calendar system is used by most of the Western world.
See Also
Claus Tøndering’s calendars FAQ (available at http://www.pauahtun.org/CalendarFAQ/cal/calendar24.pdf) contains the illuminating theory behind many calculations in this chapter.
Jason Diamond’s original XSLT implementation can be found at http://xsltsl.sourceforge.net/date-time.html.
Calendrical Calculations (http://emr.cs.iit.edu/~reingold/calendar.ps) by Nachum Dershowitz and Edward M. Reingold provides additional insight. Their paper discusses the non-Gregorian systems covered here.
I do not cover the Chinese or Indian calendars in this chapter because they are more complex. Information about the mathematics of the Chinese and Indian calendars can be found at http://www.math.nus.edu.sg/aslaksen/calendar/chinese.shtml and http://www.math.nus.edu.sg/aslaksen/calendar/indian.shtml, respectively.
If, for some reason, you have an application for the Mayan, French Revolutionary, or Old Hindu calendars, then you should investigate Calendrical Calculations II (http://emr.cs.iit.edu/~reingold/calendar2.ps).
EXSLT.org has a date-time module that provides much of the same functionality as the Gregorian date-time templates implemented in this chapter. They also provide templates for working durations (differences between dates and times).
XForms (http://www.w3.org/MarkUp/Forms/) provides
some date-time functionality. It uses XML Schema (ISO) lexical
representations (with durations subdivided into
dateTime
and yearMonth
flavors). The new functions are: now()
,
days-from-date()
, seconds-from-dateTime()
, seconds()
, and months(
)
.
4.1. Calculating the Day of the Week
Solution
XSLT 1.0
The following calculation does the trick and returns an integer in the range of 0-6, where 0=Sunday.
<xsl:template name="ckbk:calculate-day-of-the-week"> <xsl:param name="date-time"/> <xsl:param name="date" select="substring-before($date-time,'T')"/> <xsl:param name="year" select="substring-before($date,'-')"/> <xsl:param name="month" select="substring-before(substring-after($date,'-'),'-')"/> <xsl:param name="day" select="substring-after(substring-after($date,'-'),'-')"/> <xsl:variable name="a" select="floor((14 - $month) div 12)"/> <xsl:variable name="y" select="$year - $a"/> <xsl:variable name="m" select="$month + 12 * $a - 2"/> <xsl:value-of select="($day + $y + floor($y div 4) - floor($y div 100) + floor($y div 400) + floor((31 * $m) div 12)) mod 7"/> </xsl:template>
XSLT 2.0
Use format-date to get the day of the week as a number or a language-dependent string.
Discussion
You will notice that these equations and those in other examples make
judicious use of the XPath floor()
function. This is the only way to
emulate integer arithmetic in XSLT 1.0, since all numbers are
represented in floating point internally. The reason why this
calculation works has to do with intricacies of the
Gregorian
calendar that are not particularly relevant to XSLT. For example, the
fact that 97 leap years occur every 400 years so that every year
divisible by 4 is a leap year, except if it is divisible by 100 and
not divisible by 400, explains the final calculation.
For further
information, see Sidebar 1.
4.2. Determining the Last Day of the Month
Solution
XSLT 1.0
<xsl:template name="ckbk:last-day-of-month"> <xsl:param name="month"/> <xsl:param name="year"/> <xsl:choose> <xsl:when test="$month = 2 and not($year mod 4) and ($year mod 100 or not($year mod 400))"> <xsl:value-of select="29"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="substring('312831303130313130313031', 2 * $month - 1,2)"/> </xsl:otherwise> </xsl:choose> </xsl:template>
XSLT 2.0
You can translate the 1.0 recipe to a function or take advantage of date math and subtract a day from the first of the following month. This solution is easier to understand but the former is probably faster:
<xsl:function name="ckbk:last-day-of-month" as="xs:integer"> <xsl:param name="month" as="xs:integer"/> <xsl:param name="year" as="xs:integer"/> <!--First of the month in given year --> <xsl:variable name="date" select="xs:date(concat(xs:string($year), '-',format-number($month,'00'),'-01'))" as="xs:date"/> <xsl:variable name="m" select="xdt:yearMonthDuration('P1M')" as="xdt:yearMonthDuration"/> <xsl:variable name="d" select="xdt:dayTimeDuration('P1D')" as="xdt:dayTimeDuration"/> <xsl:sequence select="day-from-date(($date + $m) - $d)"/> </xsl:function>
Discussion
This function has potential application for constructing pages of a
calendar. It is simple enough to understand once you know the rules
governing a leap year. This function was translated from Lisp, in
which the number of days in each month was extracted from a list. I
choose to use substring
to accomplish the same
task. You may prefer to place the logic in additional
when
elements. You might also store data about
each month, including its length, in XML.
See Also
See Recipe 4.5 for ways to store calendar metadata in XML.
4.3. Getting Names for Days and Months
Solution
XSLT 1.0
If internationalization is not important to your application, then the following simple code will do:
<xsl:template name="ckbk:get-day-of-the-week-name"> <xsl:param name="day-of-the-week"/> <xsl:choose> <xsl:when test="$day-of-the-week = 0">Sunday</xsl:when> <xsl:when test="$day-of-the-week = 1">Monday</xsl:when> <xsl:when test="$day-of-the-week = 2">Tuesday</xsl:when> <xsl:when test="$day-of-the-week = 3">Wednesday</xsl:when> <xsl:when test="$day-of-the-week = 4">Thursday</xsl:when> <xsl:when test="$day-of-the-week = 5">Friday</xsl:when> <xsl:when test="$day-of-the-week = 6">Saturday</xsl:when> <xsl:otherwise> error: <xsl:value-of select="$day-of-the-week"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:get-day-of-the-week-abbreviation"> <xsl:param name="day-of-the-week"/> <xsl:choose> <xsl:when test="$day-of-the-week = 0">Sun</xsl:when> <xsl:when test="$day-of-the-week = 1">Mon</xsl:when> <xsl:when test="$day-of-the-week = 2">Tue</xsl:when> <xsl:when test="$day-of-the-week = 3">Wed</xsl:when> <xsl:when test="$day-of-the-week = 4">Thu</xsl:when> <xsl:when test="$day-of-the-week = 5">Fri</xsl:when> <xsl:when test="$day-of-the-week = 6">Sat</xsl:when> <xsl:otherwise> error: <xsl:value-of select="$day-of-the-week"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:get-month-name"> <xsl:param name="month"/> <xsl:choose> <xsl:when test="$month = 1">January</xsl:when> <xsl:when test="$month = 2">February</xsl:when> <xsl:when test="$month = 3">March</xsl:when> <xsl:when test="$month = 4">April</xsl:when> <xsl:when test="$month = 5">May</xsl:when> <xsl:when test="$month = 6">June</xsl:when> <xsl:when test="$month = 7">July</xsl:when> <xsl:when test="$month = 8">August</xsl:when> <xsl:when test="$month = 9">September</xsl:when> <xsl:when test="$month = 10">October</xsl:when> <xsl:when test="$month = 11">November</xsl:when> <xsl:when test="$month = 12">December</xsl:when> <xsl:otherwise>error: <xsl:value-of select="$month"/></xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:get-month-abbreviation"> <xsl:param name="month"/> <xsl:choose> <xsl:when test="$month = 1">Jan</xsl:when> <xsl:when test="$month = 2">Feb</xsl:when> <xsl:when test="$month = 3">Mar</xsl:when> <xsl:when test="$month = 4">Apr</xsl:when> <xsl:when test="$month = 5">May</xsl:when> <xsl:when test="$month = 6">Jun</xsl:when> <xsl:when test="$month = 7">Jul</xsl:when> <xsl:when test="$month = 8">Aug</xsl:when> <xsl:when test="$month = 9">Sep</xsl:when> <xsl:when test="$month = 10">Oct</xsl:when> <xsl:when test="$month = 11">Nov</xsl:when> <xsl:when test="$month = 12">Dec</xsl:when> <xsl:otherwise>error: <xsl:value-of select="$month"/></xsl:otherwise> </xsl:choose> </xsl:template>
XSLT 2.0
XSLT 2.0 has the format-date()
function, which solves this recipe and much
more. Depending on your implementation, it may also handle
internationalization:
<xsl:function name="ckbk:get-day-of-the-week-name" as="xs:string"> <xsl:param name="day-of-the-week" as="xs:integer"/> <!-- Any old Sunday will do as the base. Here I arbitrarily picked Sunday, Aug 14 2005 as my base date because it happens to be the day I am writing this--> <xsl:variable name="date" select="concat('2005-08-', string(14 + $day-of-the-week))" as="xs:date"/> <xsl:sequence select="format-date($date,"[F]"/> </xsl:function> <xsl:function name="ckbk:get-day-of-the-week-name-abbr" as="xs:string"> <xsl:param name="day-of-week" as="xs:integer"/> <xsl:variable name="date" select="concat('2005-08-', string(14 + $day-of-the-week))" as="xs:date"/> <xsl:sequence select="format-date($date, '[FNn,3-3]')"/> </xsl:function> <xsl:function name="ckbk:get-month-name" as="xs:string"> <xsl:param name="month" as="xs:integer"/> <xsl:variable name="jan01" select="xs:date('2005-01-01')" as="xs:date"/> <!-- Here we use date+duration math rather than string manipulation to get the date we need for format-date --> <xsl:sequence select="format-date($jan01 + xdt:yearMonthDuration(concat('P',$month - 1,'M')), '[MNn]')"/> </xsl:function> <xsl:function name="ckbk:get-month-name-abbr" as="xs:string"> <xsl:param name="month" as="xs:integer"/> <xsl:variable name="jan01" select="xs:date('2005-01-01')" as="xs:date"/> <xsl:sequence select="format-date($jan01 + xdt:yearMonthDuration(concat('P',$month - 1,'M')), '[MNn,3-3]')"/> </xsl:function>
The second parameter to format-date
is a picture
string that surrounds formatting codes in square brackets (e.g.,
[D01]
). There is a rich variety of formatting
options available. See http://www.w3.org/TR/xslt20/#function-format-date
for more details.
Discussion
XSLT 1.0
These templates are just fine if your application will never be used outside of the English-speaking world. However, you might consider using a table-driven approach for added portability:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://www.ora.com/XSLTCookbook/NS/dates"> <!-- United States : us --> <ckbk:month country="us" m="1" name="January" abbrev="Jan" /> <ckbk:month country="us" m="2" name="February" abbrev="Feb"/> <ckbk:month country="us" m="3" name="March" abbrev="Mar"/> <ckbk:month country="us" m="4" name="April" abbrev="Apr"/> <ckbk:month country="us" m="5" name="May" abbrev="May"/> <ckbk:month country="us" m="6" name="June" abbrev="Jun"/> <ckbk:month country="us" m="7" name="July" abbrev="Jul"/> <ckbk:month country="us" m="8" name="August" abbrev="Aug"/> <ckbk:month country="us" m="9" name="September" abbrev="Sep"/> <ckbk:month country="us" m="10" name="October" abbrev="Oct"/> <ckbk:month country="us" m="11" name="November" abbrev="Nov"/> <ckbk:month country="us" m="12" name="December" abbrev="Dec"/> <!-- Germany : de --> <ckbk:month country="de" m="1" name="Januar" abbrev="Jan"/> <ckbk:month country="de" m="2"name=";Februar" abbrev="Feb"/> <ckbk:month country="de" m="3" name="März" abbrev="Mär"/> <ckbk:month country="de" m="4" name="April" abbrev="Apr"/> <ckbk:month country="de" m="5" name="Mai" abbrev="Mai"/> <ckbk:month country="de" m="6" name="Juni" abbrev="Jun"/> <ckbk:month country="de" m="7" name="Juli" abbrev="Jul"/> <ckbk:month country="de" m="8" name="August" abbrev="Aug"/> <ckbk:month country="de" m="9" name="September" abbrev="Sep"/> <ckbk:month country="de" m="10" name="Oktober" abbrev="Okt"/> <ckbk:month country="de" m="11" name="November" abbrev="Nov"/> <ckbk:month country="de" m="12" name="Dezember" abbrev="Dez"/> <!-- You get the idea ... --> <!-- Store element in variable for easy access --> <xsl:variable name="ckbk:months" select="document('')/*/ckbk:month"/> </xsl:stylesheet> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:date="http://www.ora.com/XSLTCookbook/dates"> <xsl:include href="date-conversion.xsl"/> <xsl:template name="ckbk:get-month-name"> <xsl:param name="month"/> <xsl:param name="country" select=" 'us' "/> <xsl:value-of select="$ckbk:months[@country=$country and @m=$month]/@name"/> </xsl:template>
XSLT 2.0
The format-date()
, format-time()
, and format-dateTime()
functions are
quite rich. In addition to the capabilities demonstrated by these
recipes, it can format all components of dates and times in multiple
ways. They also allow country, calendar, and language codes to be
specified, so internationalization is free (subject to the limits of
your implementation).
See Also
The full specification of the XSLT 2.0 date formatting functions can be found in Recipe 4.10.
4.4. Calculating Julian and Absolute Day Numbers from a Specified Date
Problem
You have a date and would like to know the corresponding Julian day number and/or absolute day number.
Solution
XSLT 1.0
This template will give you the Julian day, given the year, month, and day:
<xsl:template name="ckbk:calculate-julian-day"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:variable name="a" select="floor((14 - $month) div 12)"/> <xsl:variable name="y" select="$year + 4800 - $a"/> <xsl:variable name="m" select="$month + 12 * $a - 3"/> <xsl:value-of select="$day + floor((153 * $m + 2) div 5) + $y * 365 + floor($y div 4) - floor($y div 100) + floor($y div 400) - 32045"/> </xsl:template>
Once you have a way to calculate the Julian day number, it is easy to create a template for determining the number of days between any two dates:
<xsl:template name="ckbk:date-difference"> <xsl:param name="from-year"/> <xsl:param name="from-month"/> <xsl:param name="from-day"/> <xsl:param name="to-year"/> <xsl:param name="to-month"/> <xsl:param name="to-day"/> <xsl:variable name="jd1"> <xsl:call-template name="ckbk:calculate-julian-day"> <xsl:with-param name="year" select="$from-year"/> <xsl:with-param name="month" select="$from-month"/> <xsl:with-param name="day" select="$from-day"/> </xsl:call-template> </xsl:variable> <xsl:variable name="jd2"> <xsl:call-template name="ckbk:calculate-julian-day"> <xsl:with-param name="year" select="$to-year"/> <xsl:with-param name="month" select="$to-month"/> <xsl:with-param name="day" select="$to-day"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$jd1 - $jd2"/> </xsl:template>
The following templates convert
from a Julian day to a Gregorian date in the form YYYY/MM/DD. Use
substring-before
,
substring-after
, and translate
to parse or convert to the conventions of a particular locale:
<xsl:template name="ckbk:julian-day-to-julian-date"> <xsl:param name="j-day"/> <xsl:call-template name="ckbk:julian-or-gregorian-date-elem"> <xsl:with param name="b" select="0"/> <xsl:with param name="c" select="$j-day + 32082"/> </xsl:call-template> </xsl:template> <xsl:template name="ckbk:julian-day-to-gregorian-date"> <xsl:param name="j-day"/> <xsl:variable name="a" select="$j-day + 32044"/> <xsl:variable name="b" select="floor((4 * $a + 3) div 146097)"/> <xsl:variable name="c" select="$a - 146097 * floor($b div 4)"/> <xsl:call-template name="ckbk:julian-or-gregorian-date-elem"> <xsl:with param name="b" select="$b"/> <xsl:with param name="c" select="$c"/> </xsl:call-template> </xsl:template> <!-- A utility that is used for both Gregorian and Julian calendars. --> <xsl:template name="ckbk:julian-or-gregorian-date-elem"> <xsl:param name="b"/> <xsl:param name="c"/> <xsl:variable name="d" select="floor((4 * $c + 3) div 1461)"/> <xsl:variable name="e" select="$c - floor((1461 * $d) div 4)"/> <xsl:variable name="m" select="floor((5 * $e + 2) div 153)"/> <xsl:variable name="day" select="$e - floor((153 * $m + 2) div 5) + 1"/> <xsl:variable name="month" select="$m + 3 - (12 * floor($m div 10))"/> <xsl:variable name="year" select="100 * $b + $d - 4800 + floor($m div 10)"/> <xsl:value-of select="concat($year,'/',$month,'/',$day)"/> </xsl:template>
You can easily convert between Julian days and absolute days with the following templates:
<xsl:template name="ckbk:julian-day-to-absolute-day"> <xsl:param name="j-day"/> <xsl:value-of select="$j-day - 1721425"/> </xsl:template> <xsl:template name="ckbk:absolute-day-to-julian-day"> <xsl:param name="abs-day"/> <xsl:value-of select="$abs-day + 1721425"/> </xsl:template>
You can then express absolute day/Gregorian conversions in terms of the existing Julian day/Gregorian conversions:
<xsl:template name="ckbk:date-to-absolute-day"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:call-template name="ckbk:julian-day-to-absolute-day"> <xsl:with-param name="j-day"> <xsl:call-template name="ckbk:date-to-julian-day"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:template> <xsl:template name="ckbk:absolute-day-to-date"> <xsl:param name="abs-day"/> <xsl:call-template name="ckbk:julian-day-to-date"> <xsl:with-param name="j-day"> <xsl:call-template name="ckbk:absolute-day-to-julian-day"> <xsl:with-param name="abs-day" select="$abs-day"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:template>
XSLT 2.0
The need for Julian and absolute day functions in XSLT 2.0 is diminished because date math is directly supported. For example,
<dateDiff> <xsl:value-of select="xs:date('2005-02-21') - xs:date('2005-01-01')"/> </dateDiff>
results in:
<dateDiff>P51D</dateDiff>
However, some applications may need these values for other purposes.
Here we choose to implement the 1.0 templates in terms of functions
that take an xs:date
rather than the individual
components:
<xsl:function name="ckbk:calculate-julian-day"> <xsl:param name="date" as="xs:date"/> <xsl:variable name="year" select="year-from-date($date)" as="xs:integer"/> <xsl:variable name="month" select="month-from-date($date)" as="xs:integer"/> <xsl:variable name="day" select="month-from-date($date)" as="xs:integer"/> <xsl:variable name="a" select="(14 - $month) idiv 12" as="xs:integer"/> <xsl:variable name="y" select="$year + 4800 - $a" as="xs:integer"/> <xsl:variable name="m" select="$month + 12 * $a - 3" as="xs:integer"/> <xsl:sequence select="$day + ((153 * $m + 2) idiv 5) + $y * 365 + floor($y div 4) - ($y idiv 100) + ($y idiv 400) - 32045"/> </xsl:function>
Discussion
The Julian day and absolute day are useful because they greatly simplify other date algorithms. Other examples in this chapter reuse these conversions extensively. These numbering schemes act as a common currency for all the calendar systems in this chapter. Should you ever find yourself needing to convert a Hebrew date to a Muslim date, the sequence Muslim to Absolute to Hebrew will do the trick.
4.5. Calculating the Week Number for aSpecified Date
Solution
XSLT 1.0
The week number ranges from 1 to 53. Although most years have 52 weeks, years containing 53 Thursdays have 53.
The solution reuses the Julian day template:
<xsl:template name="ckbk:calculate-week-number"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:variable name="ckbk:j-day"> <xsl:call-template name="ckbk:calculate-julian-day"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:variable> <xsl:variable name="d4" select="($j-day + 31741 - ($j-day mod 7)) mod 146097 mod 36524 mod 1461"/> <xsl:variable name="L" select="floor($d4 div 1460)"/> <xsl:variable name="d1" select="(($d4 - $L) mod 365) + $L"/> <xsl:value-of select="floor($d1 div 7) + 1"/> </xsl:template>
Discussion
The week number is the number assigned to each week of the year. Week 1 of any year is the week that contains January 4 or, equivalently, the week that contains the first Thursday in January. A week that overlaps the end of one year and the beginning of the next is assigned to the year when most of the week’s days lie. This will occur when the year starts on Thursday or Wednesday in a leap year. The U.S. does not currently use this numbering system.
See Also
See Recipe 4.8, later in this chapter.
4.6. Working with the Julian Calendar
Solution
<xsl:template name="ckbk:julian-date-to-julian-day"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:variable name="a" select="floor((14 - $month) div 12)"/> <xsl:variable name="y" select="$year + 4800 - $a"/> <xsl:variable name="m" select="$month + 12 * $a - 3"/> <xsl:value-of select="$day + floor((153 * $m + 2) div 5) + 365 * $y + floor($y div 4) - 32083"/> </xsl:template>
Once you have the Julian day, you can use other recipes in this chapter for date formatting, date math, or conversion to other calendar systems.
Discussion
The Julian system is rarely used in modern times. (One exception is the Russian Orthodox Church.) The Julian calendar was abandoned in favor of the Gregorian calendar due to its slightly inaccurate estimate of a year containing 365 1/4 days. The mean length is actually 365.2425 days, and over time, the seasons began to shift under the Julian system.
4.7. Working with the ISO Calendar
Problem
You need to work with dates in the International standard ISO-8601 calendar.[1]
Solution
A basic facility you need to work with ISO dates (and, later, for determining certain holidays) is a function for finding the absolute day of the k th day on or before a specific absolute day. For example, the first Monday (k = 1) on or before January 4, 2004 (absolute day 731,584) is December 29, 2003 (absolute day 731,578):
<xsl:template name="ckbk:k-day-on-or-before-abs-day"> <xsl:param name="abs-day"/> <xsl:param name="k"/> <xsl:value-of select="$abs-day - (($abs-day - $k) mod 7)"/> </xsl:template>
You can now convert ISO dates to absolute days, which is a simple matter of determining the number of absolute days in prior years and adding in the remaining days in the given ISO date:
<xsl:template name="ckbk:iso-date-to-absolute-day"> <xsl:param name="iso-week"/> <xsl:param name="iso-day"/> <xsl:param name="iso-year"/> <xsl:variable name="jan-4-of-year"> <xsl:call-template name="ckbk:date-to-absolute-day"> <xsl:with-param name="year" select="$iso-year"/> <xsl:with-param name="month" select="1"/> <xsl:with-param name="day" select="4"/> </xsl:call-template> </xsl:variable> <xsl:variable name="days-in-prior-yrs"> <xsl:call-template name="ckbk:k-day-on-or-before-abs-day"> <xsl:with-param name="abs-day" select="$jan-4-of-year"/> <xsl:with-param name="k" select="1"/> </xsl:call-template> </xsl:variable> <xsl:variable name="days-in-prior-weeks-this-yr" select="7 * ($iso-week - 1)"/> <xsl:variable name="prior-days-this-week" select="$iso-day - 1"/> <xsl:value-of select="$days-in-prior-yrs + $days-in-prior-weeks-this-yr + $prior-days-this-week"/> </xsl:template>
To convert from absolute days to an ISO
date, the code will first try to establish the year by making a guess
that it is the same as the Gregorian minus 3 days. This guess can be
wrong only if the absolute day is actually on Jan 1 to Jan 3 of the
following year. To correct for the possible off-by-one mistake, a
comparison is made to Jan 1 of the following year using the
iso-date-to-absolute-day
code already on hand.
Having firmly established the ISO year, the week and day follow by
computing the offset from Jan 1 of that year. We return the ISO date
formatted as
year
-week-day
.
This format is an ISO convention to prevent ISO dates from being
confused with Gregorian dates:
<xsl:template name="ckbk:absolute-day-to-iso-date"> <xsl:param name="abs-day"/> <xsl:variable name="d"> <xsl:call-template name="ckbk:absolute-day-to-date"> <xsl:with-param name="abs-day" select="$abs-day - 3"/> </xsl:call-template> </xsl:variable> <xsl:variable name="approx" select="substring-before($d,'/')"/> <xsl:variable name="iso-year"> <xsl:variable name="a"> <xsl:call-template name="ckbk:iso-date-to-absolute-day"> <xsl:with-param name="iso-week" select="1"/> <xsl:with-param name="iso-day" select="1"/> <xsl:with-param name="iso-year" select="$approx + 1"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$abs-day >= $a"> <xsl:value-of select="$approx + 1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$approx"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="ckbk:iso-week"> <xsl:variable name="a"> <xsl:call-template name="ckbk:iso-date-to-absolute-day"> <xsl:with-param name="iso-week" select="1"/> <xsl:with-param name="iso-day" select="1"/> <xsl:with-param name="iso-year" select="$iso-year"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="1 + floor(($abs-day - $a) div 7)"/> </xsl:variable> <xsl:variable name="iso-day"> <xsl:variable name="a" select="$abs-day mod 7"/> <xsl:choose> <xsl:when test="not($a)"> <xsl:value-of select="7"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$a"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:value-of select="concat($iso-year,'-W',$iso-week,'-',$iso-day)"/> </xsl:template>
Discussion
In European commercial and industrial applications, reference to a week of a year is often required. The ISO calendar specifies dates by using the Gregorian year, the week number (1-53) within the year, and the ordinal day of the week (1-7, where ISO mandates the first day of the week is Monday). A week that overlaps successive years is assigned to the year with the most days in that week. According to this rule, the first week of the ISO calendar year can begin as late as January 4 and as early as December 29 of the previous year. Likewise, the last week of the ISO calendar year can end as early as December 28 and as late as January 3 of the following year. For example, in 2004, ISO week 1 actually started on December 29, 2003![2] To determine the start of the ISO week, then, you need to find the Monday on or before January 4.
See Also
You can see the ISO calendar in action at http://personal.ecu.edu/mccartyr/isowdcal.html.
4.8. Working with the Islamic Calendar
Problem
You need to work with dates in the Islamic system.
Warning
It is difficult if not impossible to devise universally accepted algorithms for the Islamic calendar. This is because each month starts when the lunar crescent is first seen (by an actual human being) after a new moon. New moons can be calculated quite precisely, but the actual visibility of the crescent depends on factors such as weather and the location of the observer. It is therefore very difficult to compute accurately in advance when a new month will start. Furthermore, some Muslims depend on a local sighting of the moon, whereas others depend on a sighting by authorities somewhere in the Muslim world. Saudi Arabia is an exception, since they use astronomical calculation rather than visual sightings. The algorithms provided here can be off by a few days when computing Islamic dates far in advance.
Solution
The last day of an Islamic month can be estimated quite accurately by assigning 30 days to odd months and 29 days to even months, except during a leap year:
<xsl:template name="ckbk:last-day-of-islamic-month"> <xsl:param name="month"/> <xsl:param name="year"/> <xsl:variable name="islamic-leap-year" select="(11 * $year + 14) mod 30 < 11"/> <xsl:choose> <xsl:when test="$month mod 2 or ($month = 12 and $islamic-leap-year)"> <xsl:value-of select="30"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="29"/> </xsl:otherwise> </xsl:choose> </xsl:template>
The Islamic calendar began with the Hijra, Muhammad’s emigration to Medina. For most Muslims, this date occurred on sunset of July 15, 622 AD (Julian calendar). This corresponds to absolute day 227,015, hence the offset of 227,014 in the otherwise straightforward calculation of the absolute day from the Islamic date:
<xsl:template name="ckbk:islamic-date-to-absolute-day"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:value-of select="$day + 29 * ($month - 1) + floor($month div 2) + 354 * ($year - 1) + floor((11 * $year + 3) div 30) + 227014"/> </xsl:template>
The absolute day to Islamic date conversion is different from the original Lisp code from which it was adapted. I use floating-point math to avoid a search technique employed in the Lisp implementation. The authors of the Lisp code desired to keep all calculations within 24-bit integer limits while retaining the greatest accuracy. However, their techniques did not translate well to XSLT. Given that XSLT 1.0 uses only floating-point math, it does not make sense to go through pains to avoid it. The numbers used stem from the average lunar month having 29.530555... days. The month number is approximated and then adjusted if it would result in a day with value less than 1. Once the year and month are established, the day can be computed as an offset from the first day of the year:
<xsl:template name="ckbk:absolute-day-to-islamic-date"> <xsl:param name="abs-day"/> <xsl:variable name="year" select="floor(($abs-day - 227014) div 354.36667) + 1"/> <xsl:variable name="month"> <xsl:variable name="a" select="$abs-day - 227014 - floor((11 * $year + 3) div 30) - 354 * ($year - 1)"/> <xsl:variable name="approx" select="floor($a div 29.53056)+1"/> <xsl:choose> <xsl:when test="(29 * ($approx - 1) + floor($approx div 2)) - $a < 1"> <xsl:value-of select="$approx - 1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$approx"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="day"> <xsl:variable name="a"> <xsl:call-template name="ckbk:islamic-date-to-absolute-day"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="1"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$abs-day - $a + 1"/> </xsl:variable> <xsl:value-of select="concat($year,'/',$month,'/',$day)"/> </xsl:template>
Discussion
The Hijri or Islamic calendar is interesting because it is based on purely lunar cycles, and thus the Muslim months are not fixed within the seasons. An Islamic year is approximately 354.36 days. The first year of the Islamic calendar is denoted 1 A.H. (After Hijra), Muhammad’s flight from Mecca to Medina. The Islamic calendar has deep religious significance to devout Muslims and is almost always based on visual observations of the moon. Approximate calendars can be computed in advance by using algorithms, but they should generally be used only for rough planning.
See Also
You can see the Muslim calendar in action at http://www.sufisattari.com/calendar.html.
Differences in convention among various Islamic counties are described at http://www.math.nus.edu.sg/aslaksen/calendar/islamic.shtml.
4.9. Working with the Hebrew Calendar
Solution
You need to build up some basic utilities to work effectively with the Hebrew calendar. Hebrew years have 12 months in a regular year and 13 in a leap year. Leap years occur on the 3rd, 6th, 8th, 11th, 14th, 17th, and 19th years of the Metonic cycle (see the “Discussion” section). A concise means of making this determination is given by the relation 7y + 1 mod 19 < 7. From this, you can easily devise a function to determine the last month of any Hebrew year:
<xsl:template name="ckbk:last-month-of-hebrew-year"> <xsl:param name="year"/> <xsl:choose> <xsl:when test="(7 * $year + 1) mod 19 < 7"> <xsl:value-of select="13"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="12"/> </xsl:otherwise> </xsl:choose> </xsl:template>
As a prerequisite to determining the number of days in any given month or year, you need to encapsulate the complex rules that determine when the Hebrew new year starts. See the paper by Dershowitz and Reingold for detailed explanation:
<!-- Number of days elapsed from the Sunday prior to the start of the Hebrew calender to the mean conjunction of Tishri of Hebrew year. --> <xsl:template name="ckbk:hebrew-calendar-elapsed-days"> <xsl:param name="year"/> <xsl:variable name="hebrew-leap-year" select="(7 * $year + 1) mod 19 < 7"/> <xsl:variable name="hebrew-leap-year-last-year" select="(7 * ($year - 1) + 1) mod 19 < 7"/> <xsl:variable name="months-elapsed" select="235 * floor(($year -1) div 19) + 12 * (($year -1) mod 19) + floor((7 * (($year - 1) mod 19) + 1) div 19)"/> <xsl:variable name="parts-elapsed" select="13753 * $months-elapsed + 5604"/> <xsl:variable name="day" select="1 + 29 * $months-elapsed + floor($parts-elapsed div 25920)"/> <xsl:variable name="parts" select="$parts-elapsed mod 25920"/> <xsl:variable name="alternative-day"> <xsl:choose> <xsl:when test="$parts >= 19440"> <xsl:value-of select="$day + 1"/> </xsl:when> <xsl:when test="$day mod 7 = 2 and $parts >= 9924 and not($hebrew-leap-year)"> <xsl:value-of select="$day + 1"/> </xsl:when> <xsl:when test="$day mod 7 = 1 and $parts >= 16789 and $hebrew-leap-year-last-year"> <xsl:value-of select="$day + 1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$day"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:choose> <xsl:when test="$alternative-day mod 7 = 0"> <xsl:value-of select="$alternative-day + 1"/> </xsl:when> <xsl:when test="$alternative-day mod 7 =3"> <xsl:value-of select="$alternative-day + 1"/> </xsl:when> <xsl:when test="$alternative-day mod 7 = 5"> <xsl:value-of select="$alternative-day + 1"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$alternative-day"/> </xsl:otherwise> </xsl:choose> </xsl:template>
The number of days in a Hebrew year is calculated as the difference between the elapsed days in successive years:
<xsl:template name="ckbk:days-in-hebrew-year"> <xsl:param name="year"/> <xsl:variable name="e1"> <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days"> <xsl:with-param name="year" select="$year + 1"/> </xsl:call-template> </xsl:variable> <xsl:variable name="e2"> <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$e1 - $e2"/> </xsl:template>
Heshvan and Kislev are the eighth and ninth months of the Hebrew year and their number of days can vary. You need to know when Heshvan is long and Kislev is short, so you create two predicates:
<xsl:template name="ckbk:long-heshvan"> <xsl:param name="year"/> <xsl:variable name="days"> <xsl:call-template name="ckbk:days-in-hebrew-year"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:if select="$days mod 10 = 5"> <xsl:value-of select="true()"/> </xsl:if> </xsl:template> <xsl:template name="ckbk:short-kislev"> <xsl:param name="year"/> <xsl:variable name="days"> <xsl:call-template name="ckbk:days-in-hebrew-year"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:if select="$days mod 10 = 3"> <xsl:value-of select="true()"/> </xsl:if> </xsl:template>
Warning
If you
write predicate templates in XSLT 1.0, you should code them to return
true()
(or alternatively,
'true
') for true but ''
(null
string)
for false. The
problem here is that templates return trees, and any tree, even one
whose only node contains false()
or
'', will evaluate to true
. The
advantage of a tree containing '' is that it can
be effectively evaluated as a Boolean by using the string()
conversion. This is one of the many awkward facts of
XSLT. In XSLT 2.0 you can use functions instead as they can return
Booleanvalues.
Most of the machinery is now in place to tackle standard date-time functions provided in other recipes. The first standard function gives the last day in a month for a specified Hebrew month and year:
<xsl:template name="ckbk:last-day-of-hebrew-month"> <xsl:param name="month"/> <xsl:param name="year"/> <xsl:variable name="hebrew-leap-year" select="(7 * $year + 1) mod 19 < 7"/> <xsl:variable name="long-heshvan"> <xsl:call-template name="ckbk:long-heshvan"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:variable name="short-kislev"> <xsl:call-template name="ckbk:short-kislev"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$month=12 and $hebrew-leap-year"> <xsl:value-of select="30"/> </xsl:when> <xsl:when test="$month=8 and string($long-heshvan)"> <xsl:value-of select="30"/> </xsl:when> <xsl:when test="$month=9 and string($short-kislev)"> <xsl:value-of select="29"/> </xsl:when> <xsl:when test="$month=13"> <xsl:value-of select="29"/> </xsl:when> <xsl:when test="$month mod 2 = 0"> <xsl:value-of select="29"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="30"/> </xsl:otherwise> </xsl:choose> </xsl:template>
This recursive utility lets you sum the last days in a range of Hebrew months for a given year. It is used when converting a Hebrew date to an absolute year:
<xsl:template name="ckbk:sum-last-day-in-hebrew-months"> <xsl:param name="year"/> <xsl:param name="from-month"/> <xsl:param name="to-month"/> <xsl:param name="accum" select="0"/> <xsl:choose> <xsl:when test="$from-month <= $to-month"> <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="from-month" select="$from-month+1"/> <xsl:with-param name="to-month" select="$to-month"/> <xsl:with-param name="accum"> <xsl:variable name="temp"> <xsl:call-template name="ckbk:last-day-of-hebrew-month"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$from-month"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$temp + $accum"/> </xsl:with-param> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$accum"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:hebrew-date-to-absolute-day"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:variable name="prior-months-days"> <xsl:choose> <xsl:when test="7 > $month"> <!-- before Tishri --> <xsl:variable name="last-month-of-year"> <xsl:call-template name="ckbk:last-month-of-hebrew-year"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <!-- Add days before and after Nisan --> <xsl:variable name="days-before-nisan"> <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="from-month" select="7"/> <xsl:with-param name="to-month" select="$last-month-of-year"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="from-month" select="1"/> <xsl:with-param name="to-month" select="$month - 1"/> <xsl:with-param name="accum" select="$days-before-nisan"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <!-- days in prior months this year--> <xsl:call-template name="ckbk:sum-last-day-in-hebrew-months"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="from-month" select="7"/> <xsl:with-param name="to-month" select="$month - 1"/> </xsl:call-template> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="days-in-prior-years"> <xsl:call-template name="ckbk:hebrew-calendar-elapsed-days"> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <!-- 1373429 days before absolute day 1 --> <xsl:value-of select="$day + $prior-months-days + $days-in-prior-years - 1373429"/> </xsl:template>
Before implementing absolute-day-to-hebrew-date
,
you need two more recursive summation utilities that will help search
for the actual year and month corresponding to an absolute day from
approximations of the same year and month:
<xsl:template name="ckbk:fixup-hebrew-year"> <xsl:param name="start-year"/> <xsl:param name="abs-day"/> <xsl:param name="accum" select="0"/> <xsl:variable name="next"> <xsl:call-template name="ckbk:hebrew-date-to-absolute-day"> <xsl:with-param name="month" select="7"/> <xsl:with-param name="day" select="1"/> <xsl:with-param name="year" select="$start-year + 1"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$abs-day >= $next"> <xsl:call-template name="ckbk:fixup-hebrew-year"> <xsl:with-param name="start-year" select="$start-year+1"/> <xsl:with-param name="abs-day" select="$abs-day"/> <xsl:with-param name="accum" select="$accum + 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$accum"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:fixup-hebrew-month"> <xsl:param name="year"/> <xsl:param name="start-month"/> <xsl:param name="abs-day"/> <xsl:param name="accum" select="0"/> <xsl:variable name="next"> <xsl:call-template name="ckbk:hebrew-date-to-absolute-day"> <xsl:with-param name="month" select="$start-month"/> <xsl:with-param name="day"> <xsl:call-template name="ckbk:last-day-of-hebrew-month"> <xsl:with-param name="month" select="$start-month"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:with-param> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:choose> <xsl:when test="$abs-day > $next"> <xsl:call-template name="ckbk:fixup-hebrew-month"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="start-month" select="$start-month + 1"/> <xsl:with-param name="abs-day" select="$abs-day"/> <xsl:with-param name="accum" select="$accum + 1"/> </xsl:call-template> </xsl:when> <xsl:otherwise> <xsl:value-of select="$accum"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="ckbk:absolute-day-to-hebrew-date"> <xsl:param name="abs-day"/> <xsl:variable name="year"> <xsl:variable name="approx" select="floor(($abs-day + 1373429) div 366)"/> <xsl:variable name="fixup"> <xsl:call-template name="ckbk:fixup-hebrew-year"> <xsl:with-param name="start-year" select="$approx"/> <xsl:with-param name="abs-day" select="$abs-day"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$approx + $fixup"/> </xsl:variable> <xsl:variable name="month"> <xsl:variable name="first-day-of-year"> <xsl:call-template name="ckbk:hebrew-date-to-absolute-day"> <xsl:with-param name="month" select="1"/> <xsl:with-param name="day" select="1"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:variable name="approx"> <xsl:choose> <xsl:when test="$abs-day < $first-day-of-year"> <xsl:value-of select="7"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="1"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="fixup"> <xsl:call-template name="ckbk:fixup-hebrew-month"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="start-month" select="$approx"/> <xsl:with-param name="abs-day" select="$abs-day"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$approx + $fixup"/> </xsl:variable> <xsl:variable name="day"> <xsl:variable name="days-to-first-of-month"> <xsl:call-template name="ckbk:hebrew-date-to-absolute-day"> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="1"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$abs-day - ($days-to-first-of-month - 1)"/> </xsl:variable> <xsl:value-of select="concat($year,'-',$month,'-',$day)"/> </xsl:template>
Tip
In the previous example, you create three unique yet similar recursive templates that compute the sum of a function. Creating a generic utility that can sum an arbitrary function is highly desirable. Readers who are familiar with Lisp could probably guess that the original Lisp code from which this XSLT was derived used a Lisp macro to accomplish just that. Chapter 13 demonstrates how generic programming can be achieved in XSLT and how this recipe can be greatly simplified as a result.
Discussion
The Hebrew calendar is the most complex calendar covered in this chapter. Hence, the Hebrew date-time code is correspondingly complicated. The Hebrew calendar is intricate because its months are strictly lunar, yet it mandates that Passover must always occur in the spring. While most other calendars have a fixed number of months, the Hebrew calendar has 12 during a conventional year and 13 during a leap year.
4.10. Formatting Dates and Times
Solution
These templates reuse many of the templates already presented in this
chapter. The format-date-time
uses a format string
where %x
is a formatting directive (see later) and
all other text is output literally. The default format is the ISO
date-time format for Gregorian dates:
<xsl:template name="ckbk:format-date-time"> <xsl:param name="year"/> <xsl:param name="month"/> <xsl:param name="day"/> <xsl:param name="hour"/> <xsl:param name="minute"/> <xsl:param name="second"/> <xsl:param name="time-zone"/> <xsl:param name="format" select="'%Y-%m-%dT%H:%M:%S%z'"/> <xsl:choose> <xsl:when test="contains($format, '%')"> <xsl:value-of select="substring-before($format, '%')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$format"/> </xsl:otherwise> </xsl:choose> <xsl:variable name="code" select="substring(substring-after($format, '%'), 1, 1)"/> <xsl:choose> <!-- Abbreviated weekday name --> <xsl:when test="$code='a'"> <xsl:variable name="day-of-the-week"> <xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="ckbk:get-day-of-the-week-abbreviation"> <xsl:with-param name="day-of-the-week" select="$day-of-the-week"/> </xsl:call-template> </xsl:when> <!-- Full weekday name --> <xsl:when test="$code='A'"> <xsl:variable name="day-of-the-week"> <xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="ckbk:get-day-of-the-week-name"> <xsl:with-param name="day-of-the-week" select="$day-of-the-week"/> </xsl:call-template> </xsl:when> <!-- Abbreviated month name --> <xsl:when test="$code='b'"> <xsl:call-template name="ckbk:get-month-abbreviation"> <xsl:with-param name="month" select="$month"/> </xsl:call-template> </xsl:when> <!-- Full month name --> <xsl:when test="$code='B'"> <xsl:call-template name="ckbk:get-month-name"> <xsl:with-param name="month" select="$month"/> </xsl:call-template> </xsl:when> <!-- Date and time representation appropriate for locale --> <xsl:when test="$code='c'"> <xsl:text>[not implemented]</xsl:text> </xsl:when> <!-- Day of month as decimal number (01 - 31) --> <xsl:when test="$code='d'"> <xsl:value-of select="format-number($day,'00')"/> </xsl:when> <!-- Hour in 24-hour format (00 - 23) --> <xsl:when test="$code='H'"> <xsl:value-of select="format-number($hour,'00')"/> </xsl:when> <!-- Hour in 12-hour format (01 - 12) --> <xsl:when test="$code='I'"> <xsl:choose> <xsl:when test="$hour = 0">12</xsl:when> <xsl:when test="$hour < 13"> <xsl:value-of select="format-number($hour,'00')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="format-number($hour - 12,'00')"/> </xsl:otherwise> </xsl:choose> </xsl:when> <!-- Day of year as decimal number (001 - 366) --> <xsl:when test="$code='j'"> <xsl:variable name="diff"> <xsl:call-template name="ckbk:date-difference"> <xsl:with-param name="from-year" select="$year"/> <xsl:with-param name="from-month" select="1"/> <xsl:with-param name="form-day" select="1"/> <xsl:with-param name="to-year" select="$year"/> <xsl:with-param name="to-month" select="$month"/> <xsl:with-param name="to-day" select="$day"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="format-number($diff + 1, '000')"/> </xsl:when> <!-- Month as decimal number (01 - 12) --> <xsl:when test="$code='m'"> <xsl:value-of select="format-number($month,'00')"/> </xsl:when> <!-- Minute as decimal number (00 - 59) --> <xsl:when test="$code='M'"> <xsl:value-of select="format-number($minute,'00')"/> </xsl:when> <!-- Current locale's A.M./P.M. indicator for 12-hour clock --> <xsl:when test="$code='p'"> <xsl:choose> <xsl:when test="$hour < 12">AM</xsl:when> <xsl:otherwise>PM</xsl:otherwise> </xsl:choose> </xsl:when> <!-- Second as decimal number (00 - 59) --> <xsl:when test="$code='S'"> <xsl:value-of select="format-number($second,'00')"/> </xsl:when> <!-- Week of year as decimal number, with Sunday as first day of week (00 - 53) --> <xsl:when test="$code='U'"> <!-- add 1 to day --> <xsl:call-template name="ckbk:calculate-week-number"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day + 1"/> </xsl:call-template> </xsl:when> <!-- Weekday as decimal number (0 - 6; Sunday is 0) --> <xsl:when test="$code='w'"> <xsl:call-template name="ckbk:calculate-day-of-the-week"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:when> <!-- Week of year as decimal number, with Monday as first day of week (00 - 53) --> <xsl:when test="$code='W'"> <xsl:call-template name="ckbk:calculate-week-number"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> </xsl:call-template> </xsl:when> <!-- Date representation for current locale --> <xsl:when test="$code='x'"> <xsl:text>[not implemented]</xsl:text> </xsl:when> <!-- Time representation for current locale --> <xsl:when test="$code='X'"> <xsl:text>[not implemented]</xsl:text> </xsl:when> <!-- Year without century, as decimal number (00 - 99) --> <xsl:when test="$code='y'"> <xsl:value-of select="format-number($year mod 100,'00')"/> </xsl:when> <!-- Year with century, as decimal number --> <xsl:when test="$code='Y'"> <xsl:value-of select="format-number($year,'0000')"/> </xsl:when> <!-- Time-zone name or abbreviation; --> <!-- no characters if time zone is unknown --> <xsl:when test="$code='z'"> <xsl:value-of select="$time-zone"/> </xsl:when> <!-- Percent sign --> <xsl:when test="$code='%'"> <xsl:text>%</xsl:text> </xsl:when> </xsl:choose> <xsl:variable name="remainder" select="substring(substring-after($format, '%'), 2)"/> <xsl:if test="$remainder"> <xsl:call-template name="ckbk:format-date-time"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> <xsl:with-param name="hour" select="$hour"/> <xsl:with-param name="minute" select="$minute"/> <xsl:with-param name="second" select="$second"/> <xsl:with-param name="time-zone" select="$time-zone"/> <xsl:with-param name="format" select="$remainder"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="ckbk:format-julian-day"> <xsl:param name="julian-day"/> <xsl:param name="format" select="'%Y-%m-%d'"/> <xsl:variable name="a" select="$julian-day + 32044"/> <xsl:variable name="b" select="floor((4 * $a + 3) div 146097)"/> <xsl:variable name="c" select="$a - floor(($b * 146097) div 4)"/> <xsl:variable name="d" select="floor((4 * $c + 3) div 1461)"/> <xsl:variable name="e" select="$c - floor((1461 * $d) div 4)"/> <xsl:variable name="m" select="floor((5 * $e + 2) div 153)"/> <xsl:variable name="day" select="$e - floor((153 * $m + 2) div 5) + 1"/> <xsl:variable name="month" select="$m + 3 - 12 * floor($m div 10)"/> <xsl:variable name="year" select="$b * 100 + $d - 4800 + floor($m div 10)"/> <xsl:call-template name="ckbk:format-date-time"> <xsl:with-param name="year" select="$year"/> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="$day"/> <xsl:with-param name="format" select="$format"/> </xsl:call-template> </xsl:template>
XSLT 2.0
Use the XSLT 2.0 date formatting functions. In the following
fragment, I assume you have to deal with a date as a string in the
form YYYYMMDD
, which is often more common
in practice than the ISO date format required by
format-date
:
xsl:param name="date" as="xs:string"/> <invoiceDate><xsl:value-of select="format-date( xs:date(concat(substring($date,1,4), '-', substring($date,5,2), '-', substring($date,7,2))), '[MNn] [D01], [Y0001]')"/></invoiceDate>
This would output the following for $date =
'20050811
':
<invoiceDate>August 11, 2005</invoiceDate>
Discussion
XSLT 1.0
This example was made possible by all the date work done in the prior examples. The options requiring locale are not implemented, but could be implemented using extension functions (see Chapter 12).
XSLT 2.0
I provide an excerpt from the W3C specification for your convenience.
Three functions are provided to represent dates and times as a string, using the conventions of a selected calendar and locale. Each has two variants:
format-dateTime($value as xs:dateTime?, $picture as xs:string, $date-format-name as xs:string) as xs:string? format-dateTime($value as xs:dateTime?, $picture as xs:string) as xs:string? format-date($value as xs:date?, $picture as xs:string, $date-format-name as xs:string) as xs:string? format-date($value as xs:date?, $picture as xs:string) as xs:string? format-time($value as xs:time?, $picture as xs:string, $date-format-name as xs:string) as xs:string? format-time($value as xs:time?, $picture as xs:string) as xs:string?
The format-dateTime
,
format-date
, and format-time
functions format $value
as a string using the
picture string specified by the $picture
argument
and the date-format named by the $date-format-name
argument, or the default date-format, if there is no
$date-format-name
argument. The result of the
function is the formatted string representation of the supplied
dateTime
, date
, or
time
value.
The three functions, format-dateTime
,
format-date
, and format-time
,
and are referred to collectively as the date formatting
functions.
It is a dynamic error
if the name specified as the $date-format-name
argument is not a
valid
QName, or if its prefix has not been declared in
an in-scope namespace declaration, or if the stylesheet does not
contain a declaration of a date-format
with a
matching expanded-QName
. The processor must either
signal the error, or must recover by ignoring the
$date-format-name
argument. If the processor is
able to detect the error statically (for example, when the argument
is supplied as a string literal), then the processor may optionally
signal this as a static error.
If $value
is the empty sequence, the empty
sequence is returned.
The date-format declaration
<!-- Category: declaration --> <xsl:date-format name = qname language = nmtoken calendar = qname />
The
xsl:date-format
element declares a date-format, which provides
information used by the date formatting functions. If there is a
name
attribute, then the element declares a named
date format; otherwise, it declares the default date format. The
value of the name
attribute is a
QName
. It is a static
error
to declare either the default date-format
or a
date-format with a given name more than once (even with different
import precedence), unless it is declared every time with the same
value for all attributes (taking into account any default values). If
a stylesheet does not contain a declaration of the default
date-format, a declaration equivalent to an
xsl:date-format
element with no attributes is
implied.
The language
attribute specifies the language to
be used for the result string of the
format-date
function. The effective value of the
attribute must be a value that would be valid for the
xml:lang
attribute. If the language
attribute is omitted,
then the default is implementation-defined.
The language is used to select the appropriate language-dependent forms of:
Names (for example, of months)
Ordinal form of numbers
Hour convention (0-23 vs 1-24, 0-11 vs 1-12)
First day of week, first week of year
The set of languages that are supported is implementation-defined.
The
calendar
attribute specifies that the dateTime
,
date
, or time
supplied in the
$value
argument must be converted to a value in
that calendar and then converted to a string using the conventions of
that calendar.
A calendar value must be a valid QName. If the QName does not have a prefix, then it identifies a calendar with the designator specified below. If the QName has a prefix, then the QName is expanded into an expanded-QName ; the expanded-QName identifies the calendar; the behavior in this case is not specified by this document.
If the calendar attribute is omitted, a locale-specific value is used.
It is a static error
if an implementation does not support the language specified in the
language
attribute, or the calendar specified in
the calendar
attribute, or the combination of the
two. The processor must either signal the error, or must recover by
using a locale-specific value of the two attributes instead of the
values specified. If a different calendar is used from that
requested, the name of this calendar must be included in the result
string.
Tip
The calendars listed next were known to be in use during the last
hundred years. Many other calendars have been used in the past, and
in many cases these cannot be fully supported without additional
parameters. Such parameters may be defined using additional
namespace-prefixed attributes on the
xsl:date-format
element; the semantics of such
attributes are not defined by this specification.
This specification does not define any of these calendars, nor the
way that they map to the value space of the
xs:date
data type in XML Schema. There is only an
approximate equivalence between dates recorded using different
calendars. For example, the start of a new day is not simultaneous in
different calendars, and may also vary geographically.
Implementations supporting calendars other than the Gregorian
calendar may therefore produce different results.
Designator |
Calendar |
AD |
Anno Domini (Christian Era) |
AH |
Anno Hegirae (Muhammedan Era) |
AME |
Mauludi Era (solar years since Mohammed’s birth) |
AM |
Anno Mundi (Jewish Calendar) |
AP |
Anno Persici |
AS |
Aji Saka Era (Java) |
BE |
Buddhist Era |
CB |
Cooch Behar Era |
CE |
Common Era |
CL |
Chinese Lunar Era |
CS |
Chula Sakarat Era |
EE |
Ethiopian Era |
FE |
Fasli Era |
ISO |
ISO 8601 calendar |
JE |
Japanese Calendar |
KE |
Khalsa Era (Sikh calendar) |
KY |
Kali Yuga |
ME |
Malabar Era |
MS |
Monarchic Solar Era |
NS |
Nepal Samwat Era |
OS |
Old Style (Julian Calendar) |
RS |
Rattanakosin (Bangkok) Era |
SE |
Saka Era |
SH |
Mohammedan Solar Era (Iran) |
SS |
Saka Samvat |
TE |
Tripurabda Era |
VE |
Vikrama Era |
VS |
Vikrama Samvat Era |
At least one of the preceding calendars must be supported. It is implementation-defined which calendars are supported.
The ISO 8601 calendar, which is included in the previous list and
designated ISO
, is essentially the same as the
Gregorian calendar designated AD
, but it
prescribes the use of particular numbering conventions as defined in
ISO 8601, rather than allowing these to be localized on a
per-language basis. Specifically, in the ISO calendar, the days of
the week are numbered from 1 (Monday) to 7 (Sunday), and week 1 in
any calendar year is the week (from Monday to Sunday) that includes
the first Thursday of that year. The numeric values of the components
year, month, day, hour, minutes, and seconds are the same in this
calendar as the values used in the lexical representation of the date
and time as defined in XML Schema. The ISO calendar is intended
primarily for applications that need to produce dates and times in
formats to be read by other software, rather than by human users.
Tip
The value space of the date and time data types, as defined in XML
Schema, is based on absolute points in time. The lexical space of
these data types defines a representation of these absolute points in
time using the proleptic Gregorian calendar — that is, the
modern Western calendar extrapolated into the past and the future
— but the value space is calendar-neutral. The date-formatting
functions produce a representation of the same absolute point in time
as denoted in a possibly different calendar. So, for example, the
date whose lexical representation in XML Schema is
1502-01-11
(the day on which Pope Gregory XIII was
born) might be formatted using the Old Style (Julian) calendar as
1 January 1502
. This reflects the fact that there
was at that time a ten-day difference between the two calendars. It
would be incorrect, and would produce incorrect results, to represent
this date in an element or attribute of type
xs:date
as 1502-01-01
, even
though this might reflect the way the date was recorded in
contemporary documents.
The picture string
The picture consists of a sequence of variable markers and literal substrings. A substring enclosed in square brackets is interpreted as a variable marker; substrings not enclosed in square brackets are taken as literal substrings. The literal substrings are optional and if present are rendered unchanged, including any whitespace. If an opening or closing square bracket is required within a literal sub-string, it must be doubled. The variable markers are replaced in the result by strings representing aspects of the date and/or time to be formatted. These are described in detail next.
A variable marker consists of a component specifier followed optionally by one or two presentation modifiers and/or optionally by a length modifier. Whitespace within a variable marker is ignored.
The component specifier indicates the component of the date or time that is required, and takes the following values:
Specifier |
Meaning |
Default presentation modifier |
Y |
Year |
1 |
M |
Month in year |
1 |
D |
Day in month |
1 |
D |
Day in year |
1 |
F |
Day of week |
n |
W |
Week in year |
1 |
W |
Week in month |
1 |
H |
Hour in day (24 hours) |
1 |
H |
Hour in half-day (12 hours) |
1 |
P |
a.m./p.m. marker |
n |
M |
Minute in hour |
1 |
S |
Second in minute |
1 |
F |
Fractional seconds |
1 |
Z |
Timezone as a time offset from UTC, or if an alphabetic modifier is present, the conventional name of a timezone (such as PST) |
1 |
Z |
Timezone as a time offset using GMT; for example, GMT+1 |
1 |
C |
Calendar: the name or abbreviation of a calendar name |
n |
E |
Era: the name of a baseline for the numbering of years; for example, the reign of a monarch |
n |
It is a dynamic error
if a component specifier within the picture refers to components that
are not available in the given $value
, or which
are not supported in the chosen calendar. This is a
recoverable error
.
The processor may signal the error, or may recover by ignoring the
offending component
specifiers
.
The first presentation modifier indicates the style in which the value of a component is to be represented, and takes the following values:
Modifier |
Meaning |
A |
Alphabetic, upper ase |
A |
Alphabetic, lowercase (may start with initial uppercase if so used in language) |
N |
Name, uppercase |
N |
Name, lowercase (may start with initial uppercase if so used in language) |
digit 1 |
Decimal representation |
I |
Lower-case Roman numeral |
I |
Upper-case Roman numeral |
Any character that has a decimal digit value of 1 (as specified in the Unicode character property database) generates a decimal representation of the number using the appropriate set of Unicode digits.
Any other character may be used to indicate a numbering sequence that starts with that character, if the implementation supports such a numbering sequence.
If the implementation does not support the use of the requested presentation modifier, it must use the default presentation modifier for that component.
If the first presentation modifier is present, then it may optionally be followed by a second presentation modifier as follows:
Modifier |
Meaning |
T |
Traditional numbering. This has the same meaning as
|
O |
Ordinal form of a number; for example, 3rd or 8º. |
Whether or not a presentation modifier is included, a width modifier may be supplied. This indicates the number of characters or digits to be included in the representation of the value.
The width modifier takes the form:
min-width ("-" max-width)?
where min-width
is either an unsigned integer
indicating the minimum number of characters to be output, or
*
indicating that there is no explicit minumum,
and max-width
is either an unsigned integer
indicating the maximum number of characters to be output, or
*
indicating that there is no explicit maximum. If
max-width
is omitted, then *
is
assumed. Both integers, if present, must be greater than zero.
If there is no width modifier, then the output uses as many characters as are required to represent the value of the component without truncation and without padding: this is referred to below as the full representation of the value.
If the full representation of the value exceeds the specified maximum
width, then the processor should attempt to use an alternative
shorter representation that fits within the maximum width. Where the
presentation modifier is n
or
N
, this is done by abbreviating the name, using
either conventional abbreviations if available, or crude
right-truncation if not. For example, setting
max-width
to 4
indicates that
four-letter abbreviations should be used, though it would be
acceptable to use a three-letter abbreviation if this is in
conventional use. (For example,
“Tuesday” might be abbreviated to
“Tues”, and
“Friday” to
“Fri”.) In the case of the year
component, setting max-width
requests omission of
high-order digits from the year; for example, if
max-width
is set to 2
, then the
year 2003 will be output as 03
. If no mechanism is
available for fitting the value within the specified maximum width
(for example, when roman numerals are used), then the value should be
output in its full representation.
If the full representation of the value is shorter than the specified minimum width, then the processor should pad the value to the specified width. For decimal representations of numbers, this should be done by prepending zero digits from the appropriate set of digit characters. In other cases, it should be done by prepending spaces.
The choice of the names and abbreviations used in any given language
is implementation-defined. For example, one
implementation might abbreviate July as Jul
while
another uses Jly
. In German, one implementation
might represent Saturday as Samstag
while another
uses Sonnabend
. Implementations may provide
mechanisms allowing users to control such choices.
The following examples show a selection of dates and times and the
way they might be formatted. These examples assume the use of the
Gregorian calendar, and assume that the name of
xsl:date-format
declarations in the stylesheet is
the same as the value of their language
attribute.
(For example, <date-format name="sv
"
language="sv"/>
.)
Required output |
Expression |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The following examples use calendars other than the Gregorian calendar:
<!-- Example: Thai --> <xsl:date-format name="modern_Thai" language="th" calendar="BE"/> format-date($d, "[D๑] [Mn] [Y๑]", "modern_Thai") <!-- Result: --> <!--Example: Islamic--> <xsl:date-format name="Islamic" language="ar" calendar="AH"/> format-date($d, "[D١] [Mn] [Y١]", "Islamic") <!-- Result: --> <!--Example: Jewish--> <xsl:date-format name="Jewish " language="he" calendar="AM"/> format-date($d, "[D] [Mn] [Y]", "Jewish") <!--Result: 26 5763-->
4.11. Determining Secular and Religious Holidays
Solution
The first type of holiday includes those that fall on the same day every year. For example, a function to determine the absolute day of American Independence for any year is simply:
<xsl:template name="ckbk:independence-day"> <xsl:param name="year"/> <xsl:call-template name="ckbk:date-to-absolute-day"> <xsl:with-param name="month" select="7"/> <xsl:with-param name="day" select="4"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:template>
The second type of holiday falls on the same day of the week relative
to the start or end of a month. You can compute those days with the
help of the following utility, which wraps the
k-day-on-or-before-abs-day
template contained in
Recipe 4.8:
<xsl:template name="ckbk:n-th-k-day"> <!-- The n'th occurance of k in the given month --> <!-- Postive n counts from beginning of month; negative from end. --> <xsl:param name="n"/> <!-- k = the day of the week (0 = Sun) --> <xsl:param name="k"/> <xsl:param name="month"/> <xsl:param name="year"/> <xsl:choose> <xsl:when test="$n > 0"> <xsl:variable name="k-day-on-or-before"> <xsl:variable name="abs-day"> <xsl:call-template name="ckbk:date-to-absolute-day"> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day" select="7"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="ckbk:k-day-on-or-before-abs-day"> <xsl:with-param name="abs-day" select="$abs-day"/> <xsl:with-param name="k" select="$k"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$k-day-on-or-before + 7 * ($n - 1)"/> </xsl:when> <xsl:otherwise> <xsl:variable name="k-day-on-or-before"> <xsl:variable name="abs-day"> <xsl:call-template name="ckbk:date-to-absolute-day"> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="day"> <xsl:call-template name="ckbk:last-day-of-month"> <xsl:with-param name="month" select="$month"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:with-param> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:variable> <xsl:call-template name="ckbk:k-day-on-or-before-abs-day"> <xsl:with-param name="abs-day" select="$abs-day"/> <xsl:with-param name="k" select="$k"/> </xsl:call-template> </xsl:variable> <xsl:value-of select="$k-day-on-or-before + 7 * ($n + 1)"/> </xsl:otherwise> </xsl:choose> </xsl:template>
This function assumes Gregorian dates. If you need to determine relative dates within other calendar systems, you need to write equivalent routines within those systems.
It is now easy to handle holidays like American Labor and Memorial days (the first Monday of September and the last Monday of May, respectively):
<xsl:template name="ckbk:labor-day"> <xsl:param name="year"/> <xsl:call-template name="ckbk:n-th-k-day "> <xsl:with-param name="n" select="1"/> <xsl:with-param name="k" select="1"/> <xsl:with-param name="month" select="9"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:template> <xsl:template name="ckbk:memorial-day"> <xsl:param name="year"/> <xsl:call-template name="ckbk:n-th-k-day "> <xsl:with-param name="n" select="-1"/> <xsl:with-param name="k" select="1"/> <xsl:with-param name="month" select="5"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:template>
Although not a holiday, American daylight savings time can also be handled:
<xsl:template name="ckbk:day-light-savings-start"> <xsl:param name="year"/> <xsl:call-template name="ckbk:n-th-k-day "> <xsl:with-param name="n" select="1"/> <xsl:with-param name="k" select="0"/> <xsl:with-param name="month" select="4"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:template> <xsl:template name="ckbk:day-light-savings-end"> <xsl:param name="year"/> <xsl:call-template name="ckbk:n-th-k-day "> <xsl:with-param name="n" select="-1"/> <xsl:with-param name="k" select="0"/> <xsl:with-param name="month" select="10"/> <xsl:with-param name="year" select="$year"/> </xsl:call-template> </xsl:template>
Discussion
Covering every secular and religious holiday in each country and year is impossible. However, you can classify most holidays into two types: those that fall on the same day every year (e.g., U.S. Independence Day) in their respective calendars and those that are on a particular day of the week relative to the start or end of a month (e.g., U.S. Labor Day). Religious holidays are often simple within their native calendrical system, but can be more difficult to determine within another system. For example, Eastern Orthodox Christmas always falls on December 25 of the Julian calendar. Thus, in a Gregorian year, Eastern Orthodox Christmas can fall in the beginning, the end, or not appear at all. Since we cannot cover every religious holiday in every faith, please explore the references mentioned in this chapter’s introduction.
Get XSLT Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.