1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.search
;
5 import com
.google
.apphosting
.api
.AppEngineInternal
;
7 import java
.text
.DateFormat
;
8 import java
.text
.ParsePosition
;
9 import java
.text
.SimpleDateFormat
;
10 import java
.util
.Calendar
;
11 import java
.util
.Date
;
12 import java
.util
.GregorianCalendar
;
13 import java
.util
.Locale
;
14 import java
.util
.TimeZone
;
17 * A utility class that centralizes processing of dates.
21 public final class DateUtil
{
24 * The milliseconds in a day.
26 public static final int MILLISECONDS_IN_DAY
= 24 * 60 * 60 * 1000;
31 private static final ThreadLocal
<TimeZone
> UTC_TZ
=
32 new ThreadLocal
<TimeZone
>() {
33 @Override protected TimeZone
initialValue() {
34 return TimeZone
.getTimeZone("UTC");
39 * The maximum date that can be stored in a date field.
41 * @deprecated Starting from SDK version 1.7.4, use
42 * {@link com.google.appengine.api.search.checkers.FieldChecker#MAX_DATE}.
44 @Deprecated public static final Date MAX_DATE
=
45 getEpochPlusDays(Integer
.MAX_VALUE
, MILLISECONDS_IN_DAY
- 1);
48 * The minimum date that can be stored in a date field.
50 * @deprecated Starting from SDK version 1.7.4, use
51 * {@link com.google.appengine.api.search.checkers.FieldChecker#MIN_DATE}.
53 @Deprecated public static final Date MIN_DATE
= getEpochPlusDays(Integer
.MIN_VALUE
, 0);
55 private static DateFormat
getDateFormat(String formatString
) {
56 DateFormat format
= new SimpleDateFormat(formatString
, Locale
.US
);
57 format
.setTimeZone(UTC_TZ
.get());
61 private static final ThreadLocal
<DateFormat
> ISO8601_SIMPLE
=
62 new ThreadLocal
<DateFormat
>() {
63 @Override protected DateFormat
initialValue() {
64 return getDateFormat("yyyy-MM-dd");
68 private static final ThreadLocal
<DateFormat
> ISO8601_DATE_TIME_SIMPLE
=
69 new ThreadLocal
<DateFormat
>() {
70 @Override protected DateFormat
initialValue() {
71 return getDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
75 private static final ThreadLocal
<DateFormat
> ISO8601_DATE_TIME_SIMPLE_ERA
=
76 new ThreadLocal
<DateFormat
>() {
77 @Override protected DateFormat
initialValue() {
78 return getDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'G");
86 * Get a UTC calendar with Locale US.
88 private static Calendar
getCalendarUTC() {
89 return new GregorianCalendar(UTC_TZ
.get(), Locale
.US
);
93 * Formats a date according ISO 8601 full date time format. Currently,
94 * this is used to print dates for error messages.
96 * @param date the date to format as a string
97 * @return a string representing the date in ISO 8601 format
99 public static String
formatDateTime(Date date
) {
103 return isBeforeCommonEra(date
) ?
104 ISO8601_DATE_TIME_SIMPLE_ERA
.get().format(date
) :
105 ISO8601_DATE_TIME_SIMPLE
.get().format(date
);
109 * Returns true if the date is before the common era.
111 private static boolean isBeforeCommonEra(Date date
) {
112 Calendar cal
= getCalendarUTC();
114 return cal
.get(Calendar
.ERA
) == GregorianCalendar
.BC
;
118 * Parses an ISO 8601 into a {@link Date} object.
120 * This function is only used for deserializing legacy "yyyy-MM-dd"
121 * formatted dates stored in backend. These are currently not supporting
122 * BC Era dates, so neither will this function.
124 * @param dateString the ISO 8601 formatted string for a date
125 * @return the {@link Date} parsed from the date string
127 private static Date
parseDate(String dateString
) {
128 ParsePosition pos
= new ParsePosition(0);
129 Date d
= ISO8601_SIMPLE
.get().parse(dateString
, pos
);
130 if (pos
.getIndex() < dateString
.length()) {
131 throw new IllegalArgumentException(
132 String
.format("Failed to parse date string \"%s\"", dateString
));
138 * Converts date into a string containing the milliseconds since the UNIX Epoch.
140 * @param date the date to serialize as a string
141 * @return a string representing the date as milliseconds since the UNIX Epoch
143 public static String
serializeDate(Date date
) {
144 return date
== null ?
"" : Long
.toString(date
.getTime());
148 * Converts a string containing the milliseconds since the UNIX Epoch into a Date.
150 * Two formats of date string are supported: "yyyy-MM-dd" and a long. Eventually,
151 * the "yyyy-MM-dd" format support will be removed.
153 * @param date the date string to deserialize into a date
156 public static Date
deserializeDate(String date
) {
160 if (date
.startsWith("-")) {
161 if (date
.length() > 1 && date
.indexOf('-', 1) >= 0) {
162 return parseDate(date
);
165 if (date
.indexOf('-') > 0) {
166 return parseDate(date
);
169 return new Date(Long
.parseLong(date
));
173 * Truncates given date leaving date elements lesser than the
174 * specified field set to 0. For example, if you wish to remove
175 * time component from the date use the following:
178 * Date yearMonthDay = Field.truncate(d, Calendar.DAY_OF_MONTH);
181 * @param date the date to be truncated
182 * @param field the least significant field to be left untouched
183 * @return the date with fields less significant than field set to 0
184 * @throws IllegalArgumentException if field is not a valid datetime field.
185 * @deprecated as of 1.7.2 this is no longer required for Date fields
188 static Date
truncate(Date date
, int field
) {
189 if (Calendar
.MILLISECOND
== field
) {
192 Calendar cal
= getCalendarUTC();
194 cal
.set(Calendar
.MILLISECOND
, 0);
195 if (Calendar
.SECOND
== field
) {
196 return cal
.getTime();
198 cal
.set(Calendar
.SECOND
, 0);
199 if (Calendar
.MINUTE
== field
) {
200 return cal
.getTime();
202 cal
.set(Calendar
.MINUTE
, 0);
203 if (Calendar
.HOUR_OF_DAY
== field
) {
204 return cal
.getTime();
206 cal
.set(Calendar
.HOUR_OF_DAY
, 0);
207 if (Calendar
.DAY_OF_MONTH
== field
) {
208 return cal
.getTime();
210 cal
.set(Calendar
.DAY_OF_MONTH
, 1);
211 if (Calendar
.MONTH
== field
) {
212 return cal
.getTime();
214 cal
.set(Calendar
.MONTH
, 0);
215 if (Calendar
.YEAR
== field
) {
216 return cal
.getTime();
218 throw new IllegalArgumentException("Invalid field value " + field
);
222 * Constructs a {@link Date} set to the UNIX Epoch plus days plus milliseconds.
224 * @param days the number of days to add to the UNIX Epoch to
226 * @param milliseconds the number of milliseconds to add to the date
227 * @return the Date with number of days plus Epoch
229 public static final Date
getEpochPlusDays(int days
, int milliseconds
) {
230 Calendar cal
= getCalendarUTC();
231 cal
.setTimeInMillis(0L);
232 cal
.add(Calendar
.DATE
, days
);
233 cal
.add(Calendar
.MILLISECOND
, milliseconds
);
234 return cal
.getTime();