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