]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/data/Timestamp.java
Version 14, October 2012
[GpsPrune.git] / tim / prune / data / Timestamp.java
1 package tim.prune.data;
2
3 import java.text.DateFormat;
4 import java.text.ParseException;
5 import java.text.SimpleDateFormat;
6 import java.util.Calendar;
7 import java.util.Date;
8 import java.util.regex.Matcher;
9 import java.util.regex.Pattern;
10
11 /**
12  * Class to hold the timestamp of a track point
13  * and provide conversion functions
14  */
15 public class Timestamp
16 {
17         private boolean _valid = false;
18         private long _milliseconds = 0L;
19         private String _text = null;
20         private String _timeText = null;
21
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 ISO8601_FRACTIONAL_PATTERN
29                 = Pattern.compile("(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{1,3})Z?");
30         private static final Pattern GENERAL_TIMESTAMP_PATTERN
31                 = Pattern.compile("(\\d{4})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})\\D(\\d{2})");
32         private static long SECS_SINCE_1970 = 0L;
33         private static long SECS_SINCE_GARTRIP = 0L;
34         private static long MSECS_SINCE_1970 = 0L;
35         private static long MSECS_SINCE_1990 = 0L;
36         private static long TWENTY_YEARS_IN_SECS = 0L;
37         private static final long GARTRIP_OFFSET = 631065600L;
38
39         /** Specifies original timestamp format */
40         public static final int FORMAT_ORIGINAL = 0;
41         /** Specifies locale-dependent timestamp format */
42         public static final int FORMAT_LOCALE = 1;
43         /** Specifies ISO 8601 timestamp format */
44         public static final int FORMAT_ISO_8601 = 2;
45
46         /** Identifier for the parsing strategy to use */
47         private enum ParseType
48         {
49                 NONE,
50                 ISO8601_FRACTIONAL,
51                 LONG,
52                 FIXED_FORMAT0,
53                 FIXED_FORMAT1,
54                 FIXED_FORMAT2,
55                 FIXED_FORMAT3,
56                 FIXED_FORMAT4,
57                 FIXED_FORMAT5,
58                 FIXED_FORMAT6,
59                 GENERAL_STRING
60         }
61
62         /** Array of parse types to loop through (first one is changed to last successful type) */
63         private static ParseType[] ALL_PARSE_TYPES = {ParseType.NONE, ParseType.ISO8601_FRACTIONAL, ParseType.LONG,
64                 ParseType.FIXED_FORMAT0, ParseType.FIXED_FORMAT1, ParseType.FIXED_FORMAT2, ParseType.FIXED_FORMAT3,
65                 ParseType.FIXED_FORMAT4, ParseType.FIXED_FORMAT5, ParseType.FIXED_FORMAT6, ParseType.GENERAL_STRING};
66
67         // Static block to initialise offsets
68         static
69         {
70                 CALENDAR = Calendar.getInstance();
71                 MSECS_SINCE_1970 = CALENDAR.getTimeInMillis();
72                 SECS_SINCE_1970 = MSECS_SINCE_1970 / 1000L;
73                 SECS_SINCE_GARTRIP = SECS_SINCE_1970 - GARTRIP_OFFSET;
74                 CALENDAR.add(Calendar.YEAR, -20);
75                 MSECS_SINCE_1990 = CALENDAR.getTimeInMillis();
76                 TWENTY_YEARS_IN_SECS = (MSECS_SINCE_1970 - MSECS_SINCE_1990) / 1000L;
77                 // Date formats
78                 ALL_DATE_FORMATS = new DateFormat[] {
79                         DEFAULT_DATE_FORMAT,
80                         new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy"),
81                         new SimpleDateFormat("HH:mm:ss dd MMM yyyy"),
82                         new SimpleDateFormat("dd MMM yyyy HH:mm:ss"),
83                         new SimpleDateFormat("yyyy MMM dd HH:mm:ss"),
84                         ISO_8601_FORMAT, ISO_8601_FORMAT_NOZ
85                 };
86         }
87
88
89         /**
90          * Constructor
91          * @param inString String containing timestamp
92          */
93         public Timestamp(String inString)
94         {
95                 _valid = false;
96                 if (inString != null && !inString.equals(""))
97                 {
98                         // Try each of the parse types in turn
99                         for (ParseType type : ALL_PARSE_TYPES)
100                         {
101                                 if (parseString(inString, type))
102                                 {
103                                         ALL_PARSE_TYPES[0] = type;
104                                         _valid = true;
105                                         return;
106                                 }
107                         }
108                 }
109         }
110
111         /**
112          * Try to parse the given string in the specified way
113          * @param inString String to parse
114          * @param inType parse type to use
115          * @return true if successful
116          */
117         private boolean parseString(String inString, ParseType inType)
118         {
119                 if (inString == null || inString.equals("")) {
120                         return false;
121                 }
122                 switch (inType)
123                 {
124                         case NONE: return false;
125                         case LONG:
126                                 // Try to parse into a long
127                                 try
128                                 {
129                                         long rawValue = Long.parseLong(inString.trim());
130                                         _milliseconds = getMilliseconds(rawValue);
131                                         return true;
132                                 }
133                                 catch (NumberFormatException nfe)
134                                 {}
135                                 break;
136
137                         case ISO8601_FRACTIONAL:
138                                 final Matcher fmatcher = ISO8601_FRACTIONAL_PATTERN.matcher(inString);
139                                 if (fmatcher.matches())
140                                 {
141                                         try {
142                                                 _milliseconds = getMilliseconds(Integer.parseInt(fmatcher.group(1)), // year
143                                                         Integer.parseInt(fmatcher.group(2)), // month
144                                                         Integer.parseInt(fmatcher.group(3)), // day
145                                                         Integer.parseInt(fmatcher.group(4)), // hour
146                                                         Integer.parseInt(fmatcher.group(5)), // minute
147                                                         Integer.parseInt(fmatcher.group(6)), // second
148                                                         fmatcher.group(7));                  // fractional seconds
149                                                 return true;
150                                         }
151                                         catch (NumberFormatException nfe) {}
152                                 }
153                                 break;
154
155                         case FIXED_FORMAT0: return parseString(inString, ALL_DATE_FORMATS[0]);
156                         case FIXED_FORMAT1: return parseString(inString, ALL_DATE_FORMATS[1]);
157                         case FIXED_FORMAT2: return parseString(inString, ALL_DATE_FORMATS[2]);
158                         case FIXED_FORMAT3: return parseString(inString, ALL_DATE_FORMATS[3]);
159                         case FIXED_FORMAT4: return parseString(inString, ALL_DATE_FORMATS[4]);
160                         case FIXED_FORMAT5: return parseString(inString, ALL_DATE_FORMATS[5]);
161                         case FIXED_FORMAT6: return parseString(inString, ALL_DATE_FORMATS[6]);
162
163                         case GENERAL_STRING:
164                                 if (inString.length() == 19)
165                                 {
166                                         final Matcher matcher = GENERAL_TIMESTAMP_PATTERN.matcher(inString);
167                                         if (matcher.matches())
168                                         {
169                                                 try {
170                                                         _milliseconds = getMilliseconds(Integer.parseInt(matcher.group(1)),
171                                                                 Integer.parseInt(matcher.group(2)),
172                                                                 Integer.parseInt(matcher.group(3)),
173                                                                 Integer.parseInt(matcher.group(4)),
174                                                                 Integer.parseInt(matcher.group(5)),
175                                                                 Integer.parseInt(matcher.group(6)),
176                                                                 null); // no fractions of a second
177                                                         return true;
178                                                 }
179                                                 catch (NumberFormatException nfe2) {} // parse shouldn't fail if matcher matched
180                                         }
181                                 }
182                                 return false;
183                 }
184                 return false;
185         }
186
187
188         /**
189          * Try to parse the given string with the given date format
190          * @param inString String to parse
191          * @param inDateFormat Date format to use
192          * @return true if successful
193          */
194         private boolean parseString(String inString, DateFormat inDateFormat)
195         {
196                 try
197                 {
198                         Date date = inDateFormat.parse(inString);
199                         CALENDAR.setTime(date);
200                         _milliseconds = CALENDAR.getTimeInMillis();
201                         return true;
202                 }
203                 catch (ParseException e) {}
204                 return false;
205         }
206
207
208         /**
209          * Constructor giving each field value individually
210          * @param inYear year
211          * @param inMonth month, beginning with 1
212          * @param inDay day of month, beginning with 1
213          * @param inHour hour of day, 0-24
214          * @param inMinute minute
215          * @param inSecond seconds
216          */
217         public Timestamp(int inYear, int inMonth, int inDay, int inHour, int inMinute, int inSecond)
218         {
219                 _milliseconds = getMilliseconds(inYear, inMonth, inDay, inHour, inMinute, inSecond, null);
220                 _valid = true;
221         }
222
223
224         /**
225          * Constructor giving millis
226          * @param inMillis milliseconds since 1970
227          */
228         public Timestamp(long inMillis)
229         {
230                 _milliseconds = inMillis;
231                 _valid = true;
232         }
233
234
235         /**
236          * Convert the given timestamp parameters into a number of milliseconds
237          * @param inYear year
238          * @param inMonth month, beginning with 1
239          * @param inDay day of month, beginning with 1
240          * @param inHour hour of day, 0-24
241          * @param inMinute minute
242          * @param inSecond seconds
243          * @param inFraction fractions of a second
244          * @return number of milliseconds
245          */
246         private static long getMilliseconds(int inYear, int inMonth, int inDay,
247                 int inHour, int inMinute, int inSecond, String inFraction)
248         {
249                 Calendar cal = Calendar.getInstance();
250                 cal.set(Calendar.YEAR, inYear);
251                 cal.set(Calendar.MONTH, inMonth - 1);
252                 cal.set(Calendar.DAY_OF_MONTH, inDay);
253                 cal.set(Calendar.HOUR_OF_DAY, inHour);
254                 cal.set(Calendar.MINUTE, inMinute);
255                 cal.set(Calendar.SECOND, inSecond);
256                 int millis = 0;
257                 if (inFraction != null)
258                 {
259                         try {
260                                 int frac = Integer.parseInt(inFraction);
261                                 final int fracLen = inFraction.length();
262                                 switch (fracLen) {
263                                         case 1: millis = frac * 100; break;
264                                         case 2: millis = frac * 10;  break;
265                                         case 3: millis = frac;       break;
266                                 }
267                         }
268                         catch (NumberFormatException nfe) {} // ignore errors, millis stay at 0
269                 }
270                 cal.set(Calendar.MILLISECOND, millis);
271                 return cal.getTimeInMillis();
272         }
273
274         /**
275          * Convert the given long parameters into a number of millisseconds
276          * @param inRawValue long value representing seconds / milliseconds
277          * @return number of milliseconds
278          */
279         private static long getMilliseconds(long inRawValue)
280         {
281                 // check for each format possibility and pick nearest
282                 long diff1 = Math.abs(SECS_SINCE_1970 - inRawValue);
283                 long diff2 = Math.abs(MSECS_SINCE_1970 - inRawValue);
284                 long diff3 = Math.abs(MSECS_SINCE_1990 - inRawValue);
285                 long diff4 = Math.abs(SECS_SINCE_GARTRIP - inRawValue);
286
287                 // Start off with "seconds since 1970" format
288                 long smallestDiff = diff1;
289                 long millis = inRawValue * 1000;
290                 // Now check millis since 1970
291                 if (diff2 < smallestDiff)
292                 {
293                         // milliseconds since 1970
294                         millis = inRawValue;
295                         smallestDiff = diff2;
296                 }
297                 // Now millis since 1990
298                 if (diff3 < smallestDiff)
299                 {
300                         // milliseconds since 1990
301                         millis = inRawValue + TWENTY_YEARS_IN_SECS * 1000L;
302                         smallestDiff = diff3;
303                 }
304                 // Lastly, check gartrip offset
305                 if (diff4 < smallestDiff)
306                 {
307                         // seconds since gartrip offset
308                         millis = (inRawValue + GARTRIP_OFFSET) * 1000L;
309                 }
310                 return millis;
311         }
312
313         /**
314          * @return true if timestamp is valid
315          */
316         public boolean isValid()
317         {
318                 return _valid;
319         }
320
321         /**
322          * @param inOther other Timestamp
323          * @return true if this one is at least a second after the other
324          */
325         public boolean isAfter(Timestamp inOther)
326         {
327                 return getSecondsSince(inOther) > 0L;
328         }
329
330         /**
331          * Calculate the difference between two Timestamps in seconds
332          * @param inOther other, earlier Timestamp
333          * @return number of seconds since other timestamp
334          */
335         public long getSecondsSince(Timestamp inOther)
336         {
337                 return (_milliseconds - inOther._milliseconds) / 1000L;
338         }
339
340         /**
341          * Calculate the difference between two Timestamps in milliseconds
342          * @param inOther other, earlier Timestamp
343          * @return number of millisseconds since other timestamp
344          */
345         public long getMillisecondsSince(Timestamp inOther)
346         {
347                 return _milliseconds - inOther._milliseconds;
348         }
349
350         /**
351          * @param inOther other timestamp to compare
352          * @return true if they're equal to the nearest second
353          */
354         public boolean isEqual(Timestamp inOther)
355         {
356                 return getSecondsSince(inOther) == 0L;
357         }
358
359         /**
360          * @param inOther other Timestamp
361          * @return true if this one is before the other
362          */
363         public boolean isBefore(Timestamp inOther)
364         {
365                 return getSecondsSince(inOther) < 0L;
366         }
367
368         /**
369          * Add the given number of seconds offset
370          * @param inOffset number of seconds to add/subtract
371          */
372         public void addOffset(long inOffset)
373         {
374                 _milliseconds += (inOffset * 1000L);
375                 _text = null;
376         }
377
378         /**
379          * Add the given TimeDifference to this Timestamp
380          * @param inOffset TimeDifference to add
381          * @return new Timestamp object
382          */
383         public Timestamp createPlusOffset(TimeDifference inOffset)
384         {
385                 return createPlusOffset(inOffset.getTotalSeconds());
386         }
387
388         /**
389          * Add the given number of seconds to this Timestamp
390          * @param inSeconds number of seconds to add
391          * @return new Timestamp object
392          */
393         public Timestamp createPlusOffset(long inSeconds)
394         {
395                 return new Timestamp(_milliseconds + (inSeconds * 1000L));
396         }
397
398
399         /**
400          * Subtract the given TimeDifference from this Timestamp
401          * @param inOffset TimeDifference to subtract
402          * @return new Timestamp object
403          */
404         public Timestamp createMinusOffset(TimeDifference inOffset)
405         {
406                 return new Timestamp(_milliseconds - (inOffset.getTotalSeconds() * 1000L));
407         }
408
409
410         /**
411          * @return Description of timestamp in locale-specific format
412          */
413         public String getText()
414         {
415                 return getText(FORMAT_LOCALE);
416         }
417
418         /**
419          * @param inFormat format of timestamp
420          * @return Description of timestamp in required format
421          */
422         public String getText(int inFormat)
423         {
424                 if (!_valid) {return "";}
425                 if (inFormat == FORMAT_ISO_8601) {
426                         return format(ISO_8601_FORMAT);
427                 }
428                 if (_text == null) {
429                         _text = (_valid?format(DEFAULT_DATE_FORMAT):"");
430                 }
431                 return _text;
432         }
433
434         /**
435          * @return Description of time part of timestamp in locale-specific format
436          */
437         public String getTimeText()
438         {
439                 if (_timeText == null)
440                 {
441                         if (_valid) {
442                                 _timeText = format(DEFAULT_TIME_FORMAT);
443                         }
444                         else _timeText = "";
445                 }
446                 return _timeText;
447         }
448
449         /**
450          * Utility method for formatting dates / times
451          * @param inFormat formatter object
452          * @return formatted String
453          */
454         private String format(DateFormat inFormat)
455         {
456                 CALENDAR.setTimeInMillis(_milliseconds);
457                 return inFormat.format(CALENDAR.getTime());
458         }
459
460         /**
461          * @return a Calendar object representing this timestamp
462          */
463         public Calendar getCalendar()
464         {
465                 Calendar cal = Calendar.getInstance();
466                 cal.setTimeInMillis(_milliseconds);
467                 return cal;
468         }
469 }