1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 */ 4 /* 5 * Licensed to the Apache Software Foundation (ASF) under one or more 6 * contributor license agreements. See the NOTICE file distributed with 7 * this work for additional information regarding copyright ownership. 8 * The ASF licenses this file to You under the Apache License, Version 2.0 9 * (the "License"); you may not use this file except in compliance with 10 * the License. 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 package com.sun.org.apache.xalan.internal.lib; 22 23 24 import java.text.ParseException; 25 import java.text.SimpleDateFormat; 26 import java.util.Calendar; 27 import java.util.Date; 28 import java.util.Locale; 29 import java.util.TimeZone; 30 31 import com.sun.org.apache.xpath.internal.objects.XBoolean; 32 import com.sun.org.apache.xpath.internal.objects.XNumber; 33 import com.sun.org.apache.xpath.internal.objects.XObject; 34 35 /** 36 * This class contains EXSLT dates and times extension functions. 37 * It is accessed by specifying a namespace URI as follows: 38 * <pre> 39 * xmlns:datetime="http://exslt.org/dates-and-times" 40 * </pre> 41 * 42 * The documentation for each function has been copied from the relevant 43 * EXSLT Implementer page. 44 * 45 * @see <a href="http://www.exslt.org/">EXSLT</a> 46 * @xsl.usage general 47 * @LastModified: Nov 2017 48 */ 49 50 public class ExsltDatetime 51 { 52 // Datetime formats (era and zone handled separately). 53 static final String dt = "yyyy-MM-dd'T'HH:mm:ss"; 54 static final String d = "yyyy-MM-dd"; 55 static final String gym = "yyyy-MM"; 56 static final String gy = "yyyy"; 57 static final String gmd = "--MM-dd"; 58 static final String gm = "--MM--"; 59 static final String gd = "---dd"; 60 static final String t = "HH:mm:ss"; 61 static final String EMPTY_STR = ""; 62 63 /** 64 * The date:date-time function returns the current date and time as a date/time string. 65 * The date/time string that's returned must be a string in the format defined as the 66 * lexical representation of xs:dateTime in 67 * <a href="http://www.w3.org/TR/xmlschema-2/#dateTime">[3.2.7 dateTime]</a> of 68 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 69 * The date/time format is basically CCYY-MM-DDThh:mm:ss, although implementers should consult 70 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a> and 71 * <a href="http://www.iso.ch/markete/8601.pdf">[ISO 8601]</a> for details. 72 * The date/time string format must include a time zone, either a Z to indicate Coordinated 73 * Universal Time or a + or - followed by the difference between the difference from UTC 74 * represented as hh:mm. 75 */ dateTime()76 public static String dateTime() 77 { 78 Calendar cal = Calendar.getInstance(); 79 Date datetime = cal.getTime(); 80 // Format for date and time. 81 SimpleDateFormat dateFormat = new SimpleDateFormat(dt); 82 83 StringBuffer buff = new StringBuffer(dateFormat.format(datetime)); 84 // Must also include offset from UTF. 85 // Get the offset (in milliseconds). 86 int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); 87 // If there is no offset, we have "Coordinated 88 // Universal Time." 89 if (offset == 0) 90 buff.append("Z"); 91 else 92 { 93 // Convert milliseconds to hours and minutes 94 int hrs = offset/(60*60*1000); 95 // In a few cases, the time zone may be +/-hh:30. 96 int min = offset%(60*60*1000); 97 char posneg = hrs < 0? '-': '+'; 98 buff.append(posneg).append(formatDigits(hrs)).append(':').append(formatDigits(min)); 99 } 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().toString(); 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().toString(); 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 = 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 Calendar cal = Calendar.getInstance(); 661 String format = "MMMM"; 662 return getNameOrAbbrev(format); 663 } 664 665 /** 666 * The date:month-abbreviation function returns the abbreviation of the month of 667 * a date. If no argument is given, then the current local date/time, as returned 668 * by date:date-time is used the default argument. 669 * The date/time string specified as the argument is a left or right-truncated 670 * string in the format defined as the lexical representation of xs:dateTime in 671 * one of the formats defined in 672 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 673 * The permitted formats are as follows: 674 * xs:dateTime (CCYY-MM-DDThh:mm:ss) 675 * xs:date (CCYY-MM-DD) 676 * xs:gYearMonth (CCYY-MM) 677 * xs:gMonth (--MM--) 678 * If the date/time string is not in one of these formats, then an empty string ('') 679 * is returned. 680 * The result is a three-letter English month abbreviation: one of 'Jan', 'Feb', 'Mar', 681 * 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov' or 'Dec'. 682 * An implementation of this extension function in the EXSLT date namespace must conform 683 * to the behaviour described in this document. 684 */ 685 public static String monthAbbreviation(String datetimeIn) 686 throws ParseException 687 { 688 String[] edz = getEraDatetimeZone(datetimeIn); 689 String datetime = edz[1]; 690 if (datetime == null) 691 return EMPTY_STR; 692 693 String[] formatsIn = {dt, d, gym, gm}; 694 String formatOut = "MMM"; 695 return getNameOrAbbrev(datetimeIn, formatsIn, formatOut); 696 } 697 698 /** 699 * See above. 700 */ 701 public static String monthAbbreviation() 702 { 703 String format = "MMM"; 704 return getNameOrAbbrev(format); 705 } 706 707 /** 708 * The date:day-name function returns the full name of the day of the week 709 * of a date. If no argument is given, then the current local date/time, 710 * as returned by date:date-time is used the default argument. 711 * The date/time string specified as the argument is a left or right-truncated 712 * string in the format defined as the lexical representation of xs:dateTime 713 * in one of the formats defined in 714 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 715 * The permitted formats are as follows: 716 * xs:dateTime (CCYY-MM-DDThh:mm:ss) 717 * xs:date (CCYY-MM-DD) 718 * If the date/time string is not in one of these formats, then the empty string ('') 719 * is returned. 720 * The result is an English day name: one of 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 721 * 'Thursday' or 'Friday'. 722 * An implementation of this extension function in the EXSLT date namespace must conform 723 * to the behaviour described in this document. 724 */ 725 public static String dayName(String datetimeIn) 726 throws ParseException 727 { 728 String[] edz = getEraDatetimeZone(datetimeIn); 729 String datetime = edz[1]; 730 if (datetime == null) 731 return EMPTY_STR; 732 733 String[] formatsIn = {dt, d}; 734 String formatOut = "EEEE"; 735 return getNameOrAbbrev(datetimeIn, formatsIn, formatOut); 736 } 737 738 /** 739 * See above. 740 */ 741 public static String dayName() 742 { 743 String format = "EEEE"; 744 return getNameOrAbbrev(format); 745 } 746 747 /** 748 * The date:day-abbreviation function returns the abbreviation of the day 749 * of the week of a date. If no argument is given, then the current local 750 * date/time, as returned by date:date-time is used the default argument. 751 * The date/time string specified as the argument is a left or right-truncated 752 * string in the format defined as the lexical representation of xs:dateTime 753 * in one of the formats defined in 754 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 755 * The permitted formats are as follows: 756 * xs:dateTime (CCYY-MM-DDThh:mm:ss) 757 * xs:date (CCYY-MM-DD) 758 * If the date/time string is not in one of these formats, then the empty string 759 * ('') is returned. 760 * The result is a three-letter English day abbreviation: one of 'Sun', 'Mon', 'Tue', 761 * 'Wed', 'Thu' or 'Fri'. 762 * An implementation of this extension function in the EXSLT date namespace must conform 763 * to the behaviour described in this document. 764 */ 765 public static String dayAbbreviation(String datetimeIn) 766 throws ParseException 767 { 768 String[] edz = getEraDatetimeZone(datetimeIn); 769 String datetime = edz[1]; 770 if (datetime == null) 771 return EMPTY_STR; 772 773 String[] formatsIn = {dt, d}; 774 String formatOut = "EEE"; 775 return getNameOrAbbrev(datetimeIn, formatsIn, formatOut); 776 } 777 778 /** 779 * See above. 780 */ 781 public static String dayAbbreviation() 782 { 783 String format = "EEE"; 784 return getNameOrAbbrev(format); 785 } 786 787 /** 788 * Returns an array with the 3 components that a datetime input string 789 * may contain: - (for BC era), datetime, and zone. If the zone is not 790 * valid, return null for that component. 791 */ 792 private static String[] getEraDatetimeZone(String in) 793 { 794 String leader = ""; 795 String datetime = in; 796 String zone = ""; 797 if (in.charAt(0)=='-' && !in.startsWith("--")) 798 { 799 leader = "-"; // '+' is implicit , not allowed 800 datetime = in.substring(1); 801 } 802 int z = getZoneStart(datetime); 803 if (z > 0) 804 { 805 zone = datetime.substring(z); 806 datetime = datetime.substring(0, z); 807 } 808 else if (z == -2) 809 zone = null; 810 //System.out.println("'" + leader + "' " + datetime + " " + zone); 811 return new String[]{leader, datetime, zone}; 812 } 813 814 /** 815 * Get the start of zone information if the input ends 816 * with 'Z' or +/-hh:mm. If a zone string is not 817 * found, return -1; if the zone string is invalid, 818 * return -2. 819 */ getZoneStart(String datetime)820 private static int getZoneStart (String datetime) 821 { 822 if (datetime.indexOf("Z") == datetime.length()-1) 823 return datetime.length()-1; 824 else if (datetime.length() >=6 825 && datetime.charAt(datetime.length()-3) == ':' 826 && (datetime.charAt(datetime.length()-6) == '+' 827 || datetime.charAt(datetime.length()-6) == '-')) 828 { 829 try 830 { 831 SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm"); 832 dateFormat.setLenient(false); 833 Date d = dateFormat.parse(datetime.substring(datetime.length() -5)); 834 return datetime.length()-6; 835 } 836 catch (ParseException pe) 837 { 838 System.out.println("ParseException " + pe.getErrorOffset()); 839 return -2; // Invalid. 840 } 841 842 } 843 return -1; // No zone information. 844 } 845 846 /** 847 * Attempt to parse an input string with the allowed formats, returning 848 * null if none of the formats work. 849 */ testFormats(String in, String[] formats)850 private static Date testFormats (String in, String[] formats) 851 throws ParseException 852 { 853 for (int i = 0; i <formats.length; i++) 854 { 855 try 856 { 857 SimpleDateFormat dateFormat = new SimpleDateFormat(formats[i]); 858 dateFormat.setLenient(false); 859 return dateFormat.parse(in); 860 } 861 catch (ParseException pe) 862 { 863 } 864 } 865 return null; 866 } 867 868 869 /** 870 * Parse the input string and return the corresponding calendar field 871 * number. 872 */ getNumber(String in, String[] formats, int calField)873 private static double getNumber(String in, String[] formats, int calField) 874 throws ParseException 875 { 876 Calendar cal = Calendar.getInstance(); 877 cal.setLenient(false); 878 // Try the allowed formats, from longest to shortest. 879 Date date = testFormats(in, formats); 880 if (date == null) return Double.NaN; 881 cal.setTime(date); 882 return cal.get(calField); 883 } 884 885 /** 886 * Get the full name or abbreviation of the month or day. 887 */ getNameOrAbbrev(String in, String[] formatsIn, String formatOut)888 private static String getNameOrAbbrev(String in, 889 String[] formatsIn, 890 String formatOut) 891 throws ParseException 892 { 893 for (int i = 0; i <formatsIn.length; i++) // from longest to shortest. 894 { 895 try 896 { 897 SimpleDateFormat dateFormat = new SimpleDateFormat(formatsIn[i], Locale.ENGLISH); 898 dateFormat.setLenient(false); 899 Date dt = dateFormat.parse(in); 900 dateFormat.applyPattern(formatOut); 901 return dateFormat.format(dt); 902 } 903 catch (ParseException pe) 904 { 905 } 906 } 907 return ""; 908 } 909 /** 910 * Get the full name or abbreviation for the current month or day 911 * (no input string). 912 */ getNameOrAbbrev(String format)913 private static String getNameOrAbbrev(String format) 914 { 915 Calendar cal = Calendar.getInstance(); 916 SimpleDateFormat dateFormat = new SimpleDateFormat(format, Locale.ENGLISH); 917 return dateFormat.format(cal.getTime()); 918 } 919 920 /** 921 * The date:format-date function formats a date/time according to a pattern. 922 * <p> 923 * The first argument to date:format-date specifies the date/time to be 924 * formatted. It must be right or left-truncated date/time strings in one of 925 * the formats defined in 926 * <a href="http://www.w3.org/TR/xmlschema-2/">[XML Schema Part 2: Datatypes]</a>. 927 * The permitted formats are as follows: 928 * <ul> 929 * <li>xs:dateTime (CCYY-MM-DDThh:mm:ss) 930 * <li>xs:date (CCYY-MM-DD) 931 * <li>xs:time (hh:mm:ss) 932 * <li>xs:gYearMonth (CCYY-MM) 933 * <li>xs:gYear (CCYY) 934 * <li>xs:gMonthDay (--MM-DD) 935 * <li>xs:gMonth (--MM--) 936 * <li>xs:gDay (---DD) 937 * </ul> 938 * The second argument is a string that gives the format pattern used to 939 * format the date. The format pattern must be in the syntax specified by 940 * the JDK 1.1 SimpleDateFormat class. The format pattern string is 941 * interpreted as described for the JDK 1.1 SimpleDateFormat class. 942 * <p> 943 * If the date/time format is right-truncated (i.e. in a format other than 944 * xs:time, or xs:dateTime) then any missing components are assumed to be as 945 * follows: if no month is specified, it is given a month of 01; if no day 946 * is specified, it is given a day of 01; if no time is specified, it is 947 * given a time of 00:00:00. 948 * <p> 949 * If the date/time format is left-truncated (i.e. xs:time, xs:gMonthDay, 950 * xs:gMonth or xs:gDay) and the format pattern has a token that uses a 951 * component that is missing from the date/time format used, then that token 952 * is replaced with an empty string ('') within the result. 953 * 954 * The author is Helg Bredow (helg.bredow@kalido.com) 955 */ formatDate(String dateTime, String pattern)956 public static String formatDate(String dateTime, String pattern) 957 { 958 final String yearSymbols = "Gy"; 959 final String monthSymbols = "M"; 960 final String daySymbols = "dDEFwW"; 961 TimeZone timeZone; 962 String zone; 963 964 // Get the timezone information if it was supplied and modify the 965 // dateTime so that SimpleDateFormat will understand it. 966 if (dateTime.endsWith("Z") || dateTime.endsWith("z")) 967 { 968 timeZone = TimeZone.getTimeZone("GMT"); 969 dateTime = dateTime.substring(0, dateTime.length()-1) + "GMT"; 970 zone = "z"; 971 } 972 else if ((dateTime.length() >= 6) 973 && (dateTime.charAt(dateTime.length()-3) == ':') 974 && ((dateTime.charAt(dateTime.length()-6) == '+') 975 || (dateTime.charAt(dateTime.length()-6) == '-'))) 976 { 977 String offset = dateTime.substring(dateTime.length()-6); 978 979 if ("+00:00".equals(offset) || "-00:00".equals(offset)) 980 { 981 timeZone = TimeZone.getTimeZone("GMT"); 982 } 983 else 984 { 985 timeZone = TimeZone.getTimeZone("GMT" + offset); 986 } 987 zone = "z"; 988 // Need to adjust it since SimpleDateFormat requires GMT+hh:mm but 989 // we have +hh:mm. 990 dateTime = dateTime.substring(0, dateTime.length()-6) + "GMT" + offset; 991 } 992 else 993 { 994 // Assume local time. 995 timeZone = TimeZone.getDefault(); 996 zone = ""; 997 // Leave off the timezone since SimpleDateFormat will assume local 998 // time if time zone is not included. 999 } 1000 String[] formats = {dt + zone, d, gym, gy}; 1001 1002 // Try the time format first. We need to do this to prevent 1003 // SimpleDateFormat from interpreting a time as a year. i.e we just need 1004 // to check if it's a time before we check it's a year. 1005 try 1006 { 1007 SimpleDateFormat inFormat = new SimpleDateFormat(t + zone); 1008 inFormat.setLenient(false); 1009 Date d= inFormat.parse(dateTime); 1010 SimpleDateFormat outFormat = new SimpleDateFormat(strip 1011 (yearSymbols + monthSymbols + daySymbols, pattern)); 1012 outFormat.setTimeZone(timeZone); 1013 return outFormat.format(d); 1014 } 1015 catch (ParseException pe) 1016 { 1017 } 1018 1019 // Try the right truncated formats. 1020 for (int i = 0; i < formats.length; i++) 1021 { 1022 try 1023 { 1024 SimpleDateFormat inFormat = new SimpleDateFormat(formats[i]); 1025 inFormat.setLenient(false); 1026 Date d = inFormat.parse(dateTime); 1027 SimpleDateFormat outFormat = new SimpleDateFormat(pattern); 1028 outFormat.setTimeZone(timeZone); 1029 return outFormat.format(d); 1030 } 1031 catch (ParseException pe) 1032 { 1033 } 1034 } 1035 1036 // Now try the left truncated ones. The Java format() function doesn't 1037 // return the correct strings in this case. We strip any pattern 1038 // symbols that shouldn't be output so that they are not defaulted to 1039 // inappropriate values in the output. 1040 try 1041 { 1042 SimpleDateFormat inFormat = new SimpleDateFormat(gmd); 1043 inFormat.setLenient(false); 1044 Date d = inFormat.parse(dateTime); 1045 SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols, pattern)); 1046 outFormat.setTimeZone(timeZone); 1047 return outFormat.format(d); 1048 } 1049 catch (ParseException pe) 1050 { 1051 } 1052 try 1053 { 1054 SimpleDateFormat inFormat = new SimpleDateFormat(gm); 1055 inFormat.setLenient(false); 1056 Date d = inFormat.parse(dateTime); 1057 SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols, pattern)); 1058 outFormat.setTimeZone(timeZone); 1059 return outFormat.format(d); 1060 } 1061 catch (ParseException pe) 1062 { 1063 } 1064 try 1065 { 1066 SimpleDateFormat inFormat = new SimpleDateFormat(gd); 1067 inFormat.setLenient(false); 1068 Date d = inFormat.parse(dateTime); 1069 SimpleDateFormat outFormat = new SimpleDateFormat(strip(yearSymbols + monthSymbols, pattern)); 1070 outFormat.setTimeZone(timeZone); 1071 return outFormat.format(d); 1072 } 1073 catch (ParseException pe) 1074 { 1075 } 1076 return EMPTY_STR; 1077 } 1078 1079 /** 1080 * Strips occurrences of the given character from a date format pattern. 1081 * @param symbols list of symbols to strip. 1082 * @param pattern 1083 * @return 1084 */ strip(String symbols, String pattern)1085 private static String strip(String symbols, String pattern) 1086 { 1087 int quoteSemaphore = 0; 1088 int i = 0; 1089 StringBuffer result = new StringBuffer(pattern.length()); 1090 1091 while (i < pattern.length()) 1092 { 1093 char ch = pattern.charAt(i); 1094 if (ch == '\'') 1095 { 1096 // Assume it's an openening quote so simply copy the quoted 1097 // text to the result. There is nothing to strip here. 1098 int endQuote = pattern.indexOf('\'', i + 1); 1099 if (endQuote == -1) 1100 { 1101 endQuote = pattern.length(); 1102 } 1103 result.append(pattern.substring(i, endQuote)); 1104 i = endQuote++; 1105 } 1106 else if (symbols.indexOf(ch) > -1) 1107 { 1108 // The char needs to be stripped. 1109 i++; 1110 } 1111 else 1112 { 1113 result.append(ch); 1114 i++; 1115 } 1116 } 1117 return result.toString(); 1118 } 1119 1120 } 1121