// ======================================================================== // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // and Apache License v2.0 which accompanies this distribution. // The Eclipse Public License is available at // http://www.eclipse.org/legal/epl-v10.html // The Apache License v2.0 is available at // http://www.opensource.org/licenses/apache2.0.php // You may elect to redistribute this code under either of these licenses. // ======================================================================== package net.hasor.rsf.json; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.TimeZone; /* ------------------------------------------------------------ */ /** Date Format Cache. * Computes String representations of Dates and caches * the results so that subsequent requests within the same minute * will be fast. * * Only format strings that contain either "ss" or "ss.SSS" are * handled. * * The timezone of the date may be included as an ID with the "zzz" * format string or as an offset with the "ZZZ" format string. * * If consecutive calls are frequently very different, then this * may be a little slower than a normal DateFormat. * * * */ class DateCache { public static String DEFAULT_FORMAT = "EEE MMM dd HH:mm:ss zzz yyyy"; private static long __hitWindow = 60 * 60; private String _formatString; private String _tzFormatString; private SimpleDateFormat _tzFormat; private String _minFormatString; private SimpleDateFormat _minFormat; private String _secFormatString; private String _secFormatString0; private String _secFormatString1; private long _lastMinutes = -1; private long _lastSeconds = -1; private int _lastMs = -1; private String _lastResult = null; private Locale _locale = null; private DateFormatSymbols _dfs = null; /* ------------------------------------------------------------ */ /** Constructor. * Make a DateCache that will use a default format. The default format * generates the same results as Date.toString(). */ public DateCache() { this(DEFAULT_FORMAT); getFormat().setTimeZone(TimeZone.getDefault()); } /* ------------------------------------------------------------ */ /** Constructor. * Make a DateCache that will use the given format */ public DateCache(String format) { _formatString = format; setTimeZone(TimeZone.getDefault()); } /* ------------------------------------------------------------ */ public DateCache(String format, Locale l) { _formatString = format; _locale = l; setTimeZone(TimeZone.getDefault()); } /* ------------------------------------------------------------ */ public DateCache(String format, DateFormatSymbols s) { _formatString = format; _dfs = s; setTimeZone(TimeZone.getDefault()); } /* ------------------------------------------------------------ */ /** Set the timezone. * @param tz TimeZone */ public synchronized void setTimeZone(TimeZone tz) { setTzFormatString(tz); if (_locale != null) { _tzFormat = new SimpleDateFormat(_tzFormatString, _locale); _minFormat = new SimpleDateFormat(_minFormatString, _locale); } else if (_dfs != null) { _tzFormat = new SimpleDateFormat(_tzFormatString, _dfs); _minFormat = new SimpleDateFormat(_minFormatString, _dfs); } else { _tzFormat = new SimpleDateFormat(_tzFormatString); _minFormat = new SimpleDateFormat(_minFormatString); } _tzFormat.setTimeZone(tz); _minFormat.setTimeZone(tz); _lastSeconds = -1; _lastMinutes = -1; } /* ------------------------------------------------------------ */ public TimeZone getTimeZone() { return _tzFormat.getTimeZone(); } /* ------------------------------------------------------------ */ /** Set the timezone. * @param timeZoneId TimeZoneId the ID of the zone as used by * TimeZone.getTimeZone(id) */ public void setTimeZoneID(String timeZoneId) { setTimeZone(TimeZone.getTimeZone(timeZoneId)); } /* ------------------------------------------------------------ */ private synchronized void setTzFormatString(final TimeZone tz) { int zIndex = _formatString.indexOf("ZZZ"); if (zIndex >= 0) { String ss1 = _formatString.substring(0, zIndex); String ss2 = _formatString.substring(zIndex + 3); int tzOffset = tz.getRawOffset(); StringBuilder sb = new StringBuilder(_formatString.length() + 10); sb.append(ss1); sb.append("'"); if (tzOffset >= 0) sb.append('+'); else { tzOffset = -tzOffset; sb.append('-'); } int raw = tzOffset / (1000 * 60); // Convert to seconds int hr = raw / 60; int min = raw % 60; if (hr < 10) sb.append('0'); sb.append(hr); if (min < 10) sb.append('0'); sb.append(min); sb.append('\''); sb.append(ss2); _tzFormatString = sb.toString(); } else _tzFormatString = _formatString; setMinFormatString(); } /* ------------------------------------------------------------ */ private void setMinFormatString() { int i = _tzFormatString.indexOf("ss.SSS"); int l = 6; if (i >= 0) throw new IllegalStateException("ms not supported"); i = _tzFormatString.indexOf("ss"); l = 2; // Build a formatter that formats a second format string String ss1 = _tzFormatString.substring(0, i); String ss2 = _tzFormatString.substring(i + l); _minFormatString = ss1 + "'ss'" + ss2; } /* ------------------------------------------------------------ */ /** Format a date according to our stored formatter. * @param inDate * @return Formatted date */ public synchronized String format(Date inDate) { return format(inDate.getTime()); } /* ------------------------------------------------------------ */ /** Format a date according to our stored formatter. * @param inDate * @return Formatted date */ public synchronized String format(long inDate) { long seconds = inDate / 1000; // Is it not suitable to cache? if (seconds < _lastSeconds || _lastSeconds > 0 && seconds > _lastSeconds + __hitWindow) { // It's a cache miss Date d = new Date(inDate); return _tzFormat.format(d); } // Check if we are in the same second // and don't care about millis if (_lastSeconds == seconds) return _lastResult; Date d = new Date(inDate); // Check if we need a new format string long minutes = seconds / 60; if (_lastMinutes != minutes) { _lastMinutes = minutes; _secFormatString = _minFormat.format(d); int i = _secFormatString.indexOf("ss"); int l = 2; _secFormatString0 = _secFormatString.substring(0, i); _secFormatString1 = _secFormatString.substring(i + l); } // Always format if we get here _lastSeconds = seconds; StringBuilder sb = new StringBuilder(_secFormatString.length()); sb.append(_secFormatString0); int s = (int) (seconds % 60); if (s < 10) sb.append('0'); sb.append(s); sb.append(_secFormatString1); _lastResult = sb.toString(); return _lastResult; } /* ------------------------------------------------------------ */ /** Format to string buffer. * @param inDate Date the format * @param buffer StringBuilder */ public void format(long inDate, StringBuilder buffer) { buffer.append(format(inDate)); } /* ------------------------------------------------------------ */ /** Get the format. */ public SimpleDateFormat getFormat() { return _minFormat; } /* ------------------------------------------------------------ */ public String getFormatString() { return _formatString; } /* ------------------------------------------------------------ */ public String now() { long now = System.currentTimeMillis(); _lastMs = (int) (now % 1000); return format(now); } /* ------------------------------------------------------------ */ public int lastMs() { return _lastMs; } }