1. | Date conversions |
| Michael Kay
> My problem is that I receive for example a date in a concatenated
> format:
>
> Ex: 20041207
>
> And I have to retrieve it in a readable format through my xslt-fo
> transformation.
>
> Ex. 12 July 2004
Are you sure that date is 12 July and not 7 December? Probably a stupid
question, it's just that I've never seen dates written as YYYYDDMM before.
I'll assume the value is in $d.
XSLT 2.0:
format-date(
xs:date(
concat(
substring($d,1,4),
'-',
substring($d,7,2),
'-',
substring($d,5,2))),
'[D01] [MNn] [Y0001]')
|
2. | Adding dates |
| Mike Kay and Jeni Tennison
>I want to add 6 months to a date (in xslt2.0) but cant find any
>references on how to do this. After much googling I tried something
>like:
>
> <xsl:value-of select="xs:date($reviewDate) +
>xs:duration('P0Y0M6DT0H00M')"/>
>
>But the processor (Saxon 8.0) says that date arithmetic is not
>supported on xs:duration.
Mike Kay replied:
xs:date($reviewDate) + xdt:yearMonthDuration('P6D')
Jeni expanded.
xs:duration is defined by XML Schema as a number of years, months,
days, hours, minutes and seconds. The trouble is that it's not possible
to give definitive answers to comparisons when you have durations that
contain both years or months and days, hours, minutes or seconds. For
example, which is longer, one month or 30 days? In technical terms,
xs:duration is "partially ordered".
XPath 2.0 dodges the question by only supporting two "totally ordered"
subtypes of xs:duration: durations that only involve years and months
(xdt:yearMonthDuration) and durations that only involve days, hours,
minutes and seconds (xdt:dayTimeDuration).
If you want to add a duration that combines the two subtypes, you need
to add one and then the other, as in:
(current-date() + xdt:yearMonthDuration('P1Y')) +
xdt:dayTimeDuration('P1D')
(Of course, the order in which you add them makes a difference to the
result...)
Note that you don't have to include a component if there are 0 of
them: if you have 0 days in a duration, you don't need to put '0D' in
it. |
3. | Dates: milliseconds since 1970 |
| Michael Kay
The main thing I need the Javascript for is the formatting of dates.
The XML produced by our database expresses dates as milliseconds since
January 1, 1970. The only formatting technique suggested by the
documentation for our database is to use Javascript's Date class. If
there's a way to format these dates in native XSLT, or anything
besides embedding the Javascript into the code, I haven't found it
yet.
Easy enough in XPath 2.0: <xsl:value-of select='xs:dateTime("1970-01-01T00:00:00") +
$in * xs:dayTimeDuration("PT0.001S")'/>
where $in is the current datetime. |
4. | Date addition |
| Chris Ferrier
This function adds a number of days to a given date
xmlns:dp="http://www.dpawson.co.uk"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xdt="http://www.w3.org/2005/02/xpath-datatypes"
<xsl:template match="/">
<xsl:value-of select="dp:date-add($date1,3)"/>
</xsl:template>
<xsl:variable name="one-day" select="xdt:dayTimeDuration('P1D')"/>
<xsl:variable name="date1" select="xs:dateTime('2005-03-01T12:00:00')"/>
<xsl:function name="dp:date-add">
<xsl:param name="date" as="xs:dateTime"/>
<xsl:param name="days" as="xs:integer"/>
<xsl:variable name="day" select="$date + ($one-day * $days)"/>
<xsl:value-of select="format-dateTime($day,'[Y,*-2]-[M,2]-[D,2]')"/>
</xsl:function>
|
5. | Date difference |
| Jeni Tennison
> I have two elements:
> <due date>2005-04-05</due date>
> <actual arrival>2005-04-11T22:21:30</actual arrival>
>
> what is the function to display the day-time difference?
Assuming you're using XSLT 2.0 and that the elements are actually
called <due-date> and <actual-arrival>, you can use the minus operator
as follows:
xs:dateTime(actual-arrival) - xs:dateTime(xs:date(due-date))
to get the xdt:dayTimeDuration P6DT22H21M30S (6 days, 22 hours, 21
minutes, 30 seconds). You can then use the days-from-duration(),
hours-from-duration() etc. functions to extract the values of the
individual components from that duration in order to make something
readable.
Note that the xs:date() constructor constructs an xs:date from the
due date, and the xs:dateTime() constructor casts this to a
xs:dateTime by adding 00:00:00 as the time. |
6. | Comparing within a date range |
| Michael Kay
> I want to know how to get particular employees covered between
> two different joining dates (for example Fromdate and Todate ).
>
> What is the Xpath / XSL expression.
If the source documents have a schema, and the attributes are defined in the
schema as being of type xs:date, and you are using a schema-aware XSLT 2.0
processor, then you can just write
employee[@FromDate le $d and @ToDate ge $d]
If you are not using a schema, but you are using XSLT 2.0, and your dates
are stored in the ISO 8601 format that XML Schema uses, then you can still
do the same kind of comparison but you need to do a little more work:
employee[xs:date(@FromDate) le $d and xs:date(@ToDate) ge $d]
If you are not using a schema and your dates are in some other format, or if
you are using XSLT 1.0, then you will need to do even more work, but this
then depends entirely on the format you are starting with. |
7. | Last n in date order |
| David Carlisle I write a
weblog using the atom format. Each entry is dated. I wanted a recent
index of the last 12 entries, by date order. I had found half a
solution. David C showed me the way to the rest, which I think has a
couple of learning points. I certainly learned from it. My source
document had a feed element as the dcoument element, and numerous
entry elements which needed sorting by date. Firstly, the
context of a template. The context is the set of all matching nodes,
the position that of the current node within the context. This is
helpful here. Next, I needed to calculate if an entry was within the last
thirty days. Then I needed to determine if its position() was greater
than that of the last 12 entries. Firstly the date difference
<xsl:variable name="now" select="current-dateTime()"/>
<xsl:function name="d:dateDiff" as="xs:boolean">
<xsl:param name="oldDate" as="xs:dateTime"/>
<xsl:param name="newDate" as="xs:dateTime"/>
<xsl:param name="count" as="xs:integer"/>
<xsl:variable name="diff" select="xs:integer(days-from-duration($newDate - $oldDate))"/>
<xsl:variable name="diffx" select="days-from-duration($newDate - $oldDate)"/>
<!--
<xsl:message>
diff <xsl:value-of select="$diff"/> [<xsl:value-of select="$diff < $count"/>]
</xsl:message>
-->
<xsl:sequence select="$diff < $count"/>
</xsl:function>
The date parameter is taken from the entry, the current date
from a global variable. The function returns true if the difference in
dates is less than the number passed as the third parameter. Next, I needed a template. David suggested.
<xsl:template
match="a:entry[d:dateDiff(a:published,$now,30)]
[position() > (last() - 12)]">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="a:title | a:link| a:summary|a:updated|a:id"/>
</xsl:copy>
</xsl:template>
<xsl:template match="a:entry"/>
All it does is copy input to output, with the processing being
done in the match pattern The first predicate uses the above function
to determine if the entry is 'recent' (within the last 30 days). The
second checks that the position() of the node within all entry nodes
is within the last 12. Simple! For some! Nice one David |
8. | Date Time addition |
| Abel Braaksma (In xslt2.0)I would like to increment a xs:dateTime
variable dt by one month. And have the result in (or
somehow converted to) xs:dateTime.
How can I achieve this. I think that you should explicitly convert the first argument to
xs:dateTime, or it must be explicitly declared. This works, and returns
"2004-12-15T15:12:10" (which is 1 day and 1 hour later than the supplied
date time): <xsl:value-of select="xs:dateTime('2004-12-14T14:12:10') +
xs:dayTimeDuration('P1DT1H')"></xsl:value-of> |
9. | Date comparisons |
| Abel Braaksma
> < So I'm dealing with a schema which has an attribute:
> <
> < <xs:simpleType name="time">
> < <xs:union memberTypes="
> < xs:gYear xs:gYearMonth xs:date xs:dateTime" />
> < </xs:simpleType>
> <
> < I had to write a function to let someone compare two of these 'time'
> < simple types. It appeared as though the only way to do comparisions
> < was with equal times, so that mean converting everything to xs:dateTime.
>
True, but that does not need to be as hard as you think. I tried
applying date:parse-date from EXSLT, but it was not supported by Saxon
(not sure you use Saxon, though). But I may have a simpler solution for you. > The details of what I need are:
>
> 'time' may be xs:dateTime, xs:date, xs:gYear, xs:gYearMonth
> $a is a 'time' in a document
> $b is a 'time' provided by a user
>
> Given $a and $b I want to allow for eq, lt, le, gt, ge
> comparision of $a and $b)
>
You created quite a complex function. I today pondered a bit about your
question and I came up with the following solution. I requires that you
do not add gMonth, gDay etc to the list, because it employs padding the
date.
What I do is, in fact, the same as you: make the value a dateTime and
compare on the resulting value. However, I don't believe you need so
many, long functions. In fact, you only need 8 lines, including the
declaration. Here's what I tried: 1) Take a "picture" string, I call it a "null-date": 0001-01-01T00:00:00.000
2) Apply that to your stringized date/datetime/year/yearmonth
3) Convert the result.
For instance, take a gYear value "2006", use the null-date as overlay
(see function below) and you get "2006-01-01T00:00:00.000", which is a
comparable dateTime. The same applies to gYearMonth "2006-06" which
would become "2006-06-01T00:00:00.000", and so on.
Here's the whole template. Call it on itself and it will show you that
it works for your four datatypes. You can use any operation on the
result of running date:parse-date. I used the EXSLT date namespace, but
you are of course free to choose your own. <xsl:stylesheet version="2.0"
xmlns:xs = "http://www.w3.org/2001/XMLSchema";
xmlns:date="http://exslt.org/dates-and-times";
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
<xsl:output method="text"/>
<xsl:template match="/" >
<xsl:variable name="dateTime"
as="xs:dateTime">2006-12-30T16:12:22.234</xsl:variable>
<xsl:variable name="date" as="xs:date">2006-12-30</xsl:variable>
<xsl:variable name="yearMonth"
as="xs:gYearMonth">2006-12</xsl:variable>
<xsl:variable name="year" as="xs:gYear">2006</xsl:variable>
dateTime : <xsl:value-of select="$dateTime" />
date : <xsl:value-of select="$date" />
yearMonth : <xsl:value-of select="$yearMonth" />
year : <xsl:value-of select="$year" />
Conv dateTime : <xsl:value-of
select="date:parse-date($dateTime)" />
Conv date : <xsl:value-of select="date:parse-date($date)" />
Conv yearMonth : <xsl:value-of
select="date:parse-date($yearMonth)" />
Conv year : <xsl:value-of select="date:parse-date($year)" />
$year lt $date : <xsl:value-of select="date:parse-date($year) gt
date:parse-date($date)" />
</xsl:template>
<xsl:function name="date:parse-date" as="xs:dateTime">
<xsl:param name="parsable-date" />
<xsl:variable
name="null-date">0001-01-01T00:00:00.000</xsl:variable>
<xsl:variable name="padded-date" select="
concat(xs:string($parsable-date),
substring($null-date
, string-length(xs:string($parsable-date)) + 1
, 40))" />
<xsl:value-of select="xs:dateTime(substring($padded-date, 1,
23))" />
</xsl:function>
</xsl:stylesheet>
|
10. | Adjust time zone |
| Abel Braaksma
I am using XSLT 2and trying to call the function
"adjust-time-to-timezone"
Here's code that works with XSLT 2.0
<xsl:variable name="MY_TIME" select=" '20:30:10' "/>
<xsl:variable name="DURATION" select=" '-PT6H' "/>
<xsl:value-of
select="adjust-time-to-timezone(xs:time($MY_TIME),
xs:dayTimeDuration($DURATION))"/> The namespace 'xs' must be bound to "http://www.w3.org/2001/XMLSchema"; |
11. | First Saturday in the month! |
| David Carlisle This for example returns the first saturday of the current month. $ saxon8 -it main saturday.xsl <?xml version="1.0" encoding="UTF-8"?>
today: 2007-01-30Z
1st of month 2007-01-01Z
1st day month Monday
1st saturday of month : 2007-01-06Z <xsl:stylesheet version="2.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema";
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";>
<xsl:template name="main">
today: <xsl:value-of select="current-date()"/>
<xsl:variable name="today" select="current-date()"/>
<xsl:variable name="fom"
select="current-date()-
xs:dayTimeDuration(concat('P',day-from-date(current-date())-1,'DT0S'))"/>
1st of month <xsl:value-of select="$fom"/>
1st day month <xsl:value-of select="format-date($fom,'[F]')"/>
1st saturday of month : <xsl:value-of select="(for $d in 0 to 6
return
$fom + xs:dayTimeDuration(concat('P',$d,'DT0S')))[format-date(.,'[F]')='Saturday']
"/>
</xsl:template>
</xsl:stylesheet>
|
12. | Testing for dates, xslt 2 |
| Florent Georges
> <xsl:variable name="is-date" select="if(DATE_PACKED_XSD
> castable as xs:date) then '1' else('0')" />
Why not simply:
<xsl:variable name="is-date" as="xs:boolean"
select="DATE_PACKED_XSD castable as xs:date"/>
> <xsl:choose>
> <xsl:when test="$is-date = '1'">
> ... some processing here
> </xsl:when>
> <xsl:otherwise>
> ... some different processing here
> </xsl:otherwise>
> </xsl:choose>
> What I expected to happen was that when the stylesheet
> processed a DATE_PACKED_XSD element that could not be cast
> as a date ($is-date = 0), processing would pass to the
> <xsl:otherwise> branch.
> What did happen was this:
> stylesheet.xslt:423: Fatal Error! Invalid date "208 2-01-1
> 2". Non-numeric component
> What am I misapprehending about "castable as"?
Nothing IMHO. Could you please show a little example that
reproduces the problem? ~> cat xslt/tests/castable-as.xsl
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
xmlns:xs="http://www.w3.org/2001/XMLSchema";
version="2.0">
<xsl:output method="text"/>
<xsl:template name="main">
<xsl:value-of select="
for $d in ('2006-08-29', '208 2-01-1 2') return
$d castable as xs:date"/>
</xsl:template>
</xsl:transform>
~> saxon -it main xslt/tests/castable-as.xsl
true false
|
13. | Subtract two dates |
| Michael Kay
>I want to know if it is possible to calculate the number of months
>between two dates. For example xs:duration(xs:date('2009-09-23') -
>current-date())
One possible algorithm, which you can easily implement as a function, is (year-from-date($d1) - year-from-date($d2))*12
+ (month-from-date($d1) - month-from-date($d2))
- if (days-from-date($d1) ge days-from-date($d2)) then 0 else 1 That works reasonably for $d2 < $d1. To handle $d1 < $d2
one approach is to use the same formula as above, another is to return
-($d2 - $d1). For 2007-05-31 - 2007-06-01 one approach gives you -1,
the other gives you 0.
Another algorithm is to ignore the day entirely, and just use the year and
month components. Or you can divide the day by the number of days in that
month. |