View Javadoc

1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.core.impl.datetime;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.lang.time.DurationFormatUtils;
20  import org.kuali.rice.core.api.datetime.DateTimeService;
21  import org.kuali.rice.core.api.CoreConstants;
22  import org.kuali.rice.core.api.config.property.ConfigContext;
23  import org.springframework.beans.factory.InitializingBean;
24  
25  import java.sql.Time;
26  import java.sql.Timestamp;
27  import java.text.DateFormat;
28  import java.text.ParseException;
29  import java.text.ParsePosition;
30  import java.text.SimpleDateFormat;
31  import java.util.Arrays;
32  import java.util.Calendar;
33  import java.util.Collections;
34  import java.util.Date;
35  import java.util.List;
36  
37  /**
38   * This class is the service implementation for a DateTime structure. This is
39   * the default, Kuali delivered implementation.
40   */
41  //@Transactional
42  public class DateTimeServiceImpl implements DateTimeService, InitializingBean {
43      /**
44       * Default date/time formats
45       */
46      private static final String STRING_TO_DATE_FORMATS = "MM/dd/yyyy hh:mm a;MM/dd/yy;MM/dd/yyyy;MM-dd-yy;MM-dd-yyyy;MMddyy;MMMM dd;yyyy;MM/dd/yy HH:mm:ss;MM/dd/yyyy HH:mm:ss;MM-dd-yy HH:mm:ss;MMddyy HH:mm:ss;MMMM dd HH:mm:ss;yyyy HH:mm:ss";
47      private static final String STRING_TO_TIME_FORMATS = "hh:mm aa";
48      private static final String STRING_TO_TIMESTAMP_FORMATS = "MM/dd/yyyy hh:mm a;MM/dd/yy;MM/dd/yyyy;MM-dd-yy;MMddyy;MMMM dd;yyyy;MM/dd/yy HH:mm:ss;MM/dd/yyyy HH:mm:ss;MM-dd-yy HH:mm:ss;MMddyy HH:mm:ss;MMMM dd HH:mm:ss;yyyy HH:mm:ss";
49      private static final String DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE = "MM/dd/yyyy";
50      private static final String TIME_TO_STRING_FORMAT_FOR_USER_INTERFACE = "hh:mm aa";
51      private static final String TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE = "MM/dd/yyyy hh:mm a";
52      private static final String DATE_TO_STRING_FORMAT_FOR_FILE_NAME = "yyyyMMdd";
53      private static final String TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME = "yyyyMMdd-HH-mm-ss-S";
54  
55  	protected String[] stringToDateFormats;
56      protected String[] stringToTimeFormats;
57  	protected String[] stringToTimestampFormats;
58  	protected String dateToStringFormatForUserInterface;
59      protected String timeToStringFormatForUserInterface;
60  	protected String timestampToStringFormatForUserInterface;
61  	protected String dateToStringFormatForFileName;
62  	protected String timestampToStringFormatForFileName;
63  
64  		
65  	/**
66  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateString(java.util.Date)
67  	 */
68  	public String toDateString(Date date) {
69  		return toString(date, dateToStringFormatForUserInterface);
70  	}
71  
72      /**
73       * @see org.kuali.rice.core.api.datetime.DateTimeService#toTimeString(java.sql.Time)
74       */
75      public String toTimeString(Time time) {
76          return toString(time, timeToStringFormatForUserInterface);
77      }
78  
79  	/**
80  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateTimeString(java.util.Date)
81  	 */
82  	public String toDateTimeString(Date date) {
83  		return toString(date, timestampToStringFormatForUserInterface);
84  	}
85  
86  	/**
87  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toString(java.util.Date,
88  	 *      java.lang.String)
89  	 */
90  	public String toString(Date date, String pattern) {
91  		DateFormat dateFormat = new SimpleDateFormat(pattern);
92  		dateFormat.setLenient(false);
93  		return dateFormat.format(date);
94  	}
95  
96  	/**
97  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentDate()
98  	 */
99  	public Date getCurrentDate() {
100 		Calendar c = Calendar.getInstance();
101 		c.setTime(new Date());
102 		return c.getTime();
103 	}
104 
105 	/**
106 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentTimestamp()
107 	 */
108 	public Timestamp getCurrentTimestamp() {
109 		return new java.sql.Timestamp(getCurrentDate().getTime());
110 	}
111 
112 	/**
113 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDate()
114 	 */
115 	public java.sql.Date getCurrentSqlDate() {
116 		return new java.sql.Date(getCurrentDate().getTime());
117 	}
118 
119 	/**
120 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDateMidnight()
121 	 */
122 	public java.sql.Date getCurrentSqlDateMidnight() {
123 		// simple and not unduely inefficient way to truncate the time component
124 		return java.sql.Date.valueOf(getCurrentSqlDate().toString());
125 	}
126 
127 	/**
128 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentCalendar()
129 	 */
130 	public Calendar getCurrentCalendar() {
131 		return getCalendar(getCurrentDate());
132 	}
133 
134 	/**
135 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCalendar
136 	 */
137 	public Calendar getCalendar(Date date) {
138 		if (date == null) {
139 			throw new IllegalArgumentException("invalid (null) date");
140 		}
141 
142 		Calendar currentCalendar = Calendar.getInstance();
143 		currentCalendar.setTime(date);
144 
145 		return currentCalendar;
146 	}
147 
148 	/**
149 	 * Formats strings into dates using the format string in the KR-NS/All/STRING_TO_DATE_FORMATS parameter
150 	 *
151 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDate(java.lang.String)
152 	 */
153 	public Date convertToDate(String dateString) throws ParseException {
154 		return parseAgainstFormatArray(dateString, stringToDateFormats);
155 	}
156 
157 	/**
158 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDateTime(java.lang.String)
159 	 */
160 	public Date convertToDateTime(String dateTimeString) throws ParseException {
161 		if (StringUtils.isBlank(dateTimeString)) {
162 			throw new IllegalArgumentException("invalid (blank) date/time string");
163 		}
164 		return parseAgainstFormatArray(dateTimeString, stringToTimestampFormats);
165 	}
166 
167 	/**
168 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlTimestamp(java.lang.String)
169 	 */
170 	public java.sql.Timestamp convertToSqlTimestamp(String timeString)
171 			throws ParseException {
172 		if (!StringUtils.isBlank(timeString)) {
173 			return new java.sql.Timestamp(convertToDateTime(timeString).getTime());
174 		}
175         return null;
176 	}
177 
178 	/**
179 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDate(java.lang.String)
180 	 */
181 	public java.sql.Date convertToSqlDate(String dateString)
182 			throws ParseException {
183 		if (StringUtils.isBlank(dateString)) {
184 			throw new IllegalArgumentException("invalid (blank) dateString");
185 		}
186 		Date date = parseAgainstFormatArray(dateString, stringToDateFormats);
187 		return new java.sql.Date(date.getTime());
188 	}
189 
190     /**
191      * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlTime(java.lang.String)
192      */
193     public java.sql.Time convertToSqlTime(String timeString)
194             throws ParseException {
195         if (StringUtils.isBlank(timeString)) {
196             throw new IllegalArgumentException("invalid (blank) dateString");
197         }
198         Date date = parseAgainstFormatArray(timeString, stringToTimeFormats);
199         return new java.sql.Time(date.getTime());
200     }
201 
202 	protected Date parseAgainstFormatArray(String dateString, String[] formats) throws ParseException {
203 		dateString = dateString.trim();
204 		StringBuffer exceptionMessage = new StringBuffer("Date or date/time string '")
205 				.append(dateString)
206 				.append("' could not be converted using any of the accepted formats: ");
207 		for (String dateFormatString : formats) {
208 			try {
209 				return parse(dateString, dateFormatString);
210 			} catch (ParseException e) {
211 				exceptionMessage.append(dateFormatString).append(
212 						" (error offset=").append(e.getErrorOffset()).append(
213 						"),");
214 			}
215 		}
216 		throw new ParseException(exceptionMessage.toString().substring(0,
217 				exceptionMessage.length() - 1), 0);
218 	}
219 
220 	/**
221 	 * @throws ParseException
222 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDate(java.sql.Timestamp)
223 	 */
224 	public java.sql.Date convertToSqlDate(Timestamp timestamp)
225 			throws ParseException {
226 		return new java.sql.Date(timestamp.getTime());
227 	}
228 
229 	public int dateDiff(Date startDate, Date endDate, boolean inclusive) {
230 		Calendar startDateCalendar = Calendar.getInstance();
231 		startDateCalendar.setTime(startDate);
232 
233 		Calendar endDateCalendar = Calendar.getInstance();
234 		endDateCalendar.setTime(endDate);
235 
236 		int startDateOffset = -(startDateCalendar.get(Calendar.ZONE_OFFSET) + startDateCalendar
237 				.get(Calendar.DST_OFFSET))
238 				/ (60 * 1000);
239 
240 		int endDateOffset = -(endDateCalendar.get(Calendar.ZONE_OFFSET) + endDateCalendar
241 				.get(Calendar.DST_OFFSET))
242 				/ (60 * 1000);
243 
244 		if (startDateOffset > endDateOffset) {
245 			startDateCalendar.add(Calendar.MINUTE, endDateOffset
246 					- startDateOffset);
247 		}
248 
249 		if (inclusive) {
250 			startDateCalendar.add(Calendar.DATE, -1);
251 		}
252 
253 		int dateDiff = Integer.parseInt(DurationFormatUtils.formatDuration(
254 				endDateCalendar.getTimeInMillis()
255 						- startDateCalendar.getTimeInMillis(), "d", true));
256 
257 		return dateDiff;
258 	}
259 
260 	protected Date parse(String dateString, String pattern) throws ParseException {
261 		if (!StringUtils.isBlank(dateString)) {
262 			DateFormat dateFormat = new SimpleDateFormat(pattern);
263 			dateFormat.setLenient(false);
264 			ParsePosition parsePosition = new ParsePosition(0);
265 			Date testDate = dateFormat.parse(dateString, parsePosition);
266 			
267 			// Ensure that the entire date String can be parsed by the current format.
268 			if (testDate == null) {
269 				throw new ParseException("The date that you provided is invalid.",parsePosition.getErrorIndex());
270 			} else if (parsePosition.getIndex() != dateString.length()) {
271 				throw new ParseException("The date that you provided is invalid.",parsePosition.getIndex());
272 			}
273 
274 			// Ensure that the date's year lies between 1000 and 9999, to help prevent database-related date errors.
275 			Calendar testCalendar = Calendar.getInstance();
276 			testCalendar.setLenient(false);
277 			testCalendar.setTime(testDate);
278 			if (testCalendar.get(Calendar.YEAR) < 1000 || testCalendar.get(Calendar.YEAR) > 9999) {
279 				throw new ParseException("The date that you provided is not between the years 1000 and 9999.",-1);
280 			}
281 			
282 			if(testCalendar.get(Calendar.YEAR) == 1970 && !pattern.contains("y".toLowerCase())){		    	
283 		    	Calendar curCalendar = Calendar.getInstance();
284 		    	curCalendar.setTime(new java.util.Date());
285 		    	testCalendar.set(Calendar.YEAR, curCalendar.get(Calendar.YEAR));
286 				testDate = testCalendar.getTime();
287 			}
288 			
289 			return testDate;
290 		}
291 		return null;
292 	}
293 
294 	/**
295 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateStringForFilename(java.util.Date)
296 	 */
297 	public String toDateStringForFilename(Date date) {
298 		SimpleDateFormat dateFormat = new SimpleDateFormat(dateToStringFormatForFileName);
299 		return dateFormat.format(date);
300 	}
301 
302 	/**
303 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateTimeStringForFilename(java.util.Date)
304 	 */
305 	public String toDateTimeStringForFilename(Date date) {
306 		SimpleDateFormat dateFormat = new SimpleDateFormat(timestampToStringFormatForFileName);
307 		return dateFormat.format(date);
308 	}
309 	
310 
311 	/**
312 	 * This overridden method ...
313 	 * 
314 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
315 	 */
316 	@Override
317 	public void afterPropertiesSet() throws Exception {
318 		if (stringToDateFormats == null) {
319             stringToDateFormats = loadAndValidateFormats(CoreConstants.STRING_TO_DATE_FORMATS, STRING_TO_DATE_FORMATS);
320 		}
321 
322         if (stringToTimeFormats == null) {
323             stringToTimeFormats = loadAndValidateFormats(CoreConstants.STRING_TO_TIME_FORMATS, STRING_TO_TIME_FORMATS);
324         }
325 
326 		if (stringToTimestampFormats == null) {
327             stringToTimestampFormats = loadAndValidateFormats(CoreConstants.STRING_TO_TIMESTAMP_FORMATS, STRING_TO_TIMESTAMP_FORMATS);
328 		}
329 
330 		if (dateToStringFormatForUserInterface == null) {
331 			dateToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE, DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE);
332 		}
333 
334         if (timeToStringFormatForUserInterface == null) {
335             timeToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.TIME_TO_STRING_FORMAT_FOR_USER_INTERFACE, TIME_TO_STRING_FORMAT_FOR_USER_INTERFACE);
336         }
337 
338 		if (timestampToStringFormatForUserInterface == null) {
339 			timestampToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE, TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE);
340 		}
341 
342 		if (dateToStringFormatForFileName == null) {
343 			dateToStringFormatForFileName = loadAndValidateFormat(CoreConstants.DATE_TO_STRING_FORMAT_FOR_FILE_NAME, DATE_TO_STRING_FORMAT_FOR_FILE_NAME);
344 		}
345 
346 		if (timestampToStringFormatForFileName == null) {
347 			timestampToStringFormatForFileName = loadAndValidateFormat(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME, TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME);
348 		}
349 	}
350 
351     	/**
352 	 *
353 	 * The dateTime config vars are ';' seperated. This method should probably
354 	 * be on the config interface.
355 	 *
356 	 * @param configValue
357 	 * @return
358 	 */
359 	private List<String> parseConfigValues(String configValue) {
360 	    if (configValue == null || "".equals(configValue)) {
361 	        return Collections.emptyList();
362 	    }
363 	    return Arrays.asList(configValue.split(";"));
364 	}
365 
366     /**
367      * Loads a format string list from config or default
368      * @param property the config property
369      * @param deflt the default value
370      * @return the config or default value
371      */
372     private List<String> loadFormats(String property, String deflt) {
373         return parseConfigValues(loadFormat(property, deflt));
374     }
375 
376     /**
377      * Loads a format string list from the config or default and validates each entry
378      * @param property the config property
379      * @param deflt the default value
380      * @return string array of valid date/time formats
381      */
382     private String[] loadAndValidateFormats(String property, String deflt) {
383         List<String> dateFormatParams = loadFormats(property, deflt);
384 
385         String[] validFormats = new String[dateFormatParams.size()];
386 
387         for (int i = 0; i < dateFormatParams.size(); i++) {
388             String dateFormatParam = dateFormatParams.get(i);
389             if (StringUtils.isBlank(dateFormatParam)) {
390                 throw new IllegalArgumentException("Core/All/" + property + " parameter contains a blank semi-colon delimited substring");
391             }
392             else {
393                 // try to create a new SimpleDateFormat to try to detect illegal patterns
394                 new SimpleDateFormat(dateFormatParam);
395                 validFormats[i] = dateFormatParam;
396             }
397         }
398 
399         return validFormats;
400     }
401 
402     /**
403      * Loads a particular date format from the config, using a default for fallback
404      * @param property the config property
405      * @param deflt the default value
406      * @return the config value or default value
407      */
408     private String loadFormat(String property, String deflt) {
409         String format = ConfigContext.getCurrentContextConfig().getProperty(property);
410         if (StringUtils.isBlank(format)) {
411             format = deflt;
412         }
413         return format;
414     }
415 
416     /**
417      * Loads a particular date format from the config, using a default for fallback,
418      * and validates the format.
419      * @param property the config property
420      * @param deflt the default value
421      * @return the validated config value or default value
422      */
423     private String loadAndValidateFormat(String property, String deflt) {
424         String format = loadFormat(property, deflt);
425         // construct new SDF to make sure it's properly formatted
426         new SimpleDateFormat(format);
427         return format;
428     }
429 }