001: // ========================================================================
002: // Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014:
015: package org.mortbay.util;
016:
017: import java.text.DateFormatSymbols;
018: import java.text.SimpleDateFormat;
019: import java.util.Date;
020: import java.util.Locale;
021: import java.util.TimeZone;
022:
023: /* ------------------------------------------------------------ */
024: /** Date Format Cache.
025: * Computes String representations of Dates and caches
026: * the results so that subsequent requests within the same minute
027: * will be fast.
028: *
029: * Only format strings that contain either "ss" or "ss.SSS" are
030: * handled.
031: *
032: * The timezone of the date may be included as an ID with the "zzz"
033: * format string or as an offset with the "ZZZ" format string.
034: *
035: * If consecutive calls are frequently very different, then this
036: * may be a little slower than a normal DateFormat.
037: *
038: * @author Kent Johnson <KJohnson@transparent.com>
039: * @author Greg Wilkins (gregw)
040: */
041:
042: public class DateCache {
043: private static long __hitWindow = 60 * 60;
044: private static long __MaxMisses = 10;
045:
046: private String _formatString;
047: private String _tzFormatString;
048: private SimpleDateFormat _tzFormat;
049:
050: private String _minFormatString;
051: private SimpleDateFormat _minFormat;
052:
053: private String _secFormatString;
054: private String _secFormatString0;
055: private String _secFormatString1;
056:
057: private boolean _millis = false;
058: private long _misses = 0;
059: private long _lastMinutes = -1;
060: private long _lastSeconds = -1;
061: private String _lastResult = null;
062:
063: private Locale _locale = null;
064: private DateFormatSymbols _dfs = null;
065:
066: /* ------------------------------------------------------------ */
067: /** Constructor.
068: * Make a DateCache that will use a default format. The default format
069: * generates the same results as Date.toString().
070: */
071: public DateCache() {
072: this ("EEE MMM dd HH:mm:ss zzz yyyy");
073: getFormat().setTimeZone(TimeZone.getDefault());
074: }
075:
076: /* ------------------------------------------------------------ */
077: /** Constructor.
078: * Make a DateCache that will use the given format
079: */
080: public DateCache(String format) {
081: _formatString = format;
082: setTimeZone(TimeZone.getDefault());
083:
084: }
085:
086: /* ------------------------------------------------------------ */
087: public DateCache(String format, Locale l) {
088: _formatString = format;
089: _locale = l;
090: setTimeZone(TimeZone.getDefault());
091: }
092:
093: /* ------------------------------------------------------------ */
094: public DateCache(String format, DateFormatSymbols s) {
095: _formatString = format;
096: _dfs = s;
097: setTimeZone(TimeZone.getDefault());
098: }
099:
100: /* ------------------------------------------------------------ */
101: /** Set the timezone.
102: * @param tz TimeZone
103: */
104: public void setTimeZone(TimeZone tz) {
105: setTzFormatString(tz);
106: if (_locale != null) {
107: _tzFormat = new SimpleDateFormat(_tzFormatString, _locale);
108: _minFormat = new SimpleDateFormat(_minFormatString, _locale);
109: } else if (_dfs != null) {
110: _tzFormat = new SimpleDateFormat(_tzFormatString, _dfs);
111: _minFormat = new SimpleDateFormat(_minFormatString, _dfs);
112: } else {
113: _tzFormat = new SimpleDateFormat(_tzFormatString);
114: _minFormat = new SimpleDateFormat(_minFormatString);
115: }
116: _tzFormat.setTimeZone(tz);
117: _minFormat.setTimeZone(tz);
118: _lastSeconds = -1;
119: _lastMinutes = -1;
120: }
121:
122: /* ------------------------------------------------------------ */
123: public TimeZone getTimeZone() {
124: return _tzFormat.getTimeZone();
125: }
126:
127: /* ------------------------------------------------------------ */
128: /** Set the timezone.
129: * @param timeZoneId TimeZoneId the ID of the zone as used by
130: * TimeZone.getTimeZone(id)
131: */
132: public void setTimeZoneID(String timeZoneId) {
133: setTimeZone(TimeZone.getTimeZone(timeZoneId));
134: }
135:
136: /* ------------------------------------------------------------ */
137: private void setTzFormatString(final TimeZone tz) {
138: int zIndex = _formatString.indexOf("ZZZ");
139: if (zIndex >= 0) {
140: String ss1 = _formatString.substring(0, zIndex);
141: String ss2 = _formatString.substring(zIndex + 3);
142: int tzOffset = tz.getRawOffset();
143:
144: StringBuffer sb = new StringBuffer(
145: _formatString.length() + 10);
146: sb.append(ss1);
147: sb.append("'");
148: if (tzOffset >= 0)
149: sb.append('+');
150: else {
151: tzOffset = -tzOffset;
152: sb.append('-');
153: }
154:
155: int raw = tzOffset / (1000 * 60); // Convert to seconds
156: int hr = raw / 60;
157: int min = raw % 60;
158:
159: if (hr < 10)
160: sb.append('0');
161: sb.append(hr);
162: if (min < 10)
163: sb.append('0');
164: sb.append(min);
165: sb.append('\'');
166:
167: sb.append(ss2);
168: _tzFormatString = sb.toString();
169: } else
170: _tzFormatString = _formatString;
171: setMinFormatString();
172: }
173:
174: /* ------------------------------------------------------------ */
175: private void setMinFormatString() {
176: int i = _tzFormatString.indexOf("ss.SSS");
177: int l = 6;
178: if (i >= 0)
179: _millis = true;
180: else {
181: i = _tzFormatString.indexOf("ss");
182: l = 2;
183: }
184:
185: // Build a formatter that formats a second format string
186: // Have to replace @ with ' later due to bug in SimpleDateFormat
187: String ss1 = _tzFormatString.substring(0, i);
188: String ss2 = _tzFormatString.substring(i + l);
189: _minFormatString = ss1 + (_millis ? "'ss.SSS'" : "'ss'") + ss2;
190: }
191:
192: /* ------------------------------------------------------------ */
193: /** Format a date according to our stored formatter.
194: * @param inDate
195: * @return Formatted date
196: */
197: public synchronized String format(Date inDate) {
198: return format(inDate.getTime());
199: }
200:
201: /* ------------------------------------------------------------ */
202: /** Format a date according to our stored formatter.
203: * @param inDate
204: * @return Formatted date
205: */
206: public synchronized String format(long inDate) {
207: long seconds = inDate / 1000;
208:
209: // Is it not suitable to cache?
210: if (seconds < _lastSeconds || _lastSeconds > 0
211: && seconds > _lastSeconds + __hitWindow) {
212: // It's a cache miss
213: _misses++;
214: if (_misses < __MaxMisses) {
215: Date d = new Date(inDate);
216: return _tzFormat.format(d);
217: }
218: } else if (_misses > 0)
219: _misses--;
220:
221: // Check if we are in the same second
222: // and don't care about millis
223: if (_lastSeconds == seconds && !_millis)
224: return _lastResult;
225:
226: Date d = new Date(inDate);
227:
228: // Check if we need a new format string
229: long minutes = seconds / 60;
230: if (_lastMinutes != minutes) {
231: _lastMinutes = minutes;
232: _secFormatString = _minFormat.format(d);
233:
234: int i;
235: int l;
236: if (_millis) {
237: i = _secFormatString.indexOf("ss.SSS");
238: l = 6;
239: } else {
240: i = _secFormatString.indexOf("ss");
241: l = 2;
242: }
243: _secFormatString0 = _secFormatString.substring(0, i);
244: _secFormatString1 = _secFormatString.substring(i + l);
245: }
246:
247: // Always format if we get here
248: _lastSeconds = seconds;
249: StringBuffer sb = new StringBuffer(_secFormatString.length());
250: synchronized (sb) {
251: sb.append(_secFormatString0);
252: int s = (int) (seconds % 60);
253: if (s < 10)
254: sb.append('0');
255: sb.append(s);
256: if (_millis) {
257: long millis = inDate % 1000;
258: if (millis < 10)
259: sb.append(".00");
260: else if (millis < 100)
261: sb.append(".0");
262: else
263: sb.append('.');
264: sb.append(millis);
265: }
266: sb.append(_secFormatString1);
267: _lastResult = sb.toString();
268: }
269:
270: return _lastResult;
271: }
272:
273: /* ------------------------------------------------------------ */
274: /** Format to string buffer.
275: * @param inDate Date the format
276: * @param buffer StringBuffer
277: */
278: public void format(long inDate, StringBuffer buffer) {
279: buffer.append(format(inDate));
280: }
281:
282: /* ------------------------------------------------------------ */
283: /** Get the format.
284: */
285: public SimpleDateFormat getFormat() {
286: return _minFormat;
287: }
288:
289: /* ------------------------------------------------------------ */
290: public String getFormatString() {
291: return _formatString;
292: }
293:
294: /* ------------------------------------------------------------ */
295: public String now() {
296: return format(System.currentTimeMillis());
297: }
298: }
|