001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: ExsltDatetime.java 1225761 2011-12-30 06:04:51Z mrglavas $
020     */
021    
022    package org.apache.xalan.lib;
023    
024    
025    import java.text.ParseException;
026    import java.text.SimpleDateFormat;
027    import java.util.Calendar;
028    import java.util.Date;
029    import java.util.Locale;
030    import java.util.TimeZone;
031    
032    import org.apache.xpath.objects.XBoolean;
033    import org.apache.xpath.objects.XNumber;
034    import org.apache.xpath.objects.XObject;
035    
036    /**
037     * This class contains EXSLT dates and times extension functions.
038     * It is accessed by specifying a namespace URI as follows:
039     * <pre>
040     *    xmlns:datetime="http://exslt.org/dates-and-times"
041     * </pre>
042     * 
043     * The documentation for each function has been copied from the relevant
044     * EXSLT Implementer page.
045     * 
046     * @see <a href="http://www.exslt.org/">EXSLT</a>
047     * @xsl.usage general
048     */
049    
050    public class ExsltDatetime
051    {
052        // Datetime formats (era and zone handled separately).
053        static final String dt = "yyyy-MM-dd'T'HH:mm:ss";
054        static final String d = "yyyy-MM-dd";
055        static final String gym = "yyyy-MM";
056        static final String gy = "yyyy";
057        static final String gmd = "--MM-dd";
058        static final String gm = "--MM--";
059        static final String gd = "---dd";
060        static final String t = "HH:mm:ss";
061        static final String EMPTY_STR = "";
062    
063        /**
064         * The date:date-time function returns the current date and time as a date/time string. 
065         * The date/time string that's returned must be a string in the format defined as the 
066         * lexical representation of xs:dateTime in 
067         * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">[3.2.7 dateTime]</a> of
068         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>.
069         * The date/time format is basically CCYY-MM-DDThh:mm:ss, although implementers should consult
070         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a> and 
071         * <a href="http://www.iso.ch/markete/8601.pdf">[ISO 8601]</a> for details.
072         * The date/time string format must include a time zone, either a Z to indicate Coordinated 
073         * Universal Time or a + or - followed by the difference between the difference from UTC 
074         * represented as hh:mm. 
075         */
076        public static String dateTime()
077        {
078          Calendar cal = Calendar.getInstance();
079          Date datetime = cal.getTime();
080          // Format for date and time.
081          SimpleDateFormat dateFormat = new SimpleDateFormat(dt);
082          
083          StringBuffer buff = new StringBuffer(dateFormat.format(datetime));
084          // Must also include offset from UTF.
085          // Get the offset (in milliseconds).
086          int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
087          // If there is no offset, we have "Coordinated
088          // Universal Time."
089          if (offset == 0)
090            buff.append('Z');
091          else
092          {
093            // Convert milliseconds to hours and minutes
094            int hrs = offset/(60*60*1000);
095            // In a few cases, the time zone may be +/-hh:30.
096            int min = offset%(60*60*1000);
097            char posneg = hrs < 0? '-': '+';
098            buff.append(posneg + formatDigits(hrs) + ':' + formatDigits(min));
099          }
100          return buff.toString();
101        }
102        
103        /**
104         * Represent the hours and minutes with two-digit strings.
105         * @param q hrs or minutes.
106         * @return two-digit String representation of hrs or minutes.
107         */
108        private static String formatDigits(int q)
109        {
110          String dd = String.valueOf(Math.abs(q));
111          return dd.length() == 1 ? '0' + dd : dd;
112        }
113    
114        /**
115         * The date:date function returns the date specified in the date/time string given 
116         * as the argument. If no argument is given, then the current local date/time, as 
117         * returned by date:date-time is used as a default argument. 
118         * The date/time string that's returned must be a string in the format defined as the 
119         * lexical representation of xs:dateTime in 
120         * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">[3.2.7 dateTime]</a> of
121         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>.
122         * If the argument is not in either of these formats, date:date returns an empty string (''). 
123         * The date/time format is basically CCYY-MM-DDThh:mm:ss, although implementers should consult 
124         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a> and 
125         * <a href="http://www.iso.ch/markete/8601.pdf">[ISO 8601]</a> for details. 
126         * The date is returned as a string with a lexical representation as defined for xs:date in 
127         * [3.2.9 date] of [XML Schema Part 2: Datatypes]. The date format is basically CCYY-MM-DD, 
128         * although implementers should consult [XML Schema Part 2: Datatypes] and [ISO 8601] for details.
129         * If no argument is given or the argument date/time specifies a time zone, then the date string 
130         * format must include a time zone, either a Z to indicate Coordinated Universal Time or a + or - 
131         * followed by the difference between the difference from UTC represented as hh:mm. If an argument 
132         * is specified and it does not specify a time zone, then the date string format must not include 
133         * a time zone. 
134         */
135        public static String date(String datetimeIn)
136          throws ParseException
137        {
138          String[] edz = getEraDatetimeZone(datetimeIn);
139          String leader = edz[0];
140          String datetime = edz[1];
141          String zone = edz[2];
142          if (datetime == null || zone == null) 
143            return EMPTY_STR;
144                        
145          String[] formatsIn = {dt, d};
146          String formatOut = d;
147          Date date = testFormats(datetime, formatsIn);
148          if (date == null) return EMPTY_STR;
149          
150          SimpleDateFormat dateFormat = new SimpleDateFormat(formatOut);
151          dateFormat.setLenient(false);
152          String dateOut = dateFormat.format(date);      
153          if (dateOut.length() == 0)
154              return EMPTY_STR;
155          else        
156            return (leader + dateOut + zone);
157        }
158        
159        
160        /**
161         * See above.
162         */
163        public static String date()
164        {
165          String datetime = dateTime();
166          String date = datetime.substring(0, datetime.indexOf("T"));
167          String zone = datetime.substring(getZoneStart(datetime));
168          return (date + zone);
169        }
170        
171        /**
172         * The date:time function returns the time specified in the date/time string given 
173         * as the argument. If no argument is given, then the current local date/time, as 
174         * returned by date:date-time is used as a default argument. 
175         * The date/time string that's returned must be a string in the format defined as the 
176         * lexical representation of xs:dateTime in 
177         * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">[3.2.7 dateTime]</a> of
178         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
179         * If the argument string is not in this format, date:time returns an empty string (''). 
180         * The date/time format is basically CCYY-MM-DDThh:mm:ss, although implementers should consult 
181         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a> and 
182         * <a href="http://www.iso.ch/markete/8601.pdf">[ISO 8601]</a> for details.
183         * The date is returned as a string with a lexical representation as defined for xs:time in 
184         * <a href="http://www.w3.org/TR/xmlschema-2/#time">[3.2.8 time]</a> of [XML Schema Part 2: Datatypes].
185         * The time format is basically hh:mm:ss, although implementers should consult [XML Schema Part 2: 
186         * Datatypes] and [ISO 8601] for details. 
187         * If no argument is given or the argument date/time specifies a time zone, then the time string 
188         * format must include a time zone, either a Z to indicate Coordinated Universal Time or a + or - 
189         * followed by the difference between the difference from UTC represented as hh:mm. If an argument 
190         * is specified and it does not specify a time zone, then the time string format must not include 
191         * a time zone. 
192         */
193        public static String time(String timeIn)
194          throws ParseException      
195        {
196          String[] edz = getEraDatetimeZone(timeIn);
197          String time = edz[1];
198          String zone = edz[2];
199          if (time == null || zone == null) 
200            return EMPTY_STR;
201                        
202          String[] formatsIn = {dt, d, t};
203          String formatOut =  t;
204          Date date = testFormats(time, formatsIn);
205          if (date == null) return EMPTY_STR;
206          SimpleDateFormat dateFormat = new SimpleDateFormat(formatOut);
207          String out = dateFormat.format(date);
208          return (out + zone);
209        }
210    
211        /**
212         * See above.
213         */
214        public static String time()
215        {
216          String datetime = dateTime();
217          String time = datetime.substring(datetime.indexOf("T")+1);
218          
219              // The datetime() function returns the zone on the datetime string.  If we
220              // append it, we get the zone substring duplicated.
221              // Fix for JIRA 2013
222    
223          // String zone = datetime.substring(getZoneStart(datetime));      
224          // return (time + zone);
225          return (time);
226        } 
227           
228        /**
229         * The date:year function returns the year of a date as a number. If no 
230         * argument is given, then the current local date/time, as returned by 
231         * date:date-time is used as a default argument.
232         * The date/time string specified as the first argument must be a right-truncated 
233         * string in the format defined as the lexical representation of xs:dateTime in one 
234         * of the formats defined in 
235         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>.
236         * The permitted formats are as follows: 
237         *   xs:dateTime (CCYY-MM-DDThh:mm:ss) 
238         *   xs:date (CCYY-MM-DD) 
239         *   xs:gYearMonth (CCYY-MM) 
240         *   xs:gYear (CCYY) 
241         * If the date/time string is not in one of these formats, then NaN is returned. 
242         */
243        public static double year(String datetimeIn)
244          throws ParseException
245        {
246          String[] edz = getEraDatetimeZone(datetimeIn);
247          boolean ad = edz[0].length() == 0; // AD (Common Era -- empty leader)
248          String datetime = edz[1];
249          if (datetime == null) 
250            return Double.NaN;
251          
252          String[] formats = {dt, d, gym, gy};
253          double yr = getNumber(datetime, formats, Calendar.YEAR);
254          if (ad || yr == Double.NaN)
255            return yr;
256          else
257            return -yr;
258        }
259         
260        /**
261         * See above.
262         */
263        public static double year()
264        {
265          Calendar cal = Calendar.getInstance();
266          return cal.get(Calendar.YEAR);
267        }
268        
269        /**
270         * The date:month-in-year function returns the month of a date as a number. If no argument 
271         * is given, then the current local date/time, as returned by date:date-time is used 
272         * as a default argument. 
273         * The date/time string specified as the first argument is a left or right-truncated 
274         * string in the format defined as the lexical representation of xs:dateTime in one of 
275         * the formats defined in 
276         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>.
277         * The permitted formats are as follows: 
278         *    xs:dateTime (CCYY-MM-DDThh:mm:ss) 
279         *    xs:date (CCYY-MM-DD) 
280         *    xs:gYearMonth (CCYY-MM)
281         *    xs:gMonth (--MM--) 
282         *    xs:gMonthDay (--MM-DD)
283         * If the date/time string is not in one of these formats, then NaN is returned. 
284         */
285        public static double monthInYear(String datetimeIn)
286          throws ParseException
287        {
288          String[] edz = getEraDatetimeZone(datetimeIn);
289          String datetime = edz[1];
290          if (datetime == null)
291            return Double.NaN;      
292          
293          String[] formats = {dt, d, gym, gm, gmd};
294          return getNumber(datetime, formats, Calendar.MONTH) + 1;
295        }
296        
297        /**
298         * See above.
299         */
300        public static double monthInYear()
301        {      
302          Calendar cal = Calendar.getInstance();
303          return cal.get(Calendar.MONTH) + 1;
304       }
305        
306        /**
307         * The date:week-in-year function returns the week of the year as a number. If no argument 
308         * is given, then the current local date/time, as returned by date:date-time is used as the 
309         * default argument. For the purposes of numbering, counting follows ISO 8601: week 1 in a year 
310         * is the week containing the first Thursday of the year, with new weeks beginning on a Monday. 
311         * The date/time string specified as the argument is a right-truncated string in the format 
312         * defined as the lexical representation of xs:dateTime in one of the formats defined in 
313         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. The 
314         * permitted formats are as follows: 
315         *    xs:dateTime (CCYY-MM-DDThh:mm:ss) 
316         *    xs:date (CCYY-MM-DD) 
317         * If the date/time string is not in one of these formats, then NaN is returned. 
318         */
319        public static double weekInYear(String datetimeIn)
320          throws ParseException
321        {
322          String[] edz = getEraDatetimeZone(datetimeIn);
323          String datetime = edz[1];
324          if (datetime == null) 
325            return Double.NaN;      
326          
327          String[] formats = {dt, d};
328          return getNumber(datetime, formats, Calendar.WEEK_OF_YEAR);
329        }
330            
331        /**
332         * See above.
333         */
334        public static double weekInYear()
335        {
336           Calendar cal = Calendar.getInstance();
337          return cal.get(Calendar.WEEK_OF_YEAR);
338       }
339    
340        /**
341         * The date:day-in-year function returns the day of a date in a year 
342         * as a number. If no argument is given, then the current local
343         * date/time, as returned by date:date-time is used the default argument.
344         * The date/time string specified as the argument is a right-truncated 
345         * string in the format defined as the lexical representation of xs:dateTime
346         * in one of the formats defined in 
347         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
348         * The permitted formats are as follows:
349         *     xs:dateTime (CCYY-MM-DDThh:mm:ss)
350         *     xs:date (CCYY-MM-DD) 
351         * If the date/time string is not in one of these formats, then NaN is returned. 
352         */
353        public static double dayInYear(String datetimeIn)
354          throws ParseException
355        {
356          String[] edz = getEraDatetimeZone(datetimeIn);
357          String datetime = edz[1];
358          if (datetime == null) 
359            return Double.NaN;            
360          
361          String[] formats = {dt, d};
362          return getNumber(datetime, formats, Calendar.DAY_OF_YEAR);
363        }
364        
365        /**
366         * See above.
367         */
368        public static double dayInYear()
369        {
370           Calendar cal = Calendar.getInstance();
371          return cal.get(Calendar.DAY_OF_YEAR);
372       }
373        
374    
375        /**
376         * The date:day-in-month function returns the day of a date as a number. 
377         * If no argument is given, then the current local date/time, as returned 
378         * by date:date-time is used the default argument. 
379         * The date/time string specified as the argument is a left or right-truncated 
380         * string in the format defined as the lexical representation of xs:dateTime 
381         * in one of the formats defined in 
382         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
383         * The permitted formats are as follows: 
384         *      xs:dateTime (CCYY-MM-DDThh:mm:ss) 
385         *      xs:date (CCYY-MM-DD) 
386         *      xs:gMonthDay (--MM-DD) 
387         *      xs:gDay (---DD) 
388         * If the date/time string is not in one of these formats, then NaN is returned. 
389         */
390        public static double dayInMonth(String datetimeIn)
391          throws ParseException
392        {
393          String[] edz = getEraDatetimeZone(datetimeIn);
394          String datetime = edz[1];
395          String[] formats = {dt, d, gmd, gd};
396          double day = getNumber(datetime, formats, Calendar.DAY_OF_MONTH);
397          return day;
398        }
399        
400        /**
401         * See above.
402         */
403        public static double dayInMonth()
404        {
405          Calendar cal = Calendar.getInstance();
406          return cal.get(Calendar.DAY_OF_MONTH);
407       }
408        
409        /**
410         * The date:day-of-week-in-month function returns the day-of-the-week 
411         * in a month of a date as a number (e.g. 3 for the 3rd Tuesday in May). 
412         * If no argument is given, then the current local date/time, as returned 
413         * by date:date-time is used the default argument. 
414         * The date/time string specified as the argument is a right-truncated string
415         * in the format defined as the lexical representation of xs:dateTime in one 
416         * of the formats defined in 
417         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
418         * The permitted formats are as follows: 
419         *      xs:dateTime (CCYY-MM-DDThh:mm:ss) 
420         *      xs:date (CCYY-MM-DD) 
421         * If the date/time string is not in one of these formats, then NaN is returned. 
422         */
423        public static double dayOfWeekInMonth(String datetimeIn)
424          throws ParseException
425        {
426          String[] edz = getEraDatetimeZone(datetimeIn);
427          String datetime = edz[1];
428          if (datetime == null) 
429            return Double.NaN;            
430    
431          String[] formats =  {dt, d};
432          return getNumber(datetime, formats, Calendar.DAY_OF_WEEK_IN_MONTH);
433        }
434        
435        /**
436         * See above.
437         */
438        public static double dayOfWeekInMonth()
439        {
440           Calendar cal = Calendar.getInstance();
441          return cal.get(Calendar.DAY_OF_WEEK_IN_MONTH);
442       }
443          
444        
445        /**
446         * The date:day-in-week function returns the day of the week given in a 
447         * date as a number. If no argument is given, then the current local date/time, 
448         * as returned by date:date-time is used the default argument. 
449         * The date/time string specified as the argument is a right-truncated string 
450         * in the format defined as the lexical representation of xs:dateTime in one 
451         * of the formats defined in 
452         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
453         * The permitted formats are as follows: 
454         *      xs:dateTime (CCYY-MM-DDThh:mm:ss) 
455         *      xs:date (CCYY-MM-DD) 
456         * If the date/time string is not in one of these formats, then NaN is returned. 
457                                The numbering of days of the week starts at 1 for Sunday, 2 for Monday and so on up to 7 for Saturday.  
458         */
459        public static double dayInWeek(String datetimeIn)
460          throws ParseException
461        {
462          String[] edz = getEraDatetimeZone(datetimeIn);
463          String datetime = edz[1];
464          if (datetime == null) 
465            return Double.NaN;            
466    
467          String[] formats = {dt, d};
468          return getNumber(datetime, formats, Calendar.DAY_OF_WEEK);
469        }
470        
471        /**
472         * See above.
473         */
474        public static double dayInWeek()
475        {
476           Calendar cal = Calendar.getInstance();
477          return cal.get(Calendar.DAY_OF_WEEK);
478       }        
479    
480        /**
481         * The date:hour-in-day function returns the hour of the day as a number. 
482         * If no argument is given, then the current local date/time, as returned 
483         * by date:date-time is used the default argument. 
484         * The date/time string specified as the argument is a right-truncated 
485         * string  in the format defined as the lexical representation of xs:dateTime
486         * in one of the formats defined in 
487         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
488         * The permitted formats are as follows: 
489         *     xs:dateTime (CCYY-MM-DDThh:mm:ss) 
490         *     xs:time (hh:mm:ss) 
491         * If the date/time string is not in one of these formats, then NaN is returned. 
492         */
493        public static double hourInDay(String datetimeIn)
494          throws ParseException
495        {
496          String[] edz = getEraDatetimeZone(datetimeIn);
497          String datetime = edz[1];
498          if (datetime == null) 
499            return Double.NaN;            
500          
501          String[] formats = {dt, t};
502          return getNumber(datetime, formats, Calendar.HOUR_OF_DAY);
503        }
504        
505        /**
506         * See above.
507         */
508        public static double hourInDay()
509        {
510           Calendar cal = Calendar.getInstance();
511          return cal.get(Calendar.HOUR_OF_DAY);
512       }
513        
514        /**
515         * The date:minute-in-hour function returns the minute of the hour 
516         * as a number. If no argument is given, then the current local
517         * date/time, as returned by date:date-time is used the default argument. 
518         * The date/time string specified as the argument is a right-truncated 
519         * string in the format defined as the lexical representation of xs:dateTime
520         * in one of the formats defined in 
521         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
522         * The permitted formats are as follows: 
523         *      xs:dateTime (CCYY-MM-DDThh:mm:ss) 
524         *      xs:time (hh:mm:ss) 
525         * If the date/time string is not in one of these formats, then NaN is returned. 
526         */
527        public static double minuteInHour(String datetimeIn)
528          throws ParseException
529        {
530          String[] edz = getEraDatetimeZone(datetimeIn);
531          String datetime = edz[1];
532          if (datetime == null) 
533            return Double.NaN;            
534          
535          String[] formats = {dt,t};
536          return getNumber(datetime, formats, Calendar.MINUTE);
537        }    
538        
539        /**
540         * See above.
541         */
542       public static double minuteInHour()
543        {
544           Calendar cal = Calendar.getInstance();
545          return cal.get(Calendar.MINUTE);
546       }    
547    
548        /**
549         * The date:second-in-minute function returns the second of the minute 
550         * as a number. If no argument is given, then the current local 
551         * date/time, as returned by date:date-time is used the default argument. 
552         * The date/time string specified as the argument is a right-truncated 
553         * string in the format defined as the lexical representation of xs:dateTime
554         * in one of the formats defined in 
555         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
556         * The permitted formats are as follows: 
557         *      xs:dateTime (CCYY-MM-DDThh:mm:ss) 
558         *      xs:time (hh:mm:ss) 
559         * If the date/time string is not in one of these formats, then NaN is returned. 
560         */
561        public static double secondInMinute(String datetimeIn)
562          throws ParseException
563        {
564          String[] edz = getEraDatetimeZone(datetimeIn);
565          String datetime = edz[1];
566          if (datetime == null) 
567            return Double.NaN;            
568          
569          String[] formats = {dt, t};
570          return getNumber(datetime, formats, Calendar.SECOND);
571        }
572    
573        /**
574         * See above.
575         */
576        public static double secondInMinute()
577        {
578           Calendar cal = Calendar.getInstance();
579          return cal.get(Calendar.SECOND);
580        }
581           
582        /**
583         * The date:leap-year function returns true if the year given in a date 
584         * is a leap year. If no argument is given, then the current local
585         * date/time, as returned by date:date-time is used as a default argument. 
586         * The date/time string specified as the first argument must be a 
587         * right-truncated string in the format defined as the lexical representation
588         * of xs:dateTime in one of the formats defined in 
589         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
590         * The permitted formats are as follows: 
591         *    xs:dateTime (CCYY-MM-DDThh:mm:ss) 
592         *    xs:date (CCYY-MM-DD) 
593         *    xs:gYearMonth (CCYY-MM) 
594         *    xs:gYear (CCYY) 
595         * If the date/time string is not in one of these formats, then NaN is returned. 
596         */
597        public static XObject leapYear(String datetimeIn)
598          throws ParseException
599        {
600          String[] edz = getEraDatetimeZone(datetimeIn);
601          String datetime = edz[1];
602          if (datetime == null) 
603            return new XNumber(Double.NaN);            
604                
605          String[] formats = {dt, d, gym, gy};
606          double dbl = getNumber(datetime, formats, Calendar.YEAR);
607          if (dbl == Double.NaN) 
608            return new XNumber(Double.NaN);
609          int yr = (int)dbl;
610          return new XBoolean(yr % 400 == 0 || (yr % 100 != 0 && yr % 4 == 0));
611        }
612        
613        /**
614         * See above.
615         */
616        public static boolean leapYear()
617        {
618          Calendar cal = Calendar.getInstance();
619          int yr = (int)cal.get(Calendar.YEAR);
620          return (yr % 400 == 0 || (yr % 100 != 0 && yr % 4 == 0));      
621        }    
622           
623        /**
624         * The date:month-name function returns the full name of the month of a date. 
625         * If no argument is given, then the current local date/time, as returned by 
626         * date:date-time is used the default argument. 
627         * The date/time string specified as the argument is a left or right-truncated 
628         * string in the format defined as the lexical representation of xs:dateTime in
629         *  one of the formats defined in 
630         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
631         * The permitted formats are as follows: 
632         *    xs:dateTime (CCYY-MM-DDThh:mm:ss) 
633         *    xs:date (CCYY-MM-DD) 
634         *    xs:gYearMonth (CCYY-MM) 
635         *    xs:gMonth (--MM--) 
636         * If the date/time string is not in one of these formats, then an empty string ('') 
637         * is returned. 
638         * The result is an English month name: one of 'January', 'February', 'March', 
639         * 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November' 
640         * or 'December'. 
641         */
642        public static String monthName(String datetimeIn)
643          throws ParseException
644        {
645          String[] edz = getEraDatetimeZone(datetimeIn);
646          String datetime = edz[1];
647          if (datetime == null) 
648            return EMPTY_STR;
649          
650          String[] formatsIn = {dt, d, gym, gm};
651          String formatOut = "MMMM";
652          return getNameOrAbbrev(datetimeIn, formatsIn, formatOut);    
653        }
654        
655        /**
656         * See above.
657         */
658        public static String monthName()
659        {
660          String format = "MMMM";
661          return getNameOrAbbrev(format);  
662        }
663            
664        /**
665         * The date:month-abbreviation function returns the abbreviation of the month of 
666         * a date. If no argument is given, then the current local date/time, as returned 
667         * by date:date-time is used the default argument. 
668         * The date/time string specified as the argument is a left or right-truncated 
669         * string in the format defined as the lexical representation of xs:dateTime in 
670         * one of the formats defined in 
671         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
672         * The permitted formats are as follows: 
673         *    xs:dateTime (CCYY-MM-DDThh:mm:ss) 
674         *    xs:date (CCYY-MM-DD) 
675         *    xs:gYearMonth (CCYY-MM) 
676         *    xs:gMonth (--MM--) 
677         * If the date/time string is not in one of these formats, then an empty string ('') 
678         * is returned. 
679         * The result is a three-letter English month abbreviation: one of 'Jan', 'Feb', 'Mar', 
680         * 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov' or 'Dec'. 
681         * An implementation of this extension function in the EXSLT date namespace must conform 
682         * to the behaviour described in this document. 
683         */
684        public static String monthAbbreviation(String datetimeIn)
685          throws ParseException
686        {
687          String[] edz = getEraDatetimeZone(datetimeIn);
688          String datetime = edz[1];
689          if (datetime == null) 
690            return EMPTY_STR;
691          
692          String[] formatsIn = {dt, d, gym, gm};
693          String formatOut = "MMM";
694          return getNameOrAbbrev(datetimeIn, formatsIn, formatOut);
695        }
696        
697        /**
698         * See above.
699         */
700        public static String monthAbbreviation()
701        {
702          String format = "MMM";
703          return getNameOrAbbrev(format);  
704        }
705            
706        /**
707         * The date:day-name function returns the full name of the day of the week 
708         * of a date.  If no argument is given, then the current local date/time, 
709         * as returned by date:date-time is used the default argument. 
710         * The date/time string specified as the argument is a left or right-truncated 
711         * string in the format defined as the lexical representation of xs:dateTime 
712         * in one of the formats defined in 
713         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
714         * The permitted formats are as follows: 
715         *     xs:dateTime (CCYY-MM-DDThh:mm:ss) 
716         *     xs:date (CCYY-MM-DD) 
717         * If the date/time string is not in one of these formats, then the empty string ('') 
718         * is returned. 
719         * The result is an English day name: one of 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 
720         * 'Thursday' or 'Friday'. 
721         * An implementation of this extension function in the EXSLT date namespace must conform 
722         * to the behaviour described in this document. 
723         */
724        public static String dayName(String datetimeIn)
725          throws ParseException
726        {
727          String[] edz = getEraDatetimeZone(datetimeIn);
728          String datetime = edz[1];
729          if (datetime == null) 
730            return EMPTY_STR;
731                
732          String[] formatsIn = {dt, d};
733          String formatOut = "EEEE";
734          return getNameOrAbbrev(datetimeIn, formatsIn, formatOut);    
735        }
736        
737        /**
738         * See above.
739         */
740        public static String dayName()
741        {
742          String format = "EEEE";
743          return getNameOrAbbrev(format);        
744        }    
745        
746        /**
747         * The date:day-abbreviation function returns the abbreviation of the day 
748         * of the week of a date. If no argument is given, then the current local 
749         * date/time, as returned  by date:date-time is used the default argument. 
750         * The date/time string specified as the argument is a left or right-truncated 
751         * string in the format defined as the lexical representation of xs:dateTime 
752         * in one of the formats defined in 
753         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
754         * The permitted formats are as follows: 
755         *     xs:dateTime (CCYY-MM-DDThh:mm:ss) 
756         *     xs:date (CCYY-MM-DD) 
757         * If the date/time string is not in one of these formats, then the empty string 
758         * ('') is returned. 
759         * The result is a three-letter English day abbreviation: one of 'Sun', 'Mon', 'Tue', 
760         * 'Wed', 'Thu' or 'Fri'. 
761         * An implementation of this extension function in the EXSLT date namespace must conform 
762         * to the behaviour described in this document. 
763         */
764        public static String dayAbbreviation(String datetimeIn)
765          throws ParseException
766        {
767          String[] edz = getEraDatetimeZone(datetimeIn);
768          String datetime = edz[1];
769          if (datetime == null) 
770            return EMPTY_STR;            
771          
772          String[] formatsIn = {dt, d};
773          String formatOut = "EEE";
774          return getNameOrAbbrev(datetimeIn, formatsIn, formatOut);
775        }
776        
777        /**
778         * See above.
779         */
780        public static String dayAbbreviation()
781        {
782          String format = "EEE";
783          return getNameOrAbbrev(format);              
784        }
785        
786        /**
787         * Returns an array with the 3 components that a datetime input string 
788         * may contain: - (for BC era), datetime, and zone. If the zone is not
789         * valid, return null for that component.
790         */
791        private static String[] getEraDatetimeZone(String in)
792        {
793          String leader = "";
794          String datetime = in;
795          String zone = "";
796          if (in.charAt(0)=='-' && !in.startsWith("--"))
797          {
798            leader = "-"; //  '+' is implicit , not allowed
799            datetime = in.substring(1);
800          }
801          int z = getZoneStart(datetime);
802          if (z > 0)
803          {
804            zone = datetime.substring(z);
805            datetime = datetime.substring(0, z);
806          }
807          else if (z == -2)
808            zone = null;
809          //System.out.println("'" + leader + "' " + datetime + " " + zone);
810          return new String[]{leader, datetime, zone};  
811        }    
812        
813        /**
814         * Get the start of zone information if the input ends
815         * with 'Z' or +/-hh:mm. If a zone string is not
816         * found, return -1; if the zone string is invalid,
817         * return -2.
818         */
819        private static int getZoneStart (String datetime)
820        {
821          if (datetime.indexOf('Z') == datetime.length()-1)
822            return datetime.length()-1;
823          else if (datetime.length() >=6 
824                    && datetime.charAt(datetime.length()-3) == ':'
825                    && (datetime.charAt(datetime.length()-6) == '+' 
826                        || datetime.charAt(datetime.length()-6) == '-'))                        
827          {
828            try
829            {
830              SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm");
831              dateFormat.setLenient(false);
832              Date d = dateFormat.parse(datetime.substring(datetime.length() -5));
833              return datetime.length()-6;
834            }
835            catch (ParseException pe)
836            {
837              System.out.println("ParseException " + pe.getErrorOffset());
838              return -2; // Invalid.
839            }
840    
841          }
842            return -1; // No zone information.
843        }
844        
845        /**
846         * Attempt to parse an input string with the allowed formats, returning
847         * null if none of the formats work.
848         */
849        private static Date testFormats (String in, String[] formats)
850          throws ParseException
851        {
852          for (int i = 0; i <formats.length; i++)
853          {
854            try
855            {
856              SimpleDateFormat dateFormat = new SimpleDateFormat(formats[i]);
857              dateFormat.setLenient(false);          
858              return dateFormat.parse(in);
859            }
860            catch (ParseException pe)
861            {
862            }
863          }
864          return null;
865        }
866        
867        
868        /**
869         * Parse the input string and return the corresponding calendar field
870         * number.
871         */
872        private static double getNumber(String in, String[] formats, int calField)
873          throws ParseException
874        {
875          Calendar cal = Calendar.getInstance();
876          cal.setLenient(false);
877          // Try the allowed formats, from longest to shortest.
878          Date date = testFormats(in, formats);
879          if (date == null) return Double.NaN;
880          cal.setTime(date);
881          return cal.get(calField);
882        }    
883         
884        /**
885         *  Get the full name or abbreviation of the month or day.
886         */
887        private static String getNameOrAbbrev(String in, 
888                                             String[] formatsIn,
889                                             String formatOut)
890          throws ParseException
891        {
892          for (int i = 0; i <formatsIn.length; i++) // from longest to shortest.
893          {
894            try
895            {
896              SimpleDateFormat dateFormat = new SimpleDateFormat(formatsIn[i], Locale.ENGLISH);
897              dateFormat.setLenient(false);
898              Date dt = dateFormat.parse(in);          
899              dateFormat.applyPattern(formatOut);
900              return dateFormat.format(dt);
901            }
902            catch (ParseException pe)
903            {
904            }
905          }
906          return "";
907        }
908        /**
909         * Get the full name or abbreviation for the current month or day 
910         * (no input string).
911         */
912        private static String getNameOrAbbrev(String format)
913        {
914          Calendar cal = Calendar.getInstance();
915          SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH);
916          return dateFormat.format(cal.getTime());
917        }
918    
919        /**
920         * The date:format-date function formats a date/time according to a pattern.
921         * <p>
922         * The first argument to date:format-date specifies the date/time to be 
923         * formatted. It must be right or left-truncated date/time strings in one of 
924         * the formats defined in 
925         * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 
926         * The permitted formats are as follows: 
927         * <ul>
928         * <li>xs:dateTime (CCYY-MM-DDThh:mm:ss) 
929         * <li>xs:date (CCYY-MM-DD) 
930         * <li>xs:time (hh:mm:ss) 
931         * <li>xs:gYearMonth (CCYY-MM) 
932         * <li>xs:gYear (CCYY) 
933         * <li>xs:gMonthDay (--MM-DD) 
934         * <li>xs:gMonth (--MM--) 
935         * <li>xs:gDay (---DD)
936         * </ul>
937         * The second argument is a string that gives the format pattern used to 
938         * format the date. The format pattern must be in the syntax specified by 
939         * the JDK 1.1 SimpleDateFormat class. The format pattern string is 
940         * interpreted as described for the JDK 1.1 SimpleDateFormat class. 
941         * <p>
942         * If the date/time format is right-truncated (i.e. in a format other than 
943         * xs:time, or xs:dateTime) then any missing components are assumed to be as 
944         * follows: if no month is specified, it is given a month of 01; if no day 
945         * is specified, it is given a day of 01; if no time is specified, it is 
946         * given a time of 00:00:00. 
947         * <p>
948         * If the date/time format is left-truncated (i.e. xs:time, xs:gMonthDay, 
949         * xs:gMonth or xs:gDay) and the format pattern has a token that uses a 
950         * component that is missing from the date/time format used, then that token 
951         * is replaced with an empty string ('') within the result.
952         * 
953         * The author is Helg Bredow (helg.bredow@kalido.com)
954         */
955        public static String formatDate(String dateTime, String pattern)
956        {
957            final String yearSymbols = "Gy";
958            final String monthSymbols = "M";
959            final String daySymbols = "dDEFwW";
960            TimeZone timeZone;
961            String zone;
962    
963            // Get the timezone information if it was supplied and modify the 
964            // dateTime so that SimpleDateFormat will understand it.
965            if (dateTime.endsWith("Z") || dateTime.endsWith("z"))
966            {
967                timeZone = TimeZone.getTimeZone("GMT");
968                dateTime = dateTime.substring(0, dateTime.length()-1) + "GMT";
969                zone = "z";
970            }
971            else if ((dateTime.length() >= 6) 
972                     && (dateTime.charAt(dateTime.length()-3) == ':') 
973                     && ((dateTime.charAt(dateTime.length()-6) == '+') 
974                        || (dateTime.charAt(dateTime.length()-6) == '-')))
975            {
976                String offset = dateTime.substring(dateTime.length()-6);
977                
978                if ("+00:00".equals(offset) || "-00:00".equals(offset))
979                {
980                    timeZone = TimeZone.getTimeZone("GMT");
981                }
982                else
983                {
984                    timeZone = TimeZone.getTimeZone("GMT" + offset);
985                }
986                zone = "z";
987                // Need to adjust it since SimpleDateFormat requires GMT+hh:mm but
988                // we have +hh:mm.
989                dateTime = dateTime.substring(0, dateTime.length()-6) + "GMT" + offset;
990            }
991            else
992            {
993                // Assume local time.
994                timeZone = TimeZone.getDefault();
995                zone = "";
996                // Leave off the timezone since SimpleDateFormat will assume local
997                // time if time zone is not included.
998            }
999            String[] formats = {dt + zone, d, gym, gy};
1000            
1001            // Try the time format first. We need to do this to prevent 
1002            // SimpleDateFormat from interpreting a time as a year. i.e we just need
1003            // to check if it's a time before we check it's a year.
1004            try
1005            {
1006                SimpleDateFormat inFormat = new SimpleDateFormat(t + zone);
1007                inFormat.setLenient(false);
1008                Date d= inFormat.parse(dateTime);
1009                SimpleDateFormat outFormat = new SimpleDateFormat(strip
1010                    (yearSymbols + monthSymbols + daySymbols, pattern));
1011                outFormat.setTimeZone(timeZone);
1012                return outFormat.format(d);
1013            }
1014            catch (ParseException pe)
1015            {
1016            }
1017            
1018            // Try the right truncated formats.
1019            for (int i = 0; i < formats.length; i++)
1020            {
1021                try
1022                {
1023                    SimpleDateFormat inFormat = new SimpleDateFormat(formats[i]);
1024                    inFormat.setLenient(false);
1025                    Date d = inFormat.parse(dateTime);
1026                    SimpleDateFormat outFormat = new SimpleDateFormat(pattern);
1027                    outFormat.setTimeZone(timeZone);
1028                    return outFormat.format(d);
1029                }
1030                catch (ParseException pe)
1031                {
1032                }
1033            }
1034            
1035            // Now try the left truncated ones. The Java format() function doesn't
1036            // return the correct strings in this case. We strip any pattern 
1037            // symbols that shouldn't be output so that they are not defaulted to 
1038            // inappropriate values in the output.
1039            try
1040            {
1041                SimpleDateFormat inFormat = new SimpleDateFormat(gmd);
1042                inFormat.setLenient(false);          
1043                Date d = inFormat.parse(dateTime);
1044                SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols, pattern));
1045                outFormat.setTimeZone(timeZone);
1046                return outFormat.format(d);
1047            }
1048            catch (ParseException pe)
1049            {
1050            }
1051            try
1052            {
1053                SimpleDateFormat inFormat = new SimpleDateFormat(gm);
1054                inFormat.setLenient(false);
1055                Date d = inFormat.parse(dateTime);
1056                SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols, pattern));
1057                outFormat.setTimeZone(timeZone);
1058                return outFormat.format(d);
1059            }
1060            catch (ParseException pe)
1061            {
1062            }
1063            try
1064            {
1065                SimpleDateFormat inFormat = new SimpleDateFormat(gd);
1066                inFormat.setLenient(false);
1067                Date d = inFormat.parse(dateTime);
1068                SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols + monthSymbols, pattern));
1069                outFormat.setTimeZone(timeZone);
1070                return outFormat.format(d);
1071            }
1072            catch (ParseException pe)
1073            {
1074            }
1075            return EMPTY_STR;
1076        }
1077        
1078        /**
1079         * Strips occurrences of the given character from a date format pattern.
1080         * @param symbols list of symbols to strip.
1081         * @param pattern
1082         * @return
1083         */
1084        private static String strip(String symbols, String pattern)
1085        {
1086            int i = 0;
1087            StringBuffer result = new StringBuffer(pattern.length());
1088    
1089            while (i < pattern.length())
1090            {
1091                char ch = pattern.charAt(i);
1092                if (ch == '\'')
1093                {
1094                    // Assume it's an opening quote so simply copy the quoted 
1095                    // text to the result. There is nothing to strip here.
1096                    int endQuote = pattern.indexOf('\'', i + 1);
1097                    if (endQuote == -1)
1098                    {
1099                        endQuote = pattern.length();
1100                    }
1101                    result.append(pattern.substring(i, endQuote));
1102                    i = endQuote++;
1103                }
1104                else if (symbols.indexOf(ch) > -1)
1105                {
1106                    // The char needs to be stripped.
1107                    i++;
1108                }
1109                else
1110                {
1111                    result.append(ch);
1112                    i++;
1113                }
1114            }
1115            return result.toString();
1116        }
1117    
1118    }