1 package tim.prune.data;
3 import java.text.DateFormat;
4 import java.text.ParseException;
5 import java.text.SimpleDateFormat;
6 import java.util.Calendar;
8 import java.util.regex.Matcher;
9 import java.util.regex.Pattern;
12 * Class to hold the timestamp of a track point
13 * and provide conversion functions
15 public class Timestamp
17 private boolean _valid = false;
18 private long _seconds = 0L;
19 private String _text = null;
20 private String _timeText = null;
22 private static final DateFormat DEFAULT_DATE_FORMAT = DateFormat.getDateTimeInstance();
23 private static final DateFormat DEFAULT_TIME_FORMAT = DateFormat.getTimeInstance();
24 private static final DateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
25 private static final DateFormat ISO_8601_FORMAT_NOZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
26 private static DateFormat[] ALL_DATE_FORMATS = null;
27 private static Calendar CALENDAR = null;
28 private static final Pattern GENERAL_TIMESTAMP_PATTERN
29 = Pattern.compile("(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})");
30 private static long SECS_SINCE_1970 = 0L;
31 private static long SECS_SINCE_GARTRIP = 0L;
32 private static long MSECS_SINCE_1970 = 0L;
33 private static long MSECS_SINCE_1990 = 0L;
34 private static long TWENTY_YEARS_IN_SECS = 0L;
35 private static final long GARTRIP_OFFSET = 631065600L;
37 /** Specifies original timestamp format */
38 public static final int FORMAT_ORIGINAL = 0;
39 /** Specifies locale-dependent timestamp format */
40 public static final int FORMAT_LOCALE = 1;
41 /** Specifies ISO 8601 timestamp format */
42 public static final int FORMAT_ISO_8601 = 2;
44 // Static block to initialise offsets
47 CALENDAR = Calendar.getInstance();
48 MSECS_SINCE_1970 = CALENDAR.getTimeInMillis();
49 SECS_SINCE_1970 = MSECS_SINCE_1970 / 1000L;
50 SECS_SINCE_GARTRIP = SECS_SINCE_1970 - GARTRIP_OFFSET;
51 CALENDAR.add(Calendar.YEAR, -20);
52 MSECS_SINCE_1990 = CALENDAR.getTimeInMillis();
53 TWENTY_YEARS_IN_SECS = (MSECS_SINCE_1970 - MSECS_SINCE_1990) / 1000L;
55 ALL_DATE_FORMATS = new DateFormat[] {
57 new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"),
58 new SimpleDateFormat("HH:mm:ss dd MMM yyyy"),
59 new SimpleDateFormat("dd MMM yyyy HH:mm:ss"),
60 new SimpleDateFormat("yyyy MMM dd HH:mm:ss"),
61 ISO_8601_FORMAT, ISO_8601_FORMAT_NOZ
68 * @param inString String containing timestamp
70 public Timestamp(String inString)
72 // TODO: Does it really help to store timestamps in seconds rather than ms?
73 if (inString != null && !inString.equals(""))
75 // Try to parse into a long
78 long rawValue = Long.parseLong(inString.trim());
79 _seconds = getSeconds(rawValue);
82 catch (NumberFormatException nfe)
84 // String is not a long, so try a date/time string instead
85 // try each of the date formatters in turn
87 for (int i=0; i<ALL_DATE_FORMATS.length && !_valid; i++)
91 date = ALL_DATE_FORMATS[i].parse(inString);
92 CALENDAR.setTime(date);
93 _seconds = CALENDAR.getTimeInMillis() / 1000L;
96 catch (ParseException e) {}
98 if (!_valid && inString.length() == 19)
100 final Matcher matcher = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
101 if (matcher.matches())
104 _seconds = getSeconds(Integer.parseInt(matcher.group(1)),
105 Integer.parseInt(matcher.group(2)),
106 Integer.parseInt(matcher.group(3)),
107 Integer.parseInt(matcher.group(4)),
108 Integer.parseInt(matcher.group(5)),
109 Integer.parseInt(matcher.group(6)));
112 catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
121 * Constructor giving each field value individually
123 * @param inMonth month, beginning with 1
124 * @param inDay day of month, beginning with 1
125 * @param inHour hour of day, 0-24
126 * @param inMinute minute
127 * @param inSecond seconds
129 public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
131 _seconds = getSeconds(inYear, inMonth, inDay, inHour, inMinute, inSecond);
137 * Constructor giving millis
138 * @param inMillis milliseconds since 1970
140 public Timestamp(long inMillis)
142 _seconds = inMillis / 1000;
148 * Convert the given timestamp parameters into a number of seconds
150 * @param inMonth month, beginning with 1
151 * @param inDay day of month, beginning with 1
152 * @param inHour hour of day, 0-24
153 * @param inMinute minute
154 * @param inSecond seconds
155 * @return number of seconds
157 private static long getSeconds(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
159 Calendar cal = Calendar.getInstance();
160 cal.set(Calendar.YEAR, inYear);
161 cal.set(Calendar.MONTH, inMonth - 1);
162 cal.set(Calendar.DAY_OF_MONTH, inDay);
163 cal.set(Calendar.HOUR_OF_DAY, inHour);
164 cal.set(Calendar.MINUTE, inMinute);
165 cal.set(Calendar.SECOND, inSecond);
166 cal.set(Calendar.MILLISECOND, 0);
167 return cal.getTimeInMillis() / 1000;
171 * Convert the given long parameters into a number of seconds
172 * @param inRawValue long value representing seconds / milliseconds
173 * @return number of seconds
175 private static long getSeconds(long inRawValue)
177 // check for each format possibility and pick nearest
178 long diff1 = Math.abs(SECS_SINCE_1970 - inRawValue);
179 long diff2 = Math.abs(MSECS_SINCE_1970 - inRawValue);
180 long diff3 = Math.abs(MSECS_SINCE_1990 - inRawValue);
181 long diff4 = Math.abs(SECS_SINCE_GARTRIP - inRawValue);
183 // Start off with "seconds since 1970" format
184 long smallestDiff = diff1;
185 long seconds = inRawValue;
186 // Now check millis since 1970
187 if (diff2 < smallestDiff)
189 // milliseconds since 1970
190 seconds = inRawValue / 1000L;
191 smallestDiff = diff2;
193 // Now millis since 1990
194 if (diff3 < smallestDiff)
196 // milliseconds since 1990
197 seconds = inRawValue / 1000L + TWENTY_YEARS_IN_SECS;
198 smallestDiff = diff3;
200 // Lastly, check gartrip offset
201 if (diff4 < smallestDiff)
203 // seconds since gartrip offset
204 seconds = inRawValue + GARTRIP_OFFSET;
210 * @return true if timestamp is valid
212 public boolean isValid()
218 * @param inOther other Timestamp
219 * @return true if this one is after the other
221 public boolean isAfter(Timestamp inOther)
223 return _seconds > inOther._seconds;
227 * Calculate the difference between two Timestamps in seconds
228 * @param inOther other, earlier Timestamp
229 * @return number of seconds since other timestamp
231 public long getSecondsSince(Timestamp inOther)
233 return _seconds - inOther._seconds;
237 * Add the given number of seconds offset
238 * @param inOffset number of seconds to add/subtract
240 public void addOffset(long inOffset)
242 _seconds += inOffset;
247 * Add the given TimeDifference to this Timestamp
248 * @param inOffset TimeDifference to add
249 * @return new Timestamp object
251 public Timestamp createPlusOffset(TimeDifference inOffset)
253 return createPlusOffset(inOffset.getTotalSeconds());
257 * Add the given number of seconds to this Timestamp
258 * @param inSeconds number of seconds to add
259 * @return new Timestamp object
261 public Timestamp createPlusOffset(long inSeconds)
263 return new Timestamp((_seconds + inSeconds) * 1000L);
268 * Subtract the given TimeDifference from this Timestamp
269 * @param inOffset TimeDifference to subtract
270 * @return new Timestamp object
272 public Timestamp createMinusOffset(TimeDifference inOffset)
274 return new Timestamp((_seconds - inOffset.getTotalSeconds()) * 1000L);
279 * @return Description of timestamp in locale-specific format
281 public String getText()
283 return getText(FORMAT_LOCALE);
287 * @param inFormat format of timestamp
288 * @return Description of timestamp in required format
290 public String getText(int inFormat)
292 if (!_valid) {return "";}
293 if (inFormat == FORMAT_ISO_8601) {
294 return format(ISO_8601_FORMAT);
297 _text = (_valid?format(DEFAULT_DATE_FORMAT):"");
303 * @return Description of time part of timestamp in locale-specific format
305 public String getTimeText()
307 if (_timeText == null)
310 _timeText = format(DEFAULT_TIME_FORMAT);
318 * Utility method for formatting dates / times
319 * @param inFormat formatter object
320 * @return formatted String
322 private String format(DateFormat inFormat)
324 CALENDAR.setTimeInMillis(_seconds * 1000L);
325 return inFormat.format(CALENDAR.getTime());
329 * @return a Calendar object representing this timestamp
331 public Calendar getCalendar()
333 Calendar cal = Calendar.getInstance();
334 cal.setTimeInMillis(_seconds * 1000L);