1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.search
;
5 import java
.text
.DateFormat
;
6 import java
.text
.ParsePosition
;
7 import java
.text
.SimpleDateFormat
;
8 import java
.util
.Calendar
;
10 import java
.util
.GregorianCalendar
;
11 import java
.util
.Locale
;
12 import java
.util
.TimeZone
;
15 * A utility class that centralizes processing of dates.
18 public final class DateUtil
{
21 * The milliseconds in a day.
23 private static final int MILLISECONDS_IN_DAY
= 24 * 60 * 60 * 1000;
28 private static final ThreadLocal
<TimeZone
> UTC_TZ
=
29 new ThreadLocal
<TimeZone
>() {
30 @Override protected TimeZone
initialValue() {
31 return TimeZone
.getTimeZone("UTC");
36 * The maximum date that can be stored in a date field.
38 public static final Date MAX_DATE
= getEpochPlusDays(Integer
.MAX_VALUE
, MILLISECONDS_IN_DAY
- 1);
41 * The minimum date that can be stored in a date field.
43 public static final Date MIN_DATE
= getEpochPlusDays(Integer
.MIN_VALUE
, 0);
45 private static DateFormat
getDateFormat(String formatString
) {
46 DateFormat format
= new SimpleDateFormat(formatString
, Locale
.US
);
47 format
.setTimeZone(UTC_TZ
.get());
51 private static final ThreadLocal
<DateFormat
> ISO8601_SIMPLE
=
52 new ThreadLocal
<DateFormat
>() {
53 @Override protected DateFormat
initialValue() {
54 return getDateFormat("yyyy-MM-dd");
58 private static final ThreadLocal
<DateFormat
> ISO8601_DATE_TIME_SIMPLE
=
59 new ThreadLocal
<DateFormat
>() {
60 @Override protected DateFormat
initialValue() {
61 return getDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
65 private static final ThreadLocal
<DateFormat
> ISO8601_DATE_TIME_SIMPLE_ERA
=
66 new ThreadLocal
<DateFormat
>() {
67 @Override protected DateFormat
initialValue() {
68 return getDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'G");
76 * Get a UTC calendar with Locale US.
78 private static Calendar
getCalendarUTC() {
79 return new GregorianCalendar(UTC_TZ
.get(), Locale
.US
);
83 * Formats a date according ISO 8601 full date time format. Currently,
84 * this is used to print dates for error messages.
86 * @param date the date to format as a string
87 * @return a string representing the date in ISO 8601 format
89 public static String
formatDateTime(Date date
) {
93 return isBeforeCommonEra(date
) ?
94 ISO8601_DATE_TIME_SIMPLE_ERA
.get().format(date
) :
95 ISO8601_DATE_TIME_SIMPLE
.get().format(date
);
99 * Returns true if the date is before the common era.
101 private static boolean isBeforeCommonEra(Date date
) {
102 Calendar cal
= getCalendarUTC();
104 return cal
.get(Calendar
.ERA
) == GregorianCalendar
.BC
;
108 * Parses an ISO 8601 into a {@link Date} object.
110 * This function is only used for deserializing legacy "yyyy-MM-dd"
111 * formatted dates stored in backend. These are currently not supporting
112 * BC Era dates, so neither will this function.
114 * @param dateString the ISO 8601 formatted string for a date
115 * @return the {@link Date} parsed from the date string
117 private static Date
parseDate(String dateString
) {
118 ParsePosition pos
= new ParsePosition(0);
119 Date d
= ISO8601_SIMPLE
.get().parse(dateString
, pos
);
120 if (pos
.getIndex() < dateString
.length()) {
121 throw new IllegalArgumentException(
122 String
.format("Failed to parse date string \"%s\"", dateString
));
128 * Converts date into a string containing the milliseconds since the UNIX Epoch.
130 * @param date the date to serialize as a string
131 * @return a string representing the date as milliseconds since the UNIX Epoch
133 public static String
serializeDate(Date date
) {
134 return date
== null ?
"" : Long
.toString(date
.getTime());
138 * Converts a string containing the milliseconds since the UNIX Epoch into a Date.
140 * Two formats of date string are supported: "yyyy-MM-dd" and a long. Eventually,
141 * the "yyyy-MM-dd" format support will be removed.
143 * @param date the date string to deserialize into a date
146 public static Date
deserializeDate(String date
) {
150 if (date
.startsWith("-")) {
151 if (date
.length() > 1 && date
.indexOf('-', 1) >= 0) {
152 return parseDate(date
);
155 if (date
.indexOf('-') > 0) {
156 return parseDate(date
);
159 return new Date(Long
.parseLong(date
));
163 * Truncates given date leaving date elements lesser than the
164 * specified field set to 0. For example, if you wish to remove
165 * time component from the date use the following:
168 * Date yearMonthDay = Field.truncate(d, Calendar.DAY_OF_MONTH);
171 * @param date the date to be truncated
172 * @param field the least significant field to be left untouched
173 * @return the date with fields less significant than field set to 0
174 * @throws IllegalArgumentException if field is not a valid datetime field.
175 * @deprecated as of 1.7.2 this is no longer required for Date fields
178 static Date
truncate(Date date
, int field
) {
179 if (Calendar
.MILLISECOND
== field
) {
182 Calendar cal
= getCalendarUTC();
184 cal
.set(Calendar
.MILLISECOND
, 0);
185 if (Calendar
.SECOND
== field
) {
186 return cal
.getTime();
188 cal
.set(Calendar
.SECOND
, 0);
189 if (Calendar
.MINUTE
== field
) {
190 return cal
.getTime();
192 cal
.set(Calendar
.MINUTE
, 0);
193 if (Calendar
.HOUR_OF_DAY
== field
) {
194 return cal
.getTime();
196 cal
.set(Calendar
.HOUR_OF_DAY
, 0);
197 if (Calendar
.DAY_OF_MONTH
== field
) {
198 return cal
.getTime();
200 cal
.set(Calendar
.DAY_OF_MONTH
, 1);
201 if (Calendar
.MONTH
== field
) {
202 return cal
.getTime();
204 cal
.set(Calendar
.MONTH
, 0);
205 if (Calendar
.YEAR
== field
) {
206 return cal
.getTime();
208 throw new IllegalArgumentException("Invalid field value " + field
);
212 * Constructs a {@link} set to the UNIX Epoch plus days plus milliseconds.
214 * @param days the number of days to add to the UNIX Epoch to
216 * @param milliseconds the number of milliseconds to add to the date
217 * @return the Date with number of days plus Epoch
219 private static final Date
getEpochPlusDays(int days
, int milliseconds
) {
220 Calendar cal
= getCalendarUTC();
221 cal
.setTimeInMillis(0L);
222 cal
.add(Calendar
.DATE
, days
);
223 cal
.add(Calendar
.MILLISECOND
, milliseconds
);
224 return cal
.getTime();