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