This project has retired. For details please refer to its
Attic page.
ISO8601 xref
1/*2 * Licensed to the Apache Software Foundation (ASF) under one or more3 * contributor license agreements. See the NOTICE file distributed with4 * this work for additional information regarding copyright ownership.5 * The ASF licenses this file to You under the Apache License, Version 2.06 * (the "License"); you may not use this file except in compliance with7 * the License. You may obtain a copy of the License at8 *9 * http://www.apache.org/licenses/LICENSE-2.010 *11 * Unless required by applicable law or agreed to in writing, software12 * distributed under the License is distributed on an "AS IS" BASIS,13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.14 * See the License for the specific language governing permissions and15 * limitations under the License.16 */17package org.apache.chemistry.opencmis.jcr.util;
1819import java.util.Calendar;
20import java.util.GregorianCalendar;
21import java.util.TimeZone;
2223/**24 * The <code>ISO8601</code> utility class provides helper methods25 * to deal with date/time formatting using a specific ISO8601-compliant26 * format (see <a href="http://www.w3.org/TR/NOTE-datetime">ISO 8601</a>).27 * <p/>28 * The currently supported format is:29 * <pre>30 * ±YYYY-MM-DDThh:mm:ss.SSSTZD31 * </pre>32 * where:33 * <pre>34 * ±YYYY = four-digit year with optional sign where values <= 0 are35 * denoting years BCE and values > 0 are denoting years CE,36 * e.g. -0001 denotes the year 2 BCE, 0000 denotes the year 1 BCE,37 * 0001 denotes the year 1 CE, and so on...38 * MM = two-digit month (01=January, etc.)39 * DD = two-digit day of month (01 through 31)40 * hh = two digits of hour (00 through 23) (am/pm NOT allowed)41 * mm = two digits of minute (00 through 59)42 * ss = two digits of second (00 through 59)43 * SSS = three digits of milliseconds (000 through 999)44 * TZD = time zone designator, Z for Zulu (i.e. UTC) or an offset from UTC45 * in the form of +hh:mm or -hh:mm46 * </pre>47 * <p/>48 * <em>Note:</em> This class is copied from org.apache.jackrabbit.util.ISO860149 */50publicfinalclass ISO8601 {
51private ISO8601() { }
5253/**54 * Parses an ISO8601-compliant date/time string.55 *56 * @param text the date/time string to be parsed57 * @return a <code>Calendar</code>, or <code>null</code> if the input could58 * not be parsed59 * @throws IllegalArgumentException if a <code>null</code> argument is passed60 */61publicstatic Calendar parse(String text) {
62if (text == null) {
63thrownew IllegalArgumentException("argument can not be null");
64 }
6566// check optional leading sign67char sign;
68int start;
69if (text.startsWith("-")) {
70 sign = '-';
71 start = 1;
72 } elseif (text.startsWith("+")) {
73 sign = '+';
74 start = 1;
75 } else {
76 sign = '+'; // no sign specified, implied '+'77 start = 0;
78 }
7980/**81 * the expected format of the remainder of the string is:82 * YYYY-MM-DDThh:mm:ss.SSSTZD83 *84 * note that we cannot use java.text.SimpleDateFormat for85 * parsing because it can't handle years <= 0 and TZD's86 */8788int year, month, day, hour, min, sec, ms;
89 String tzID;
90try {
91// year (YYYY)92 year = Integer.parseInt(text.substring(start, start + 4));
93 start += 4;
94// delimiter '-'95if (text.charAt(start) != '-') {
96returnnull;
97 }
98 start++;
99// month (MM)100 month = Integer.parseInt(text.substring(start, start + 2));
101 start += 2;
102// delimiter '-'103if (text.charAt(start) != '-') {
104returnnull;
105 }
106 start++;
107// day (DD)108 day = Integer.parseInt(text.substring(start, start + 2));
109 start += 2;
110// delimiter 'T'111if (text.charAt(start) != 'T') {
112returnnull;
113 }
114 start++;
115// hour (hh)116 hour = Integer.parseInt(text.substring(start, start + 2));
117 start += 2;
118// delimiter ':'119if (text.charAt(start) != ':') {
120returnnull;
121 }
122 start++;
123// minute (mm)124 min = Integer.parseInt(text.substring(start, start + 2));
125 start += 2;
126// delimiter ':'127if (text.charAt(start) != ':') {
128returnnull;
129 }
130 start++;
131// second (ss)132 sec = Integer.parseInt(text.substring(start, start + 2));
133 start += 2;
134// delimiter '.'135if (text.charAt(start) != '.') {
136returnnull;
137 }
138 start++;
139// millisecond (SSS)140 ms = Integer.parseInt(text.substring(start, start + 3));
141 start += 3;
142// time zone designator (Z or +00:00 or -00:00)143if (text.charAt(start) == '+' || text.charAt(start) == '-') {
144// offset to UTC specified in the format +00:00/-00:00145 tzID = "GMT" + text.substring(start);
146 } elseif (text.substring(start).equals("Z")) {
147 tzID = "GMT";
148 } else {
149// invalid time zone designator150returnnull;
151 }
152 } catch (IndexOutOfBoundsException e) {
153returnnull;
154 } catch (NumberFormatException e) {
155returnnull;
156 }
157158 TimeZone tz = TimeZone.getTimeZone(tzID);
159// verify id of returned time zone (getTimeZone defaults to "GMT")160if (!tz.getID().equals(tzID)) {
161// invalid time zone162returnnull;
163 }
164165// initialize Calendar object166 Calendar cal = Calendar.getInstance(tz);
167 cal.setLenient(false);
168// year and era169if (sign == '-' || year == 0) {
170// not CE, need to set era (BCE) and adjust year171 cal.set(Calendar.YEAR, year + 1);
172 cal.set(Calendar.ERA, GregorianCalendar.BC);
173 } else {
174 cal.set(Calendar.YEAR, year);
175 cal.set(Calendar.ERA, GregorianCalendar.AD);
176 }
177// month (0-based!)178 cal.set(Calendar.MONTH, month - 1);
179// day of month180 cal.set(Calendar.DAY_OF_MONTH, day);
181// hour182 cal.set(Calendar.HOUR_OF_DAY, hour);
183// minute184 cal.set(Calendar.MINUTE, min);
185// second186 cal.set(Calendar.SECOND, sec);
187// millisecond188 cal.set(Calendar.MILLISECOND, ms);
189190try {
191/**192 * the following call will trigger an IllegalArgumentException193 * if any of the set values are illegal or out of range194 */195 cal.getTime();
196/**197 * in addition check the validity of the year198 */199 getYear(cal);
200 } catch (IllegalArgumentException e) {
201returnnull;
202 }
203204return cal;
205 }
206207/**208 * Formats a <code>Calendar</code> value into an ISO8601-compliant209 * date/time string.210 *211 * @param cal the time value to be formatted into a date/time string.212 * @return the formatted date/time string.213 * @throws IllegalArgumentException if a <code>null</code> argument is passed214 * or the calendar cannot be represented as defined by ISO 8601 (i.e. year215 * with more than four digits).216 */217publicstatic String format(Calendar cal) throws IllegalArgumentException {
218if (cal == null) {
219thrownew IllegalArgumentException("argument can not be null");
220 }
221222/**223 * the format of the date/time string is:224 * YYYY-MM-DDThh:mm:ss.SSSTZD225 *226 * note that we cannot use java.text.SimpleDateFormat for227 * formatting because it can't handle years <= 0 and TZD's228 */229 StringBuffer buf = new StringBuffer();
230// year ([-]YYYY)231 appendZeroPaddedInt(buf, getYear(cal), 4);
232 buf.append('-');
233// month (MM)234 appendZeroPaddedInt(buf, cal.get(Calendar.MONTH) + 1, 2);
235 buf.append('-');
236// day (DD)237 appendZeroPaddedInt(buf, cal.get(Calendar.DAY_OF_MONTH), 2);
238 buf.append('T');
239// hour (hh)240 appendZeroPaddedInt(buf, cal.get(Calendar.HOUR_OF_DAY), 2);
241 buf.append(':');
242// minute (mm)243 appendZeroPaddedInt(buf, cal.get(Calendar.MINUTE), 2);
244 buf.append(':');
245// second (ss)246 appendZeroPaddedInt(buf, cal.get(Calendar.SECOND), 2);
247 buf.append('.');
248// millisecond (SSS)249 appendZeroPaddedInt(buf, cal.get(Calendar.MILLISECOND), 3);
250// time zone designator (Z or +00:00 or -00:00)251 TimeZone tz = cal.getTimeZone();
252// determine offset of timezone from UTC (incl. daylight saving)253int offset = tz.getOffset(cal.getTimeInMillis());
254if (offset == 0) {
255 buf.append('Z');
256 }
257else {
258int hours = Math.abs(offset / (60 * 1000) / 60);
259int minutes = Math.abs(offset / (60 * 1000) % 60);
260 buf.append(offset < 0 ? '-' : '+');
261 appendZeroPaddedInt(buf, hours, 2);
262 buf.append(':');
263 appendZeroPaddedInt(buf, minutes, 2);
264 }
265return buf.toString();
266 }
267268/**269 * Returns the astronomical year of the given calendar.270 *271 * @param cal a calendar instance.272 * @return the astronomical year.273 * @throws IllegalArgumentException if calendar cannot be represented as274 * defined by ISO 8601 (i.e. year with more275 * than four digits).276 */277publicstaticint getYear(Calendar cal) throws IllegalArgumentException {
278// determine era and adjust year if necessary279int year = cal.get(Calendar.YEAR);
280if (cal.isSet(Calendar.ERA)
281 && cal.get(Calendar.ERA) == GregorianCalendar.BC) {
282283// calculate year using astronomical system:284// year n BCE => astronomical year -n + 1285 year = 0 - year + 1;
286 }
287288if (year > 9999 || year < -9999) {
289thrownew IllegalArgumentException("Calendar has more than four " +
290"year digits, cannot be formatted as ISO8601: " + year);
291 }
292return year;
293 }
294295/**296 * Appends a zero-padded number to the given string buffer.297 * <p/>298 * This is an internal helper method which doesn't perform any299 * validation on the given arguments.300 *301 * @param buf String buffer to append to302 * @param n number to append303 * @param precision number of digits to append304 */305privatestaticvoid appendZeroPaddedInt(StringBuffer buf, int n, int precision) {
306if (n < 0) {
307 buf.append('-');
308 n = -n;
309 }
310311for (int exp = precision - 1; exp > 0; exp--) {
312if (n < Math.pow(10, exp)) {
313 buf.append('0');
314 } else {
315break;
316 }
317 }
318 buf.append(n);
319 }
320 }