source: josm/trunk/src/org/openstreetmap/josm/tools/date/DateUtils.java@ 11048

Last change on this file since 11048 was 11048, checked in by Don-vip, 8 years ago

sonar

  • Property svn:eol-style set to native
File size: 12.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.tools.date;
3
4import java.text.DateFormat;
5import java.text.ParsePosition;
6import java.text.SimpleDateFormat;
7import java.time.Instant;
8import java.time.ZoneId;
9import java.time.ZoneOffset;
10import java.time.ZonedDateTime;
11import java.time.format.DateTimeFormatter;
12import java.util.Date;
13import java.util.Locale;
14import java.util.TimeZone;
15
16import javax.xml.datatype.DatatypeConfigurationException;
17import javax.xml.datatype.DatatypeFactory;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.preferences.BooleanProperty;
21import org.openstreetmap.josm.tools.CheckParameterUtil;
22import org.openstreetmap.josm.tools.UncheckedParseException;
23
24/**
25 * A static utility class dealing with:
26 * <ul>
27 * <li>parsing XML date quickly and formatting a date to the XML UTC format regardless of current locale</li>
28 * <li>providing a single entry point for formatting dates to be displayed in JOSM GUI, based on user preferences</li>
29 * </ul>
30 * @author nenik
31 */
32public final class DateUtils {
33
34 /**
35 * The UTC time zone.
36 */
37 public static final TimeZone UTC = TimeZone.getTimeZone("UTC");
38
39 /**
40 * Property to enable display of ISO dates globally.
41 * @since 7299
42 */
43 public static final BooleanProperty PROP_ISO_DATES = new BooleanProperty("iso.dates", false);
44
45 private static final DatatypeFactory XML_DATE;
46
47 static {
48 DatatypeFactory fact = null;
49 try {
50 fact = DatatypeFactory.newInstance();
51 } catch (DatatypeConfigurationException ce) {
52 Main.error(ce);
53 }
54 XML_DATE = fact;
55 }
56
57 protected DateUtils() {
58 // Hide default constructor for utils classes
59 }
60
61 /**
62 * Parses XML date quickly, regardless of current locale.
63 * @param str The XML date as string
64 * @return The date
65 * @throws UncheckedParseException if the date does not match any of the supported date formats
66 */
67 public static synchronized Date fromString(String str) {
68 return new Date(tsFromString(str));
69 }
70
71 /**
72 * Parses XML date quickly, regardless of current locale.
73 * @param str The XML date as string
74 * @return The date in milliseconds since epoch
75 * @throws UncheckedParseException if the date does not match any of the supported date formats
76 */
77 public static synchronized long tsFromString(String str) {
78 // "2007-07-25T09:26:24{Z|{+|-}01[:00]}"
79 if (checkLayout(str, "xxxx-xx-xxTxx:xx:xxZ") ||
80 checkLayout(str, "xxxx-xx-xxTxx:xx:xx") ||
81 checkLayout(str, "xxxx:xx:xx xx:xx:xx") ||
82 checkLayout(str, "xxxx-xx-xx xx:xx:xx UTC") ||
83 checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx") ||
84 checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx") ||
85 checkLayout(str, "xxxx-xx-xxTxx:xx:xx+xx:00") ||
86 checkLayout(str, "xxxx-xx-xxTxx:xx:xx-xx:00")) {
87 final ZonedDateTime local = ZonedDateTime.of(
88 parsePart4(str, 0),
89 parsePart2(str, 5),
90 parsePart2(str, 8),
91 parsePart2(str, 11),
92 parsePart2(str, 14),
93 parsePart2(str, 17),
94 0,
95 // consider EXIF date in default timezone
96 checkLayout(str, "xxxx:xx:xx xx:xx:xx") ? ZoneId.systemDefault() : ZoneOffset.UTC
97 );
98 if (str.length() == 22 || str.length() == 25) {
99 final int plusHr = parsePart2(str, 20);
100 final long mul = str.charAt(19) == '+' ? -1 : 1;
101 return local.plusHours(plusHr * mul).toInstant().toEpochMilli();
102 }
103 return local.toInstant().toEpochMilli();
104 } else if (checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxxZ") ||
105 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx") ||
106 checkLayout(str, "xxxx:xx:xx xx:xx:xx.xxx") ||
107 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx+xx:00") ||
108 checkLayout(str, "xxxx-xx-xxTxx:xx:xx.xxx-xx:00")) {
109 final ZonedDateTime local = ZonedDateTime.of(
110 parsePart4(str, 0),
111 parsePart2(str, 5),
112 parsePart2(str, 8),
113 parsePart2(str, 11),
114 parsePart2(str, 14),
115 parsePart2(str, 17),
116 parsePart3(str, 20) * 1_000_000,
117 // consider EXIF date in default timezone
118 checkLayout(str, "xxxx:xx:xx xx:xx:xx.xxx") ? ZoneId.systemDefault() : ZoneOffset.UTC
119 );
120 if (str.length() == 29) {
121 final int plusHr = parsePart2(str, 24);
122 final long mul = str.charAt(23) == '+' ? -1 : 1;
123 return local.plusHours(plusHr * mul).toInstant().toEpochMilli();
124 }
125 return local.toInstant().toEpochMilli();
126 } else {
127 // example date format "18-AUG-08 13:33:03"
128 SimpleDateFormat f = new SimpleDateFormat("dd-MMM-yy HH:mm:ss");
129 Date d = f.parse(str, new ParsePosition(0));
130 if (d != null)
131 return d.getTime();
132 }
133
134 try {
135 return XML_DATE.newXMLGregorianCalendar(str).toGregorianCalendar().getTimeInMillis();
136 } catch (IllegalArgumentException ex) {
137 throw new UncheckedParseException("The date string (" + str + ") could not be parsed.", ex);
138 }
139 }
140
141 /**
142 * Formats a date to the XML UTC format regardless of current locale.
143 * @param timestamp number of seconds since the epoch
144 * @return The formatted date
145 */
146 public static synchronized String fromTimestamp(int timestamp) {
147 final ZonedDateTime temporal = Instant.ofEpochMilli(timestamp * 1000L).atZone(ZoneOffset.UTC);
148 return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(temporal);
149 }
150
151 /**
152 * Formats a date to the XML UTC format regardless of current locale.
153 * @param date The date to format
154 * @return The formatted date
155 */
156 public static synchronized String fromDate(Date date) {
157 final ZonedDateTime temporal = date.toInstant().atZone(ZoneOffset.UTC);
158 return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(temporal);
159 }
160
161 private static boolean checkLayout(String text, String pattern) {
162 if (text.length() != pattern.length())
163 return false;
164 for (int i = 0; i < pattern.length(); i++) {
165 char pc = pattern.charAt(i);
166 char tc = text.charAt(i);
167 if (pc == 'x' && Character.isDigit(tc))
168 continue;
169 else if (pc == 'x' || pc != tc)
170 return false;
171 }
172 return true;
173 }
174
175 private static int num(char c) {
176 return c - '0';
177 }
178
179 private static int parsePart2(String str, int off) {
180 return 10 * num(str.charAt(off)) + num(str.charAt(off + 1));
181 }
182
183 private static int parsePart3(String str, int off) {
184 return 100 * num(str.charAt(off)) + 10 * num(str.charAt(off + 1)) + num(str.charAt(off + 2));
185 }
186
187 private static int parsePart4(String str, int off) {
188 return 1000 * num(str.charAt(off)) + 100 * num(str.charAt(off + 1)) + 10 * num(str.charAt(off + 2)) + num(str.charAt(off + 3));
189 }
190
191 /**
192 * Returns a new {@code SimpleDateFormat} for date only, according to <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>.
193 * @return a new ISO 8601 date format, for date only.
194 * @since 7299
195 */
196 public static SimpleDateFormat newIsoDateFormat() {
197 return new SimpleDateFormat("yyyy-MM-dd");
198 }
199
200 /**
201 * Returns a new {@code SimpleDateFormat} for date and time, according to <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601</a>.
202 * @return a new ISO 8601 date format, for date and time.
203 * @since 7299
204 */
205 public static SimpleDateFormat newIsoDateTimeFormat() {
206 return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
207 }
208
209 /**
210 * Returns a new {@code SimpleDateFormat} for date and time, according to format used in OSM API errors.
211 * @return a new date format, for date and time, to use for OSM API error handling.
212 * @since 7299
213 */
214 public static SimpleDateFormat newOsmApiDateTimeFormat() {
215 // Example: "2010-09-07 14:39:41 UTC".
216 // Always parsed with US locale regardless of the current locale in JOSM
217 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z", Locale.US);
218 }
219
220 /**
221 * Returns the date format to be used for current user, based on user preferences.
222 * @param dateStyle The date style as described in {@link DateFormat#getDateInstance}. Ignored if "ISO dates" option is set
223 * @return The date format
224 * @since 7299
225 */
226 public static DateFormat getDateFormat(int dateStyle) {
227 if (PROP_ISO_DATES.get()) {
228 return newIsoDateFormat();
229 } else {
230 return DateFormat.getDateInstance(dateStyle, Locale.getDefault());
231 }
232 }
233
234 /**
235 * Formats a date to be displayed to current user, based on user preferences.
236 * @param date The date to display. Must not be {@code null}
237 * @param dateStyle The date style as described in {@link DateFormat#getDateInstance}. Ignored if "ISO dates" option is set
238 * @return The formatted date
239 * @since 7299
240 */
241 public static String formatDate(Date date, int dateStyle) {
242 CheckParameterUtil.ensureParameterNotNull(date, "date");
243 return getDateFormat(dateStyle).format(date);
244 }
245
246 /**
247 * Returns the time format to be used for current user, based on user preferences.
248 * @param timeStyle The time style as described in {@link DateFormat#getTimeInstance}. Ignored if "ISO dates" option is set
249 * @return The time format
250 * @since 7299
251 */
252 public static DateFormat getTimeFormat(int timeStyle) {
253 if (PROP_ISO_DATES.get()) {
254 // This is not strictly conform to ISO 8601. We just want to avoid US-style times such as 3.30pm
255 return new SimpleDateFormat("HH:mm:ss");
256 } else {
257 return DateFormat.getTimeInstance(timeStyle, Locale.getDefault());
258 }
259 }
260
261 /**
262 * Formats a time to be displayed to current user, based on user preferences.
263 * @param time The time to display. Must not be {@code null}
264 * @param timeStyle The time style as described in {@link DateFormat#getTimeInstance}. Ignored if "ISO dates" option is set
265 * @return The formatted time
266 * @since 7299
267 */
268 public static String formatTime(Date time, int timeStyle) {
269 CheckParameterUtil.ensureParameterNotNull(time, "time");
270 return getTimeFormat(timeStyle).format(time);
271 }
272
273 /**
274 * Returns the date/time format to be used for current user, based on user preferences.
275 * @param dateStyle The date style as described in {@link DateFormat#getDateTimeInstance}. Ignored if "ISO dates" option is set
276 * @param timeStyle The time style as described in {@code DateFormat.getDateTimeInstance}. Ignored if "ISO dates" option is set
277 * @return The date/time format
278 * @since 7299
279 */
280 public static DateFormat getDateTimeFormat(int dateStyle, int timeStyle) {
281 if (PROP_ISO_DATES.get()) {
282 // This is not strictly conform to ISO 8601. We just want to avoid US-style times such as 3.30pm
283 // and we don't want to use the 'T' separator as a space character is much more readable
284 return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
285 } else {
286 return DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.getDefault());
287 }
288 }
289
290 /**
291 * Formats a date/time to be displayed to current user, based on user preferences.
292 * @param datetime The date/time to display. Must not be {@code null}
293 * @param dateStyle The date style as described in {@link DateFormat#getDateTimeInstance}. Ignored if "ISO dates" option is set
294 * @param timeStyle The time style as described in {@code DateFormat.getDateTimeInstance}. Ignored if "ISO dates" option is set
295 * @return The formatted date/time
296 * @since 7299
297 */
298 public static String formatDateTime(Date datetime, int dateStyle, int timeStyle) {
299 CheckParameterUtil.ensureParameterNotNull(datetime, "datetime");
300 return getDateTimeFormat(dateStyle, timeStyle).format(datetime);
301 }
302}
Note: See TracBrowser for help on using the repository browser.