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