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 }