1 //
2 // ========================================================================
3 // Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
4 // ------------------------------------------------------------------------
5 // All rights reserved. This program and the accompanying materials
6 // are made available under the terms of the Eclipse Public License v1.0
7 // and Apache License v2.0 which accompanies this distribution.
8 //
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
11 //
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
14 //
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
17 //
18
19 package org.eclipse.jetty.util;
20
21 import java.text.SimpleDateFormat;
22 import java.util.Date;
23 import java.util.Locale;
24 import java.util.TimeZone;
25
26 /** Date Format Cache.
27 * Computes String representations of Dates and caches
28 * the results so that subsequent requests within the same second
29 * will be fast.
30 *
31 * Only format strings that contain either "ss". Sub second formatting is
32 * not handled.
33 *
34 * The timezone of the date may be included as an ID with the "zzz"
35 * format string or as an offset with the "ZZZ" format string.
36 *
37 * If consecutive calls are frequently very different, then this
38 * may be a little slower than a normal DateFormat.
39 */
40 public class DateCache
41 {
42 public static final String DEFAULT_FORMAT="EEE MMM dd HH:mm:ss zzz yyyy";
43
44 private final String _formatString;
45 private final String _tzFormatString;
46 private final SimpleDateFormat _tzFormat;
47 private final Locale _locale ;
48
49 private volatile Tick _tick;
50
51 /* ------------------------------------------------------------ */
52 /* ------------------------------------------------------------ */
53 public static class Tick
54 {
55 final long _seconds;
56 final String _string;
57 public Tick(long seconds, String string)
58 {
59 _seconds = seconds;
60 _string = string;
61 }
62 }
63
64 /* ------------------------------------------------------------ */
65 /** Constructor.
66 * Make a DateCache that will use a default format. The default format
67 * generates the same results as Date.toString().
68 */
69 public DateCache()
70 {
71 this(DEFAULT_FORMAT);
72 }
73
74 /* ------------------------------------------------------------ */
75 /** Constructor.
76 * Make a DateCache that will use the given format
77 * @param format the format to use
78 */
79 public DateCache(String format)
80 {
81 this(format,null,TimeZone.getDefault());
82 }
83
84 /* ------------------------------------------------------------ */
85 public DateCache(String format,Locale l)
86 {
87 this(format,l,TimeZone.getDefault());
88 }
89
90 /* ------------------------------------------------------------ */
91 public DateCache(String format,Locale l,String tz)
92 {
93 this(format,l,TimeZone.getTimeZone(tz));
94 }
95
96 /* ------------------------------------------------------------ */
97 public DateCache(String format,Locale l,TimeZone tz)
98 {
99 _formatString=format;
100 _locale = l;
101
102
103 int zIndex = _formatString.indexOf( "ZZZ" );
104 if( zIndex >= 0 )
105 {
106 String ss1 = _formatString.substring( 0, zIndex );
107 String ss2 = _formatString.substring( zIndex+3 );
108 int tzOffset = tz.getRawOffset();
109
110 StringBuilder sb = new StringBuilder(_formatString.length()+10);
111 sb.append(ss1);
112 sb.append("'");
113 if( tzOffset >= 0 )
114 sb.append( '+' );
115 else
116 {
117 tzOffset = -tzOffset;
118 sb.append( '-' );
119 }
120
121 int raw = tzOffset / (1000*60); // Convert to seconds
122 int hr = raw / 60;
123 int min = raw % 60;
124
125 if( hr < 10 )
126 sb.append( '0' );
127 sb.append( hr );
128 if( min < 10 )
129 sb.append( '0' );
130 sb.append( min );
131 sb.append( '\'' );
132
133 sb.append(ss2);
134 _tzFormatString=sb.toString();
135 }
136 else
137 _tzFormatString=_formatString;
138
139 if( _locale != null )
140 {
141 _tzFormat=new SimpleDateFormat(_tzFormatString,_locale);
142 }
143 else
144 {
145 _tzFormat=new SimpleDateFormat(_tzFormatString);
146 }
147 _tzFormat.setTimeZone(tz);
148
149 _tick=null;
150 }
151
152
153 /* ------------------------------------------------------------ */
154 public TimeZone getTimeZone()
155 {
156 return _tzFormat.getTimeZone();
157 }
158
159
160 /* ------------------------------------------------------------ */
161 /** Format a date according to our stored formatter.
162 * @param inDate the Date
163 * @return Formatted date
164 */
165 public String format(Date inDate)
166 {
167 long seconds = inDate.getTime() / 1000;
168
169 Tick tick=_tick;
170
171 // Is this the cached time
172 if (tick==null || seconds!=tick._seconds)
173 {
174 // It's a cache miss
175 synchronized (this)
176 {
177 return _tzFormat.format(inDate);
178 }
179 }
180
181 return tick._string;
182 }
183
184 /* ------------------------------------------------------------ */
185 /** Format a date according to our stored formatter.
186 * If it happens to be in the same second as the last formatNow
187 * call, then the format is reused.
188 * @param inDate the date in milliseconds since unix epoch
189 * @return Formatted date
190 */
191 public String format(long inDate)
192 {
193 long seconds = inDate / 1000;
194
195 Tick tick=_tick;
196
197 // Is this the cached time
198 if (tick==null || seconds!=tick._seconds)
199 {
200 // It's a cache miss
201 Date d = new Date(inDate);
202 synchronized (this)
203 {
204 return _tzFormat.format(d);
205 }
206 }
207
208 return tick._string;
209 }
210
211 /* ------------------------------------------------------------ */
212 /** Format a date according to our stored formatter.
213 * The passed time is expected to be close to the current time, so it is
214 * compared to the last value passed and if it is within the same second,
215 * the format is reused. Otherwise a new cached format is created.
216 * @param now the milliseconds since unix epoch
217 * @return Formatted date
218 */
219 public String formatNow(long now)
220 {
221 long seconds = now / 1000;
222
223 Tick tick=_tick;
224
225 // Is this the cached time
226 if (tick!=null && tick._seconds==seconds)
227 return tick._string;
228 return formatTick(now)._string;
229 }
230
231 /* ------------------------------------------------------------ */
232 public String now()
233 {
234 return formatNow(System.currentTimeMillis());
235 }
236
237 /* ------------------------------------------------------------ */
238 public Tick tick()
239 {
240 return formatTick(System.currentTimeMillis());
241 }
242
243 /* ------------------------------------------------------------ */
244 protected Tick formatTick(long now)
245 {
246 long seconds = now / 1000;
247
248 // Synchronize to protect _tzFormat
249 synchronized (this)
250 {
251 // recheck the tick, to save multiple formats
252 if (_tick==null || _tick._seconds!=seconds)
253 {
254 String s= _tzFormat.format(new Date(now));
255 return _tick=new Tick(seconds,s);
256 }
257 return _tick;
258 }
259 }
260
261 /* ------------------------------------------------------------ */
262 public String getFormatString()
263 {
264 return _formatString;
265 }
266 }