1 package nom.tam.fits;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import java.util.Calendar;
35 import java.util.Date;
36 import java.util.GregorianCalendar;
37 import java.util.TimeZone;
38 import java.util.regex.Matcher;
39 import java.util.regex.Pattern;
40
41
42
43
44
45 public class FitsDate implements Comparable<FitsDate> {
46
47
48
49
50
51 private static final int FIRST_FOUR_CHARACTER_VALUE = 1000;
52
53 private static final int FIRST_THREE_CHARACTER_VALUE = 100;
54
55 private static final int FIRST_TWO_CHARACTER_VALUE = 10;
56
57 private static final int FIRST_FIVE_CHARACTER_VALUE = 10000;
58
59
60
61
62
63 private static final int MAX_FITS_YEAR = 99999;
64
65
66
67
68 private static final int MAX_FOUR_DIGIT_YEAR = 9999;
69
70 private static final int FITS_DATE_STRING_SIZE = 25;
71
72 private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
73
74 private static final int NEW_FORMAT_DAY_OF_MONTH_GROUP = 4;
75
76 private static final int NEW_FORMAT_HOUR_GROUP = 6;
77
78 private static final int NEW_FORMAT_MILLISECOND_GROUP = 10;
79
80 private static final int NEW_FORMAT_MINUTE_GROUP = 7;
81
82 private static final int NEW_FORMAT_MONTH_GROUP = 3;
83
84 private static final int NEW_FORMAT_SECOND_GROUP = 8;
85
86 private static final int NEW_FORMAT_YEAR_GROUP = 2;
87
88 private static final Pattern NORMAL_REGEX = Pattern
89 .compile("\\s*((\\d{4}|[+-]\\d{5})-(\\d\\d)-(\\d\\d))(T(\\d\\d):(\\d\\d):(\\d\\d)(\\.(\\d+))?)?\\s*");
90
91 private static final int OLD_FORMAT_DAY_OF_MONTH_GROUP = 1;
92
93 private static final int OLD_FORMAT_MONTH_GROUP = 2;
94
95 private static final int OLD_FORMAT_YEAR_GROUP = 3;
96
97 private static final Pattern OLD_REGEX = Pattern.compile("\\s*(\\d\\d)/(\\d\\d)/(\\d\\d)\\s*");
98
99 private static final int YEAR_OFFSET = 1900;
100
101 private static final int NB_DIGITS_MILLIS = 3;
102
103 private static final int POW_TEN = 10;
104
105
106
107
108
109
110
111
112 public static String getFitsDateString() {
113 return getFitsDateString(new Date(), true);
114 }
115
116
117
118
119
120
121
122
123
124
125
126 public static String getFitsDateString(Date epoch) {
127 return getFitsDateString(epoch, true);
128 }
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 public static String getFitsDateString(Date epoch, boolean timeOfDay) {
150 Calendar cal = new GregorianCalendar(UTC);
151 cal.setTime(epoch);
152
153 int fitsYear = toFitsYear(cal.get(Calendar.ERA), cal.get(Calendar.YEAR));
154
155 StringBuilder fitsDate = new StringBuilder(FITS_DATE_STRING_SIZE);
156 appendYear(fitsDate, fitsYear);
157 fitsDate.append('-');
158 appendTwoDigitValue(fitsDate, cal.get(Calendar.MONTH) + 1);
159 fitsDate.append('-');
160 appendTwoDigitValue(fitsDate, cal.get(Calendar.DAY_OF_MONTH));
161
162 if (timeOfDay) {
163 fitsDate.append('T');
164 appendTwoDigitValue(fitsDate, cal.get(Calendar.HOUR_OF_DAY));
165 fitsDate.append(':');
166 appendTwoDigitValue(fitsDate, cal.get(Calendar.MINUTE));
167 fitsDate.append(':');
168 appendTwoDigitValue(fitsDate, cal.get(Calendar.SECOND));
169 fitsDate.append('.');
170 appendThreeDigitValue(fitsDate, cal.get(Calendar.MILLISECOND));
171 }
172 return fitsDate.toString();
173 }
174
175
176
177
178 private static int toFitsYear(int era, int calendarYear) {
179 if (era == GregorianCalendar.BC) {
180 return 1 - calendarYear;
181 }
182 return calendarYear;
183 }
184
185
186
187
188
189
190 private static void appendYear(StringBuilder buf, int year) {
191 if (year < -MAX_FITS_YEAR || year > MAX_FITS_YEAR) {
192 throw new FitsException(
193 "Year " + year + " is outside of the range [-" + MAX_FITS_YEAR + ":" + MAX_FITS_YEAR
194 + "] that a FITS date can represent");
195 }
196 if (year < 0 || year > MAX_FOUR_DIGIT_YEAR) {
197 appendFiveDigitValue(buf, year);
198 } else {
199 appendFourDigitValue(buf, year);
200 }
201 }
202
203 private int hour = -1;
204
205 private int mday = -1;
206
207 private int millisecond = -1;
208
209 private int minute = -1;
210
211 private int month = -1;
212
213 private int second = -1;
214
215 private int year = -1;
216
217
218
219
220
221
222
223
224 public FitsDate(String dStr) throws FitsException {
225
226 if (dStr == null || dStr.isEmpty()) {
227 return;
228 }
229
230 Matcher match = NORMAL_REGEX.matcher(dStr);
231 if (match.matches()) {
232
233 year = Integer.parseInt(match.group(NEW_FORMAT_YEAR_GROUP));
234 month = getInt(match, NEW_FORMAT_MONTH_GROUP);
235 mday = getInt(match, NEW_FORMAT_DAY_OF_MONTH_GROUP);
236 hour = getInt(match, NEW_FORMAT_HOUR_GROUP);
237 minute = getInt(match, NEW_FORMAT_MINUTE_GROUP);
238 second = getInt(match, NEW_FORMAT_SECOND_GROUP);
239 millisecond = getMilliseconds(match, NEW_FORMAT_MILLISECOND_GROUP);
240 } else {
241
242 match = OLD_REGEX.matcher(dStr);
243 if (!match.matches()) {
244 if (dStr.trim().isEmpty()) {
245 return;
246 }
247 throw new FitsException("Bad FITS date string \"" + dStr + '"');
248 }
249 year = getInt(match, OLD_FORMAT_YEAR_GROUP) + YEAR_OFFSET;
250 month = getInt(match, OLD_FORMAT_MONTH_GROUP);
251 mday = getInt(match, OLD_FORMAT_DAY_OF_MONTH_GROUP);
252 }
253 }
254
255 private static int getInt(Matcher match, int groupIndex) throws NumberFormatException {
256 String value = match.group(groupIndex);
257 if (value != null) {
258 return Integer.parseInt(value);
259 }
260 return -1;
261 }
262
263 private static int getMilliseconds(Matcher match, int groupIndex) throws NumberFormatException {
264 String value = match.group(groupIndex);
265 if (value != null) {
266 value = String.format("%-3s", value).replace(' ', '0');
267 int num = Integer.parseInt(value);
268 if (value.length() > NB_DIGITS_MILLIS) {
269 num = (int) Math.round(num / Math.pow(POW_TEN, value.length() - NB_DIGITS_MILLIS));
270 }
271 return num;
272 }
273 return -1;
274 }
275
276
277
278
279
280
281 public Date toDate() {
282 if (month == -1) {
283 return null;
284 }
285
286 Calendar cal = new GregorianCalendar(UTC);
287
288 if (year > 0) {
289 cal.set(Calendar.ERA, GregorianCalendar.AD);
290 cal.set(Calendar.YEAR, year);
291 } else {
292 cal.set(Calendar.ERA, GregorianCalendar.BC);
293 cal.set(Calendar.YEAR, 1 - year);
294 }
295 cal.set(Calendar.MONTH, month - 1);
296 cal.set(Calendar.DAY_OF_MONTH, mday);
297
298 if (hour == -1) {
299 cal.set(Calendar.HOUR_OF_DAY, 0);
300 cal.set(Calendar.MINUTE, 0);
301 cal.set(Calendar.SECOND, 0);
302 cal.set(Calendar.MILLISECOND, 0);
303 } else {
304 cal.set(Calendar.HOUR_OF_DAY, hour);
305 cal.set(Calendar.MINUTE, minute);
306 cal.set(Calendar.SECOND, second);
307 if (millisecond == -1) {
308 cal.set(Calendar.MILLISECOND, 0);
309 } else {
310 cal.set(Calendar.MILLISECOND, millisecond);
311 }
312 }
313 return cal.getTime();
314 }
315
316 @Override
317 public String toString() {
318 if (month == -1) {
319 return "";
320 }
321
322
323
324 String formatted = getFitsDateString(toDate(), hour != -1);
325 if (hour != -1 && millisecond == -1) {
326 return formatted.substring(0, formatted.lastIndexOf('.'));
327 }
328 return formatted;
329 }
330
331 @Override
332 public boolean equals(Object o) {
333 if (o == this) {
334 return true;
335 }
336 if (!(o instanceof FitsDate)) {
337 return false;
338 }
339
340 return compareTo((FitsDate) o) == 0;
341 }
342
343 @Override
344 public int hashCode() {
345 return Integer.hashCode(year) ^ Integer.hashCode(month) ^ Integer.hashCode(mday) ^ Integer.hashCode(hour)
346 ^ Integer.hashCode(minute) ^ Integer.hashCode(second) ^ Integer.hashCode(millisecond);
347 }
348
349 @Override
350 public int compareTo(FitsDate fitsDate) {
351 int result = Integer.compare(year, fitsDate.year);
352 if (result != 0) {
353 return result;
354 }
355
356 result = Integer.compare(month, fitsDate.month);
357 if (result != 0) {
358 return result;
359 }
360
361 result = Integer.compare(mday, fitsDate.mday);
362 if (result != 0) {
363 return result;
364 }
365
366 result = Integer.compare(hour, fitsDate.hour);
367 if (result != 0) {
368 return result;
369 }
370
371 result = Integer.compare(minute, fitsDate.minute);
372 if (result != 0) {
373 return result;
374 }
375
376 result = Integer.compare(second, fitsDate.second);
377 if (result != 0) {
378 return result;
379 }
380
381 return Integer.compare(millisecond, fitsDate.millisecond);
382 }
383
384 private static void appendFourDigitValue(StringBuilder buf, int value) {
385 if (value < FIRST_FOUR_CHARACTER_VALUE) {
386 buf.append('0');
387 }
388 appendThreeDigitValue(buf, value);
389 }
390
391 private static void appendFiveDigitValue(StringBuilder buf, int value) {
392 if (value < 0) {
393 buf.append('-');
394 value = -value;
395 } else {
396 buf.append('+');
397 }
398 if (value < FIRST_FIVE_CHARACTER_VALUE) {
399 buf.append('0');
400 }
401 appendFourDigitValue(buf, value);
402 }
403
404 private static void appendThreeDigitValue(StringBuilder buf, int value) {
405 if (value < FIRST_THREE_CHARACTER_VALUE) {
406 buf.append('0');
407 }
408 appendTwoDigitValue(buf, value);
409 }
410
411 private static void appendTwoDigitValue(StringBuilder buf, int value) {
412 if (value < FIRST_TWO_CHARACTER_VALUE) {
413 buf.append('0');
414 }
415 buf.append(value);
416 }
417 }