Version 1.7.4
[gae.git] / java / src / main / com / google / appengine / api / search / DateUtil.java
blobab56468ac965c4a9249854378f0e1f1325fdcf3f
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;
16 /**
17 * A utility class that centralizes processing of dates.
20 @AppEngineInternal
21 public final class DateUtil {
23 /**
24 * The milliseconds in a day.
26 public static final int MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
28 /**
29 * The UTC time zone.
31 private static final ThreadLocal<TimeZone> UTC_TZ =
32 new ThreadLocal<TimeZone>() {
33 @Override protected TimeZone initialValue() {
34 return TimeZone.getTimeZone("UTC");
38 /**
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);
47 /**
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());
58 return format;
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");
82 private DateUtil() {
85 /**
86 * Get a UTC calendar with Locale US.
88 private static Calendar getCalendarUTC() {
89 return new GregorianCalendar(UTC_TZ.get(), Locale.US);
92 /**
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) {
100 if (date == null) {
101 return null;
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();
113 cal.setTime(date);
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));
134 return d;
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
154 * @return a date
156 public static Date deserializeDate(String date) {
157 if (date == null) {
158 return null;
160 if (date.startsWith("-")) {
161 if (date.length() > 1 && date.indexOf('-', 1) >= 0) {
162 return parseDate(date);
164 } else {
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:
176 * <pre>
177 * Date d = ...
178 * Date yearMonthDay = Field.truncate(d, Calendar.DAY_OF_MONTH);
179 * </pre>
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
187 @Deprecated
188 static Date truncate(Date date, int field) {
189 if (Calendar.MILLISECOND == field) {
190 return date;
192 Calendar cal = getCalendarUTC();
193 cal.setTime(date);
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
225 * get a Date
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();