View Javadoc
1   /**
2    * Copyright 2005-2015 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.joda.time.DateTime;
21  import org.kuali.rice.core.api.datetime.DateTimeService;
22  import org.kuali.rice.core.api.CoreConstants;
23  import org.kuali.rice.core.api.config.property.ConfigContext;
24  import org.springframework.beans.factory.InitializingBean;
25  
26  import java.sql.Time;
27  import java.sql.Timestamp;
28  import java.text.DateFormat;
29  import java.text.ParseException;
30  import java.text.ParsePosition;
31  import java.text.SimpleDateFormat;
32  import java.util.Arrays;
33  import java.util.Calendar;
34  import java.util.Collections;
35  import java.util.Date;
36  import java.util.List;
37  
38  /**
39   * This class is the service implementation for a DateTime structure. This is
40   * the default, Kuali delivered implementation.
41   */
42  //@Transactional
43  public class DateTimeServiceImpl implements DateTimeService, InitializingBean {
44  
45  	protected String[] stringToDateFormats;
46      protected String[] stringToTimeFormats;
47  	protected String[] stringToTimestampFormats;
48  	protected String dateToStringFormatForUserInterface;
49      protected String timeToStringFormatForUserInterface;
50  	protected String timestampToStringFormatForUserInterface;
51  	protected String dateToStringFormatForFileName;
52  	protected String timestampToStringFormatForFileName;
53  
54  		
55  	/**
56  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateString(java.util.Date)
57  	 */
58  	public String toDateString(Date date) {
59  		return toString(date, dateToStringFormatForUserInterface);
60  	}
61  
62      /**
63       * @see org.kuali.rice.core.api.datetime.DateTimeService#toTimeString(java.sql.Time)
64       */
65      public String toTimeString(Time time) {
66          return toString(time, timeToStringFormatForUserInterface);
67      }
68  
69  	/**
70  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateTimeString(java.util.Date)
71  	 */
72  	public String toDateTimeString(Date date) {
73  		return toString(date, timestampToStringFormatForUserInterface);
74  	}
75  
76  	/**
77  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toString(java.util.Date,
78  	 *      java.lang.String)
79  	 */
80  	public String toString(Date date, String pattern) {
81  		DateFormat dateFormat = new SimpleDateFormat(pattern);
82  		dateFormat.setLenient(false);
83  		return dateFormat.format(date);
84  	}
85  
86  	/**
87  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentDate()
88  	 */
89  	public Date getCurrentDate() {
90  		Calendar c = Calendar.getInstance();
91  		c.setTime(new Date());
92  		return c.getTime();
93  	}
94  
95  	/**
96  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentTimestamp()
97  	 */
98  	public Timestamp getCurrentTimestamp() {
99  		return new java.sql.Timestamp(getCurrentDate().getTime());
100 	}
101 
102 	/**
103 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDate()
104 	 */
105 	public java.sql.Date getCurrentSqlDate() {
106 		return new java.sql.Date(getCurrentDate().getTime());
107 	}
108 
109 	/**
110 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDateMidnight()
111 	 */
112 	public java.sql.Date getCurrentSqlDateMidnight() {
113 		// simple and not unduely inefficient way to truncate the time component
114 		return java.sql.Date.valueOf(getCurrentSqlDate().toString());
115 	}
116 
117 	/**
118 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentCalendar()
119 	 */
120 	public Calendar getCurrentCalendar() {
121 		return getCalendar(getCurrentDate());
122 	}
123 
124 	/**
125 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCalendar
126 	 */
127 	public Calendar getCalendar(Date date) {
128 		if (date == null) {
129 			throw new IllegalArgumentException("invalid (null) date");
130 		}
131 
132 		Calendar currentCalendar = Calendar.getInstance();
133 		currentCalendar.setTime(date);
134 
135 		return currentCalendar;
136 	}
137 
138 	/**
139 	 * Formats strings into dates using the format string in the KR-NS/All/STRING_TO_DATE_FORMATS parameter
140 	 *
141 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDate(java.lang.String)
142 	 */
143 	public Date convertToDate(String dateString) throws ParseException {
144 		return parseAgainstFormatArray(dateString, stringToDateFormats);
145 	}
146 
147 	/**
148 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDateTime(java.lang.String)
149 	 */
150 	public Date convertToDateTime(String dateTimeString) throws ParseException {
151 		if (StringUtils.isBlank(dateTimeString)) {
152 			throw new IllegalArgumentException("invalid (blank) date/time string");
153 		}
154 		return parseAgainstFormatArray(dateTimeString, stringToTimestampFormats);
155 	}
156 
157 	/**
158 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlTimestamp(java.lang.String)
159 	 */
160 	public java.sql.Timestamp convertToSqlTimestamp(String timeString)
161 			throws ParseException {
162 		if (!StringUtils.isBlank(timeString)) {
163 			return new java.sql.Timestamp(convertToDateTime(timeString).getTime());
164 		}
165         return null;
166 	}
167 
168 	/**
169 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDate(java.lang.String)
170 	 */
171 	public java.sql.Date convertToSqlDate(String dateString)
172 			throws ParseException {
173 		if (StringUtils.isBlank(dateString)) {
174 			throw new IllegalArgumentException("invalid (blank) dateString");
175 		}
176 		Date date = parseAgainstFormatArray(dateString, stringToDateFormats);
177 		return new java.sql.Date(date.getTime());
178 	}
179 
180     /**
181      * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDateUpperBound(java.lang.String)
182      */
183     public java.sql.Date convertToSqlDateUpperBound(String dateString)
184             throws ParseException {
185         java.sql.Date date = convertToSqlDate(dateString);
186         DateTime dateUpperBound = new DateTime(date).plusDays(1);
187         return new java.sql.Date(dateUpperBound.getMillis());
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, CoreConstants.STRING_TO_DATE_FORMATS_DEFAULT);
320 		}
321 
322         if (stringToTimeFormats == null) {
323             stringToTimeFormats = loadAndValidateFormats(CoreConstants.STRING_TO_TIME_FORMATS, CoreConstants.STRING_TO_TIME_FORMATS_DEFAULT);
324         }
325 
326 		if (stringToTimestampFormats == null) {
327             stringToTimestampFormats = loadAndValidateFormats(CoreConstants.STRING_TO_TIMESTAMP_FORMATS, CoreConstants.STRING_TO_TIMESTAMP_FORMATS_DEFAULT);
328 		}
329 
330 		if (dateToStringFormatForUserInterface == null) {
331 			dateToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE, CoreConstants.DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE_DEFAULT);
332 		}
333 
334         if (timeToStringFormatForUserInterface == null) {
335             timeToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.TIME_TO_STRING_FORMAT_FOR_USER_INTERFACE, CoreConstants.TIME_TO_STRING_FORMAT_FOR_USER_INTERFACE_DEFAULT);
336         }
337 
338 		if (timestampToStringFormatForUserInterface == null) {
339 			timestampToStringFormatForUserInterface = loadAndValidateFormat(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE, CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE_DEFAULT);
340 		}
341 
342 		if (dateToStringFormatForFileName == null) {
343 			dateToStringFormatForFileName = loadAndValidateFormat(CoreConstants.DATE_TO_STRING_FORMAT_FOR_FILE_NAME, CoreConstants.DATE_TO_STRING_FORMAT_FOR_FILE_NAME_DEFAULT);
344 		}
345 
346 		if (timestampToStringFormatForFileName == null) {
347 			timestampToStringFormatForFileName = loadAndValidateFormat(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME, CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME_DEFAULT);
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 }