Recipe4.10.Formatting Dates and Times


Recipe 4.10. Formatting Dates and Times

Problem

You want to format dates and times based on a format string.

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 &lt; 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 &lt; 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.

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.

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 letter-value="traditional" in xsl:number.

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

2002-12-31

format-date($d,"[Y]-[M]-[D]")

12-31-2002

format-date($d,"[M]-[D]-[Y]")

31-12-2002

format-date($d,"[D]-[M]-[Y]")

31 XII 2002

format-date($d,"[D1] [MI] [Y]")

31st December, 2002

format-date($d,"[Do] [Mn], [Y]", "en")

31 DEC 2002

format-date($d,"[D] [MN,*-3] [Y]", "en")

December 31, 2002

format-date($d,"[Mn] [D], [Y]", "en")

31 Dezember, 2002

format-date($d,"[D] [Mn], [Y]", "de")

Tisdag 31 December 2002

format-date($d,"[Fn] [D] [Mn] [Y]", "sv")

[2003-04-07]

format-date($t,"[[[Y]-[M]-[D]]]")

3:58 PM

format-time($t,"[h]:[m] [PN]", "en")

3:58:45 pm

format-time($t,"[h]:[m]:[s] [Pn]", "en")

3:58:45 PM PDT

format-time($t,"[h]:[m]:[s] [PN] [ZN,*-3]", "en")

3:58:45 o'clock PM PDT

format-time($t,"[h]:[m]:[s] o'clock [PN] [ZN,*-3]", "en")

15:58

format-time($t,"[H]:[m]")

15:58:45

format-time($t,"[H]:[m]:[s]")

15:58:45 GMT+02:00

format-time($t,"[H]:[m]:[s] [z]", "en")

15.58 Uhr GMT+02:00

format-time($t,"[H]:[m]:[s] Uhr [z]", "de")


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&#x0E51;] [Mn] [Y&#x0E51;]", "modern_Thai") <!-- Result: 

--> <!--Example: Islamic--> <xsl:date-format name="Islamic" language="ar" calendar="AH"/> format-date($d, "[D&#x0661;] [Mn] [Y&#x0661;]", "Islamic") <!-- Result:

--> <!--Example: Jewish--> <xsl:date-format name="Jewish " language="he" calendar="AM"/> format-date($d, "[D] [Mn] [Y]", "Jewish") <!--Result: 26

5763-->




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net