/*******************************************************************************
* Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* emil.crumhorn@gmail.com - initial API and implementation
*******************************************************************************/
package org.eclipse.nebula.widgets.calendarcombo;
import java.text.DateFormat;
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
public class DateHelper {
private static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static long daysBetween(Calendar start, Calendar end, Locale locale) {
// create copies
GregorianCalendar startDate = new GregorianCalendar(locale);
GregorianCalendar endDate = new GregorianCalendar(locale);
// switch calendars to pure Julian mode for correct day-between
// calculation, from the Java API:
// - To obtain a pure Julian calendar, set the change date to
// Date(Long.MAX_VALUE).
startDate.setGregorianChange(new Date(Long.MAX_VALUE));
endDate.setGregorianChange(new Date(Long.MAX_VALUE));
// set them
startDate.setTime(start.getTime());
endDate.setTime(end.getTime());
// force times to be exactly the same
startDate.set(Calendar.HOUR_OF_DAY, 12);
endDate.set(Calendar.HOUR_OF_DAY, 12);
startDate.set(Calendar.MINUTE, 0);
endDate.set(Calendar.MINUTE, 0);
startDate.set(Calendar.SECOND, 0);
endDate.set(Calendar.SECOND, 0);
startDate.set(Calendar.MILLISECOND, 0);
endDate.set(Calendar.MILLISECOND, 0);
// now we should be able to do a "safe" millisecond/day caluclation to
// get the number of days
long endMilli = endDate.getTimeInMillis();
long startMilli = startDate.getTimeInMillis();
// calculate # of days, finally
long diff = (endMilli - startMilli) / MILLISECONDS_IN_DAY;
return diff;
}
public static long daysBetween(Date start, Date end, Locale locale) {
Calendar dEnd = Calendar.getInstance(locale);
Calendar dStart = Calendar.getInstance(locale);
dEnd.setTime(end);
dStart.setTime(start);
return daysBetween(dStart, dEnd, locale);
}
public static boolean isToday(Date date, Locale locale) {
Calendar cal = Calendar.getInstance(locale);
cal.setTime(date);
return isToday(cal, locale);
}
public static boolean isToday(Calendar cal, Locale locale) {
Calendar today = Calendar.getInstance(locale);
if (today.get(Calendar.YEAR) == cal.get(Calendar.YEAR)) {
if (today.get(Calendar.DAY_OF_YEAR) == cal.get(Calendar.DAY_OF_YEAR)) {
return true;
}
}
return false;
}
public static String getDate(Calendar cal, String dateFormat) {
Calendar toUse = (Calendar) cal.clone();
toUse.add(Calendar.MONTH, -1);
SimpleDateFormat df = new SimpleDateFormat(dateFormat);
df.setLenient(true);
return df.format(cal.getTime());
}
public static boolean sameDate(Date date1, Date date2, Locale locale) {
Calendar cal1 = Calendar.getInstance(locale);
Calendar cal2 = Calendar.getInstance(locale);
cal1.setTime(date1);
cal2.setTime(date2);
return sameDate(cal1, cal2);
}
public static boolean sameDate(Calendar cal1, Calendar cal2) {
if (cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)) {
if (cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR)) {
return true;
}
}
return false;
}
public static Date getDate(String str, String dateFormat) throws Exception {
SimpleDateFormat df = new SimpleDateFormat(dateFormat);
df.setLenient(false);
return df.parse(str);
}
public static Calendar getDate(String str, String dateFormat, Locale locale) throws Exception {
SimpleDateFormat df = new SimpleDateFormat(dateFormat, locale);
Date d = df.parse(str);
Calendar cal = Calendar.getInstance(locale);
cal.setTime(d);
return cal;
}
public static Calendar parseDate(String str, Locale locale) throws Exception {
Date foo = DateFormat.getDateInstance(DateFormat.SHORT, locale).parse(str);
Calendar cal = Calendar.getInstance(locale);
cal.setTime(foo);
return cal;
}
private static Calendar calendarize(Date date, Locale locale) {
Calendar cal = Calendar.getInstance(locale);
cal.setTime(date);
return cal;
}
public static Calendar parse(final String comboText, final Locale locale, final String dateFormat, final char[] acceptedSeparatorChars, final List additionalDateFormats)
throws CalendarDateParseException, Exception {
boolean isNumeric = comboText.replaceAll("[^0-9]", "").length() == comboText.length();
if (isNumeric) {
return numericParse(comboText, locale, false);
}
else {
return slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
}
//return null;
//}
/*if (comboText.length() == 0) {
return null;
}
try {
// start with a hard parse as date format parses can return
// false positives on various locales.
// false positives may sound good, but they're bad, as they can
// cause a year to end up 2000 years off...
try {
mStartDate = DateHelper.parseDateHard(comboText, locale);
return mStartDate;
}
catch (Exception err) {
}
// try true date format parse
mStartDate = DateHelper.getDate(comboText, dateFormat, locale);
return mStartDate;
// System.err.println("Got here 2 - Settings parse " +
// mStartDate.getTime());
}
catch (Exception err) {
// try the locale (this is error prone due to how java parses
// dates)
try {
mStartDate = DateHelper.parseDate(comboText, locale);
return mStartDate;
// System.err.println("Got here 3 - Locale parse " +
// mStartDate.getTime());
}
catch (Exception deeper) {
try {
mStartDate = DateHelper.slashParse(comboText, dateFormat, acceptedSeparatorChars, locale);
return mStartDate;
}
catch (Exception ohwell) {
// System.err.println("Failed parse, trying additional formats");
if (additionalDateFormats != null) {
try {
for (int i = 0; i < additionalDateFormats.size(); i++) {
try {
String format = (String) additionalDateFormats.get(i);
Date date = DateHelper.getDate(comboText, format);
return mStartDate;
}
catch (Exception failed) {
// keep trying
}
}
}
catch (Exception err2) {
// don't care
}
}
}
return mStartDate;
}
}
}
catch (Exception err) {
err.printStackTrace();
}*/
//return null;
}
/**
* This method will try its best to parse a date based on the current
* Locale.
*
* @param str
* String to parse
* @param locale
* Current Locale
* @return Calendar or null on failure
* @throws CalendarDateParseException
* If date could not be parsed
* @throws Exception
* on any unforseen issues or bad parse errors
*/
public static Calendar parseDateHard(final String str, final Locale locale) throws CalendarDateParseException, Exception {
try {
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
String actualLocalePattern = ((SimpleDateFormat) df).toPattern();
try {
Calendar foo = slashParse(str, actualLocalePattern, new char[] {
'/', '-', '.'
}, locale);
return foo;
}
catch (Exception err) {
}
try {
Date foo = df.parse(str);
return calendarize(foo, locale);
}
catch (Exception err) {
// some locales already have 4 y's
if (actualLocalePattern.indexOf("yyyy") == -1)
actualLocalePattern = actualLocalePattern.replaceAll("yy", "yyyy");
try {
Date foo = df.parse(str);
return calendarize(foo, locale);
}
catch (Exception err2) {
// fall through
}
}
}
catch (Exception err) {
// fall through
}
try {
Date foo = DateFormat.getDateInstance().parse(str);
return calendarize(foo, locale);
}
catch (Exception err) {
try {
Integer.parseInt(str);
try {
DateFormat df = DateFormat.getDateInstance();
df.setLenient(false);
Date foo = df.parse(str);
return calendarize(foo, locale);
}
catch (Exception err2) {
return numericParse(str, locale, true);
}
}
catch (Exception failedInt) {
// clear the bad chars and try again
StringBuffer buf = new StringBuffer();
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) >= '0' && str.charAt(i) <= '9')
buf.append(str.charAt(i));
}
String fixed = buf.toString();
try {
Integer.parseInt(fixed);
return numericParse(fixed, locale, true);
}
catch (Exception forgetit) {
throw new CalendarDateParseException(forgetit, CalendarDateParseException.TYPE_EXCEPTION);
}
}
}
}
// date formats with a single M d y etc are highly problematic (US dates),
// so replace them with their proper format so that we can parse
// or else we'll be parsing years like "03" as "0003"
public static String dateFormatFix(String str) {
if (str.indexOf("M") != -1 && str.indexOf("MM") == -1 && str.indexOf("MMM") == -1) {
str = str.replaceAll("M", "MM");
}
if (str.indexOf("d") != -1 && str.indexOf("dd") == -1 && str.indexOf("ddd") == -1) {
str = str.replaceAll("d", "dd");
}
if (str.indexOf("y") != -1 && str.indexOf("yy") == -1 && str.indexOf("yyy") == -1) {
str = str.replaceAll("y", "yy");
}
return str;
}
public static Calendar numericParse(String str, Locale locale, boolean doUsEuParse) throws Exception {
// we always start with the locale and try to parse that numerically, if
// that fails we'll try another few possibilities before we give up
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, locale);
String actualLocalePattern = ((SimpleDateFormat) df).toPattern();
// remove all non letters which will leave us with a clean date pattern
actualLocalePattern = dateFormatFix(actualLocalePattern.replaceAll("[^a-zA-Z]", ""));
actualLocalePattern = actualLocalePattern.replaceAll("G", "");
// parse it into long / short versions where the year is 4 or 2 digits
String actualLocaleLong = "";
String actualLocaleShort = "";
if (actualLocalePattern.indexOf("yyyy") == -1) {
actualLocaleShort = actualLocalePattern;
actualLocaleLong = actualLocalePattern.replaceAll("yy", "yyyy");
}
else {
actualLocaleLong = actualLocalePattern;
actualLocaleShort = actualLocalePattern.replaceAll("yyyy", "yy");
}
Date parsed = null;
// now parse it according to locale if we can
try {
if (str.length() == 6) {
SimpleDateFormat sdf = new SimpleDateFormat(actualLocaleShort);
parsed = sdf.parse(str);
}
else if (str.length() == 5) {
// if a user enters a 5-digit date we assume they were clever enough to get the day and month in 2 digit formats, and the last being the year in a 1 digit format.
// so we need to 2-digitize the year and re-parse. As any 1900-year would be 2 digit except for early 1900's (we don't care) we assume it's 2000+. As I said, if we
// get here the user is really pushing their luck on parsing anyway and we're doing them a favor to begin with
StringBuffer buf = new StringBuffer();
buf.append(str.substring(0, 4));
buf.append("0");
buf.append(str.substring(4, 5));
return numericParse(buf.toString(), locale, doUsEuParse);
}
else {
SimpleDateFormat sdf = new SimpleDateFormat(actualLocaleLong);
parsed = sdf.parse(str);
}
if (parsed != null)
return calendarize(parsed, locale);
}
catch (ParseException pe) {
// ignore, try more
}
if (doUsEuParse) {
// try a couple of pre-defined formats, it's highly likely it's
// either
// US or European
String usFormat6 = "MMddyy";
String usFormat8 = "MMddyyyy";
String euFormat6 = "ddMMyy";
String euFormat8 = "ddMMyyyy";
if (locale.equals(Locale.US)) {
if (str.length() == 6) {
SimpleDateFormat sdf = new SimpleDateFormat(usFormat6);
parsed = sdf.parse(str);
}
else {
SimpleDateFormat sdf = new SimpleDateFormat(usFormat8);
parsed = sdf.parse(str);
}
}
else {
if (str.length() == 6) {
SimpleDateFormat sdf = new SimpleDateFormat(euFormat6);
parsed = sdf.parse(str);
}
else {
SimpleDateFormat sdf = new SimpleDateFormat(euFormat8);
parsed = sdf.parse(str);
}
}
}
if (parsed != null) {
return calendarize(parsed, locale);
}
return null;
}
public static int getCalendarTypeForString(String oneChar) {
int calType = -1;
switch (oneChar.charAt(0)) {
case 'G':
calType = Calendar.ERA;
break;
case 'y':
calType = Calendar.YEAR;
break;
case 'M':
calType = Calendar.MONTH;
break;
case 'd':
calType = Calendar.DAY_OF_MONTH;
break;
case 'E':
calType = Calendar.DAY_OF_WEEK;
break;
case 'D':
calType = Calendar.DAY_OF_YEAR;
break;
case 'F':
calType = Calendar.DATE;
break;
case 'h':
calType = Calendar.HOUR;
break;
case 'm':
calType = Calendar.MINUTE;
break;
case 's':
calType = Calendar.SECOND;
break;
case 'S':
calType = Calendar.MILLISECOND;
break;
case 'w':
calType = Calendar.WEEK_OF_YEAR;
break;
case 'W':
calType = Calendar.WEEK_OF_MONTH;
break;
case 'a':
calType = Calendar.AM_PM;
break;
case 'k':
calType = Calendar.HOUR_OF_DAY;
break;
case 'K':
// ?
break;
case 'z':
calType = Calendar.ZONE_OFFSET;
break;
}
return calType;
}
/**
* This method assumes the dateFormat has a separator char in it, and that
* we can use that to determine what the user entered by using that
* separator to split up the user entered date, and then do some logic on
* it. This is by no means a foolproof method and should not be relied upon
* returning 100% correct dates all the time.
*
* @param str
* String to parse
* @param dateFormat
* DateFormat to use
* @param separators
* Separator chars that can be encountered
* @param locale
* Locale
* @return Calendar
* @throws CalendarDateParseException
* If date could not be parsed
* @throws Exception
* If any step of the parsing failed
*/
public static Calendar slashParse(final String str, final String dateFormat, final char[] separators, final Locale locale) throws CalendarDateParseException, Exception {
int start = -1;
String splitter = null;
String dateFormatToUse = dateFormat;
for (int i = 0; i < separators.length; i++) {
start = str.indexOf(separators[i]);
if (start != -1) {
splitter = String.valueOf(separators[i]);
break;
}
}
if (start == -1)
throw new CalendarDateParseException("Failed to find splitter char", CalendarDateParseException.TYPE_NO_SLPITTER_CHAR);
// replace dateFormat until we have same splitter
for (int i = 0; i < separators.length; i++) {
if (String.valueOf(separators[i]).equals(splitter))
continue;
dateFormatToUse = dateFormatToUse.replaceAll("\\" + String.valueOf(separators[i]), splitter);
}
Calendar toReturn = Calendar.getInstance(locale);
StringTokenizer st = new StringTokenizer(str, splitter);
StringTokenizer st2 = new StringTokenizer(dateFormatToUse, splitter);
if (st.countTokens() != st2.countTokens())
throw new CalendarDateParseException("Date format does not match date string in terms of splitter character numbers", CalendarDateParseException.TYPE_INSUFFICIENT_SPLITTERS);
// variables we'll be extracting
int monthToSet = -1;
int dayToSet = -1;
int yearToSet = -1;
// reset, skipping month this time
st = new StringTokenizer(str, splitter);
st2 = new StringTokenizer(dateFormatToUse, splitter);
while (st.hasMoreTokens()) {
String dateValue = st.nextToken();
String dateType = st2.nextToken();
dateValue = dateValue.replaceAll(" ", "");
dateType = dateType.replaceAll(" ", "");
int calType = getCalendarTypeForString(dateType);
// we already did month
if (calType == Calendar.MONTH) {
monthToSet = Integer.parseInt(dateValue);
continue;
}
if (calType == Calendar.YEAR) {
yearToSet = Integer.parseInt(dateValue);
continue;
}
if (calType == Calendar.DATE) {
dayToSet = Integer.parseInt(dateValue);
continue;
}
toReturn.set(calType, Integer.parseInt(dateValue));
}
// set all date parameters at the same time, or else we'll get month-skipping due to setting a value later (such as a date that is too high
// for the current month). (-1 for month as Calendar class is month-zero-based).
if (monthToSet != -1 && dayToSet != -1 && yearToSet != -1) {
toReturn.set(yearToSet, monthToSet-1, dayToSet);
}
else {
// set what we know
if (yearToSet != -1) {
toReturn.set(Calendar.YEAR, yearToSet);
}
if (monthToSet != -1) {
toReturn.set(Calendar.MONTH, monthToSet-1);
}
if (dayToSet != -1) {
toReturn.set(Calendar.DATE, dayToSet);
}
}
if (toReturn.get(Calendar.YEAR) < 100)
toReturn.set(Calendar.YEAR, toReturn.get(Calendar.YEAR) + 2000);
toReturn.set(Calendar.HOUR_OF_DAY, 0);
toReturn.set(Calendar.MINUTE, 0);
toReturn.set(Calendar.SECOND, 0);
toReturn.set(Calendar.MILLISECOND, 0);
return toReturn;
}
/**
* Parses a string (representing a month) and returns it's corresponding
* value as a Calendar month. This is used to parse MMM month dates
*
* @param monthStr
* String to parse
* @param locale
* Locale to use
* @return Month value or -1 if not found
*/
private static int getMonthForString(String monthStr, Locale locale) {
DateFormatSymbols dfs = new DateFormatSymbols(locale);
String[] months = dfs.getMonths();
for (int i = 0; i < months.length; i++) {
if (months[i].toLowerCase(locale).startsWith(monthStr.toLowerCase(locale))) {
return i + 1;
}
}
return -1;
}
/*public Calendar smartParse(String dateStr, Locale locale) {
// Samples:
// 080101 20080101
// 08/01/01 2008/01/01
return null;
}*/
}