001: /*
002: * This file or a portion of this file is licensed under the terms of
003: * the Globus Toolkit Public License, found in file GTPL, or at
004: * http://www.globus.org/toolkit/download/license.html. This notice must
005: * appear in redistributions of this file, with or without modification.
006: *
007: * Redistributions of this Software, with or without modification, must
008: * reproduce the GTPL in: (1) the Software, or (2) the Documentation or
009: * some other similar material which is provided with the Software (if
010: * any).
011: *
012: * Copyright 1999-2004 University of Chicago and The University of
013: * Southern California. All rights reserved.
014: */
015:
016: package org.griphyn.common.util;
017:
018: import java.util.*;
019: import java.util.regex.*;
020: import java.text.*;
021:
022: /**
023: * Create a common interface to handle obtaining string timestamps.
024: * The difficult part is to allow for an ISO 8601 date formatting.
025: *
026: * @author Jens-S. Vöckler
027: * @author Yong Zhao
028: * @version $Revision: 50 $
029: *
030: * @see java.util.Date
031: * @see java.text.SimpleDateFormat
032: */
033: public class Currently {
034: /**
035: * This is used to format the time stamp.
036: */
037: private SimpleDateFormat m_formatter = null;
038:
039: /**
040: * Default time format, which is compact and has a timezone
041: */
042: public static final String DEFAULT_FORMAT = "yyyy.MM.dd HH:mm:ss zzz";
043:
044: /**
045: * Default ctor: Create a new instance with a default formatting
046: * string for timestamps.
047: */
048: public Currently() {
049: m_formatter = new SimpleDateFormat(DEFAULT_FORMAT);
050: }
051:
052: /**
053: * Ctor: Create a new instance with a formatting string for time stamps.
054: * @param formatString complies to {@link java.text.SimpleDateFormat}.
055: */
056: public Currently(String formatString) {
057: m_formatter = new SimpleDateFormat(formatString);
058: }
059:
060: /**
061: * Ctor: Create a new instance with a formatting string for time stamps.
062: * @param format is a description of the simple date format to use.
063: */
064: public Currently(SimpleDateFormat format) {
065: m_formatter = (SimpleDateFormat) format.clone();
066: }
067:
068: /**
069: * Accessor: Obtains the default timestamp format for all queues.
070: *
071: * @return the currently active timestamp prefix format.
072: * @see #setDateFormat( String )
073: * @see #setDateFormat( SimpleDateFormat )
074: */
075: public SimpleDateFormat getDateFormat() {
076: return m_formatter;
077: }
078:
079: /**
080: * Accessor: Sets the default timestamp format for all queues.
081: *
082: * @param format is the new timestamp prefix format.
083: * @see #setDateFormat( SimpleDateFormat )
084: * @see #getDateFormat()
085: */
086: public void setDateFormat(SimpleDateFormat format) {
087: if (format != null)
088: m_formatter = format;
089: }
090:
091: /**
092: * Accessor: Sets the default timestamp format for all queues.
093: *
094: * @param format is the new timestamp prefix format as a string.
095: * @see #setDateFormat( String )
096: * @see #getDateFormat()
097: */
098: public void setDateFormat(String format) {
099: if (format != null)
100: m_formatter = new SimpleDateFormat(format);
101: }
102:
103: /**
104: * Obtains the current time as formatted string according to
105: * the format option.
106: * @return the current time as formatted string.
107: * @see #now( Date )
108: */
109: public String now() {
110: return this .now(new Date());
111: }
112:
113: /**
114: * Obtains the current time as formatted string according to
115: * the format option.
116: * @param then is a timestamp expressed as Date.
117: * @return the current time as formatted string.
118: * @see #now()
119: */
120: public String now(Date then) {
121: return this .m_formatter.format(then);
122: }
123:
124: /**
125: * Store the regular expressions necessary to parse ISO timestamps.
126: */
127: private static final String c_expression[] = {
128: "([12][0-9]{3})-([01][0-9])-([0123][0-9])(T([012][0-9]):([0-6][0-9]):([0-6][0-9])?(\\.[0-9]+)?)?(Z|[-+][01][0-9]:?[0-6][0-9])?",
129: "([12][0-9]{3})([01][0-9])([0123][0-9])(T?([012][0-9])([0-6][0-9])([0-6][0-9])?(\\.[0-9]+)?)?(Z|[-+][01][0-9]:?[0-6][0-9])?" };
130:
131: /**
132: * Stores compiled patterns at first use, quasi-Singleton.
133: */
134: private static Pattern c_pattern[] = null;
135:
136: /**
137: * Parses one of the ISO 8601 that it produces. Note, it will not
138: * parse the full range of ISO timestamps.
139: *
140: * @param stamp is the textual timestamp representation.
141: * @return a time or <code>null</code>, if unparsable.
142: */
143: public static Date parse(String stamp) {
144: // initialize the compiled expressions once
145: if (c_pattern == null) {
146: c_pattern = new Pattern[c_expression.length];
147: for (int i = 0; i < c_expression.length; ++i)
148: c_pattern[i] = Pattern.compile(c_expression[i]);
149: }
150:
151: // match against pattern
152: for (int i = 0; i < c_expression.length; ++i) {
153: Matcher m = c_pattern[i].matcher(stamp);
154: if (m.matches()) {
155: Calendar c = Calendar.getInstance();
156: TimeZone z = TimeZone.getDefault();
157: if (m.group(9) != null && m.group(9).length() > 0) {
158: boolean utc = (Character.toUpperCase(m.group(9)
159: .charAt(0)) == 'Z');
160: if (utc)
161: z = TimeZone.getTimeZone("GMT+0");
162: else
163: z = TimeZone.getTimeZone("GMT" + m.group(9));
164: }
165:
166: c.setTimeZone(z);
167: c.set(Calendar.YEAR, Integer.parseInt(m.group(1)));
168: c.set(Calendar.MONTH, Integer.parseInt(m.group(2))
169: + (Calendar.JANUARY - 1));
170: c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(m
171: .group(3)));
172:
173: if (m.group(4).length() > 0) {
174: c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m
175: .group(5)));
176: c
177: .set(Calendar.MINUTE, Integer.parseInt(m
178: .group(6)));
179: if (m.group(7) != null && m.group(7).length() > 0)
180: c.set(Calendar.SECOND, Integer.parseInt(m
181: .group(7)));
182: if (m.group(8) != null && m.group(8).length() > 1) {
183: String millis = m.group(8).substring(1);
184: while (millis.length() < 3)
185: millis += "0";
186: millis = millis.substring(0, 3);
187: c.set(Calendar.MILLISECOND, Integer
188: .parseInt(millis));
189: }
190: }
191:
192: return c.getTime();
193: }
194: }
195:
196: // not found
197: return null;
198: }
199:
200: /**
201: * Ignores any internal date format, and tries to show a complete
202: * date/timp stamp of the current time in extended ISO 8601 format.
203: * UTC time (Zulu time) or a local timezone will be used. A sample for
204: * UTC output is 2002-04-23T02:49:58Z A sample for local zone
205: * (CDT) is 2002-04-22T21:49:58-05:00
206: *
207: * @param zuluTime returns a UTC formatted stamp, if true. Otherwise
208: * the time will be formatted according to the local zone.
209: * @return an ISO 8601 formatted date and time representation for the
210: * current time in extended format without millisecond resolution
211: * @see #iso8601( boolean, boolean, boolean, Date )
212: */
213: public static String iso8601(boolean zuluTime) {
214: return Currently.iso8601(zuluTime, true, false, new Date());
215: }
216:
217: /**
218: * Ignores any internal date format, and tries to show a complete
219: * date/timp stamp in extended ISO 8601 format. UTC time (Zulu time)
220: * or a local timezone will be used.<p>
221: *
222: * <table border=1>
223: * <tr><th>zone</th><th>format</th><th>fraction</td><th>example</th></tr>
224: * <tr><td>local</td><td>basic</td><td>integral</td><td>20020523T140427-0500</td></tr>
225: * <tr><td>UTC</td><td>basic</td><td>integral</td><td>20020523190427Z</td></tr>
226: * <tr><td>local</td><td>extd.</td><td>integral</td><td>2002-05-23T14:04:27-05:00</td></tr>
227: * <tr><td>UTC</td><td>extd.</td><td>integral</td><td>2002-05-23T19:04:27Z</td></tr>
228: * <tr><td>local</td><td>basic</td><td>millis</td><td>20020523T140427.166-0500</td></tr>
229: * <tr><td>UTC</td><td>basic</td><td>millis</td><td>20020523190427.166Z</td></tr>
230: * <tr><td>local</td><td>extd.</td><td>millis</td><td>2002-05-23T14:04:27.166-05:00</td></tr>
231: * <tr><td>UTC</td><td>extd.</td><td>millis</td><td>2002-05-23T19:04:27.166Z</td></tr>
232: * </table><p>
233: *
234: * @param zuluTime returns a UTC formatted stamp, if true. Otherwise
235: * the time will be formatted according to the local zone. Local time
236: * should be prefixed with the 'T'.
237: * @param extendedFormat will use the extended ISO 8601 format which
238: * separates the different timestamp items. If false, the basic
239: * format will be used. In UTC and basic format, the 'T' separator
240: * will be omitted.
241: * @param withMillis will put the millisecond extension into the timestamp.
242: * If false, the time will be without millisecond fraction. The separator
243: * is taken from {@link java.text.DecimalFormatSymbols#getMinusSign()},
244: * which usually is a period or a comma.
245: * @param now is a time stamp as Date.
246: * @return an ISO 8601 formatted date and time representation for
247: * the given point in time.
248: */
249: public static String iso8601(boolean zuluTime,
250: boolean extendedFormat, boolean withMillis, Date now) {
251: Calendar c = Calendar.getInstance(zuluTime ? TimeZone
252: .getTimeZone("UTC") : TimeZone.getDefault());
253: c.setTime(now);
254:
255: // set up formattting options
256: DecimalFormat nf2 = new DecimalFormat("##00");
257: DecimalFormat nf3 = new DecimalFormat("###000");
258: DecimalFormat nf4 = new DecimalFormat("####0000");
259: DecimalFormatSymbols dfs = nf2.getDecimalFormatSymbols();
260:
261: // allocate result string buffer
262: int size = extendedFormat ? (zuluTime ? 25 : 30)
263: : (zuluTime ? 21 : 25);
264: if (!withMillis)
265: size -= 4;
266: StringBuffer result = new StringBuffer(size);
267:
268: result.append(nf4.format(c.get(Calendar.YEAR)));
269: if (extendedFormat)
270: result.append('-'); // position 5
271: result.append(nf2.format(c.get(Calendar.MONTH) + 1));
272: if (extendedFormat)
273: result.append('-'); // position 8
274: result.append(nf2.format(c.get(Calendar.DAY_OF_MONTH)));
275: // generating UTC in basic format may leave out the 'T' separator
276: if (extendedFormat || !zuluTime)
277: result.append('T'); // position 11
278: result.append(nf2.format(c.get(Calendar.HOUR_OF_DAY)));
279: if (extendedFormat)
280: result.append(':'); // position 14
281: result.append(nf2.format(c.get(Calendar.MINUTE)));
282: if (extendedFormat)
283: result.append(':'); // position 17
284: result.append(nf2.format(c.get(Calendar.SECOND)));
285:
286: if (withMillis) {
287: // Though there is no explicit spec which allows a complete
288: // timestamp with milliseconds, it is implied through two
289: // levels, sigh. 5.3.4.2 allows decimal fractions with
290: // time-only stamps that have a timezone. The intro of 5.4.2
291: // allows 5.3.1.3.
292: result.append(dfs.getDecimalSeparator()); // position 20
293: result.append(nf3.format(c.get(Calendar.MILLISECOND)));
294: }
295:
296: if (zuluTime) {
297: // this is easy
298: result.append('Z');
299: } else {
300: // time zone calculations
301: int zone_offset = c.get(Calendar.ZONE_OFFSET) / 1000;
302: int save_offset = c.get(Calendar.DST_OFFSET) / 1000;
303: int diff = (zone_offset + save_offset) / 60;
304: result.append(diff < 0 ? dfs.getMinusSign() : '+'); // position 24
305:
306: if (diff < 0)
307: diff = Math.abs(diff);
308: result.append(nf2.format(diff / 60));
309: if (extendedFormat)
310: result.append(':');
311: result.append(nf2.format(diff % 60));
312: }
313:
314: return result.toString();
315: }
316:
317: }
|