View Javadoc

1   /*
2    * Copyright 2006-2011 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  
17  package org.kuali.rice.core.impl.datetime;
18  
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.commons.lang.time.DurationFormatUtils;
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.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  	protected String[] stringToDateFormats;
44  	protected String[] stringToTimestampFormats;
45  	protected String dateToStringFormatForUserInterface;
46  	protected String timestampToStringFormatForUserInterface;
47  	protected String dateToStringFormatForFileName;
48  	protected String timestampToStringFormatForFileName;
49  
50  		
51  	/**
52  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateString(java.util.Date)
53  	 */
54  	public String toDateString(Date date) {
55  		return toString(date, dateToStringFormatForUserInterface);
56  	}
57  
58  	/**
59  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateTimeString(java.util.Date)
60  	 */
61  	public String toDateTimeString(Date date) {
62  		return toString(date, timestampToStringFormatForUserInterface);
63  	}
64  
65  	/**
66  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toString(java.util.Date,
67  	 *      java.lang.String)
68  	 */
69  	public String toString(Date date, String pattern) {
70  		DateFormat dateFormat = new SimpleDateFormat(pattern);
71  		dateFormat.setLenient(false);
72  		return dateFormat.format(date);
73  	}
74  
75  	/**
76  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentDate()
77  	 */
78  	public Date getCurrentDate() {
79  		Calendar c = Calendar.getInstance();
80  		c.setTime(new Date());
81  		return c.getTime();
82  	}
83  
84  	/**
85  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentTimestamp()
86  	 */
87  	public Timestamp getCurrentTimestamp() {
88  		return new java.sql.Timestamp(getCurrentDate().getTime());
89  	}
90  
91  	/**
92  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDate()
93  	 */
94  	public java.sql.Date getCurrentSqlDate() {
95  		return new java.sql.Date(getCurrentDate().getTime());
96  	}
97  
98  	/**
99  	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentSqlDateMidnight()
100 	 */
101 	public java.sql.Date getCurrentSqlDateMidnight() {
102 		// simple and not unduely inefficient way to truncate the time component
103 		return java.sql.Date.valueOf(getCurrentSqlDate().toString());
104 	}
105 
106 	/**
107 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCurrentCalendar()
108 	 */
109 	public Calendar getCurrentCalendar() {
110 		return getCalendar(getCurrentDate());
111 	}
112 
113 	/**
114 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#getCalendar
115 	 */
116 	public Calendar getCalendar(Date date) {
117 		if (date == null) {
118 			throw new IllegalArgumentException("invalid (null) date");
119 		}
120 
121 		Calendar currentCalendar = Calendar.getInstance();
122 		currentCalendar.setTime(date);
123 
124 		return currentCalendar;
125 	}
126 
127 	/**
128 	 * Formats strings into dates using the format string in the KR-NS/All/STRING_TO_DATE_FORMATS parameter
129 	 *
130 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDate(java.lang.String)
131 	 */
132 	public Date convertToDate(String dateString) throws ParseException {
133 		return parseAgainstFormatArray(dateString, stringToDateFormats);
134 	}
135 
136 	/**
137 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToDateTime(java.lang.String)
138 	 */
139 	public Date convertToDateTime(String dateTimeString) throws ParseException {
140 		if (StringUtils.isBlank(dateTimeString)) {
141 			throw new IllegalArgumentException("invalid (blank) date/time string");
142 		}
143 		return parseAgainstFormatArray(dateTimeString, stringToTimestampFormats);
144 	}
145 
146 	/**
147 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlTimestamp(java.lang.String)
148 	 */
149 	public java.sql.Timestamp convertToSqlTimestamp(String timeString)
150 			throws ParseException {
151 		if (!StringUtils.isBlank(timeString)) {
152 			return new java.sql.Timestamp(convertToDateTime(timeString).getTime());
153 		}
154         return null;
155 	}
156 
157 	/**
158 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDate(java.lang.String)
159 	 */
160 	public java.sql.Date convertToSqlDate(String dateString)
161 			throws ParseException {
162 		if (StringUtils.isBlank(dateString)) {
163 			throw new IllegalArgumentException("invalid (blank) timeString");
164 		}
165 		Date date = parseAgainstFormatArray(dateString, stringToDateFormats);
166 		return new java.sql.Date(date.getTime());
167 	}
168 
169 	protected Date parseAgainstFormatArray(String dateString, String[] formats) throws ParseException {
170 		dateString = dateString.trim();
171 		StringBuffer exceptionMessage = new StringBuffer("Date or date/time string '")
172 				.append(dateString)
173 				.append("' could not be converted using any of the accepted formats: ");
174 		for (String dateFormatString : formats) {
175 			try {
176 				return parse(dateString, dateFormatString);
177 			} catch (ParseException e) {
178 				exceptionMessage.append(dateFormatString).append(
179 						" (error offset=").append(e.getErrorOffset()).append(
180 						"),");
181 			}
182 		}
183 		throw new ParseException(exceptionMessage.toString().substring(0,
184 				exceptionMessage.length() - 1), 0);
185 	}
186 
187 	/**
188 	 * @throws ParseException
189 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#convertToSqlDate(java.sql.Timestamp)
190 	 */
191 	public java.sql.Date convertToSqlDate(Timestamp timestamp)
192 			throws ParseException {
193 		return new java.sql.Date(timestamp.getTime());
194 	}
195 
196 	public int dateDiff(Date startDate, Date endDate, boolean inclusive) {
197 		Calendar startDateCalendar = Calendar.getInstance();
198 		startDateCalendar.setTime(startDate);
199 
200 		Calendar endDateCalendar = Calendar.getInstance();
201 		endDateCalendar.setTime(endDate);
202 
203 		int startDateOffset = -(startDateCalendar.get(Calendar.ZONE_OFFSET) + startDateCalendar
204 				.get(Calendar.DST_OFFSET))
205 				/ (60 * 1000);
206 
207 		int endDateOffset = -(endDateCalendar.get(Calendar.ZONE_OFFSET) + endDateCalendar
208 				.get(Calendar.DST_OFFSET))
209 				/ (60 * 1000);
210 
211 		if (startDateOffset > endDateOffset) {
212 			startDateCalendar.add(Calendar.MINUTE, endDateOffset
213 					- startDateOffset);
214 		}
215 
216 		if (inclusive) {
217 			startDateCalendar.add(Calendar.DATE, -1);
218 		}
219 
220 		int dateDiff = Integer.parseInt(DurationFormatUtils.formatDuration(
221 				endDateCalendar.getTimeInMillis()
222 						- startDateCalendar.getTimeInMillis(), "d", true));
223 
224 		return dateDiff;
225 	}
226 
227 	protected Date parse(String dateString, String pattern) throws ParseException {
228 		if (!StringUtils.isBlank(dateString)) {
229 			DateFormat dateFormat = new SimpleDateFormat(pattern);
230 			dateFormat.setLenient(false);
231 			ParsePosition parsePosition = new ParsePosition(0);
232 			Date testDate = dateFormat.parse(dateString, parsePosition);
233 			
234 			// Ensure that the entire date String can be parsed by the current format.
235 			if (testDate == null) {
236 				throw new ParseException("The date that you provided is invalid.",parsePosition.getErrorIndex());
237 			} else if (parsePosition.getIndex() != dateString.length()) {
238 				throw new ParseException("The date that you provided is invalid.",parsePosition.getIndex());
239 			}
240 
241 			// Ensure that the date's year lies between 1000 and 9999, to help prevent database-related date errors.
242 			Calendar testCalendar = Calendar.getInstance();
243 			testCalendar.setLenient(false);
244 			testCalendar.setTime(testDate);
245 			if (testCalendar.get(Calendar.YEAR) < 1000 || testCalendar.get(Calendar.YEAR) > 9999) {
246 				throw new ParseException("The date that you provided is not between the years 1000 and 9999.",-1);
247 			}
248 			
249 			if(testCalendar.get(Calendar.YEAR) == 1970 && !pattern.contains("y".toLowerCase())){		    	
250 		    	Calendar curCalendar = Calendar.getInstance();
251 		    	curCalendar.setTime(new java.util.Date());
252 		    	testCalendar.set(Calendar.YEAR, curCalendar.get(Calendar.YEAR));
253 				testDate = testCalendar.getTime();
254 			}
255 			
256 			return testDate;
257 		}
258 		return null;
259 	}
260 
261 	/**
262 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateStringForFilename(java.util.Date)
263 	 */
264 	public String toDateStringForFilename(Date date) {
265 		SimpleDateFormat dateFormat = new SimpleDateFormat(dateToStringFormatForFileName);
266 		return dateFormat.format(date);
267 	}
268 
269 	/**
270 	 * @see org.kuali.rice.core.api.datetime.DateTimeService#toDateTimeStringForFilename(java.util.Date)
271 	 */
272 	public String toDateTimeStringForFilename(Date date) {
273 		SimpleDateFormat dateFormat = new SimpleDateFormat(timestampToStringFormatForFileName);
274 		return dateFormat.format(date);
275 	}
276 	
277 	/**
278 	 * 
279 	 * The dateTime config vars are ';' seperated. This method should probably 
280 	 * be on the config interface.
281 	 * 
282 	 * @param configValue
283 	 * @return
284 	 */
285 	private List<String> parseConfigValues(String configValue) {
286 	    if (configValue == null || "".equals(configValue)) {
287 	        return Collections.emptyList();
288 	    }
289 	    return Arrays.asList(configValue.split(";"));
290 	}
291 	/**
292 	 * This overridden method ...
293 	 * 
294 	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
295 	 */
296 	@Override
297 	public void afterPropertiesSet() throws Exception {
298 		if (stringToDateFormats == null) {
299 			List<String> dateFormatParams = parseConfigValues(ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.STRING_TO_DATE_FORMATS));
300 
301 			stringToDateFormats = new String[dateFormatParams.size()];
302 
303 			for (int i = 0; i < dateFormatParams.size(); i++) {
304 				String dateFormatParam = dateFormatParams.get(i);
305 				if (StringUtils.isBlank(dateFormatParam)) {
306 					throw new IllegalArgumentException("Core/All/STRING_TO_DATE_FORMATS parameter contains a blank semi-colon delimited substring");
307 				}
308 				else {
309 					// try to create a new SimpleDateFormat to try to detect illegal patterns
310 					new SimpleDateFormat(dateFormatParam);
311 					stringToDateFormats[i] = dateFormatParam;
312 				}
313 			}
314 		}
315 
316 		if (stringToTimestampFormats == null) {
317 			List<String> dateFormatParams = parseConfigValues(ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.STRING_TO_TIMESTAMP_FORMATS));			
318 
319 			stringToTimestampFormats = new String[dateFormatParams.size()];
320 
321 			for (int i = 0; i < dateFormatParams.size(); i++) {
322 				String dateFormatParam = dateFormatParams.get(i);
323 				if (StringUtils.isBlank(dateFormatParam)) {
324 					throw new IllegalArgumentException("Core/All/STRING_TO_TIMESTAMP_FORMATS parameter contains a blank semi-colon delimited substring");
325 				}
326 				else {
327 					// try to create a new SimpleDateFormat to try to detect illegal patterns
328 					new SimpleDateFormat(dateFormatParam);
329 					stringToTimestampFormats[i] = dateFormatParam;
330 				}
331 			}
332 		}
333 
334 		if (dateToStringFormatForUserInterface == null) {
335 			dateToStringFormatForUserInterface = ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.DATE_TO_STRING_FORMAT_FOR_USER_INTERFACE);
336 			// construct new SDF to make sure it's properly formatted
337 			new SimpleDateFormat(dateToStringFormatForUserInterface);
338 		}
339 
340 		if (timestampToStringFormatForUserInterface == null) {
341 			timestampToStringFormatForUserInterface = ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_USER_INTERFACE);			
342 			// construct new SDF to make sure it's properly formatted
343 			new SimpleDateFormat(timestampToStringFormatForUserInterface);
344 		}
345 
346 		if (dateToStringFormatForFileName == null) {
347 			dateToStringFormatForFileName = ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.DATE_TO_STRING_FORMAT_FOR_FILE_NAME);
348 			
349 			// construct new SDF to make sure it's properly formatted
350 			new SimpleDateFormat(dateToStringFormatForFileName);
351 		}
352 
353 		if (timestampToStringFormatForFileName == null) {
354 			timestampToStringFormatForFileName = ConfigContext.getCurrentContextConfig().getProperty(CoreConstants.TIMESTAMP_TO_STRING_FORMAT_FOR_FILE_NAME);
355 			
356 			// construct new SDF to make sure it's properly formatted
357 			new SimpleDateFormat(timestampToStringFormatForFileName);
358 		}		
359 	}
360 }