/**********************************************************************************
* $URL$
* $Id$
**********************************************************************************
*
* Copyright (c) 2008 The Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.tool.podcasts.util;
import java.text.DateFormatSymbols;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Performs date validation respecting i18n.<br>
* <b>Note:</b> This class does not support "hi_IN", "ja_JP_JP" and "th_TH" locales.
*/
public final class DateUtil {
private DateUtil() {
}
/**
* Performs date validation checking like Feb 30, etc.
*
* @param date
* The candidate String date.
* @param format
* The given date-time format.
* @param locale
* The given locale.
* @return TRUE - Conforms to a valid input date format string.<br>
* FALSE - Does not conform.
*/
public static boolean isValidDate(final String date, final String format, final Locale locale) {
if (date == null || format == null) {
return false;
}
List<String> replaced = new ArrayList<String>();
String regex = createRegexFromDateFormat(format.trim(), replaced);
DateFormatSymbols dateFormatSymbols;
if (locale == null) {
dateFormatSymbols = DateFormatSymbols.getInstance();
} else {
dateFormatSymbols = DateFormatSymbols.getInstance(locale);
}
// Check date
int year = 0;
int month = 1;
int day = 1;
Matcher matcher = Pattern.compile(regex).matcher(Matcher.quoteReplacement(date.trim()));
if (!matcher.find()) {
// Invalid date
return false;
}
for (int group = 1; group <= matcher.groupCount(); group++) {
boolean isValid;
String[] strs;
switch (replaced.get(group - 1).charAt(0)) {
case 'y': // Year
case 'Y': // Week year
year = Integer.parseInt(matcher.group(group));
if (year < 1582) {
// Allow only 4 digits and Gregorian
return false;
}
break;
case 'M': // Month in year
try {
month = Integer.parseInt(matcher.group(group));
} catch (NumberFormatException e) {
// Maybe Jan, January, ...
isValid = false;
for (int i = 0; i < 4 && !isValid; i++) {
switch (i) {
case 0:
default:
strs = DateFormatSymbols.getInstance(Locale.ENGLISH).getMonths();
break;
case 1:
strs = DateFormatSymbols.getInstance(Locale.ENGLISH).getShortMonths();
break;
case 2:
strs = dateFormatSymbols.getMonths();
break;
case 3:
strs = dateFormatSymbols.getShortMonths();
break;
}
for (int num = Calendar.JANUARY; num <= Calendar.DECEMBER; num++) {
if (strs[num].equalsIgnoreCase(matcher.group(group))) {
isValid = true;
month = num + 1;
break;
}
}
}
if (!isValid) {
return false;
}
}
if (month < 1 || month > 12) {
return false;
}
break;
case 'w': // Week in year
int num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 53) {
return false;
}
break;
case 'W': // Week in month
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 6) {
return false;
}
break;
case 'D': // Day in year
num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 366) {
return false;
}
break;
case 'd': // Day in month
day = Integer.parseInt(matcher.group(group));
if (day < 1 || day > 31) {
return false;
}
break;
case 'F': // Day of week in month
num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 5) {
return false;
}
break;
case 'E': // Day name in week
isValid = false;
for (int i = 0; i < 4 && !isValid; i++) {
switch (i) {
case 0:
default:
strs = DateFormatSymbols.getInstance(Locale.ENGLISH).getWeekdays();
break;
case 1:
strs = DateFormatSymbols.getInstance(Locale.ENGLISH).getShortWeekdays();
break;
case 2:
strs = dateFormatSymbols.getWeekdays();
break;
case 3:
strs = dateFormatSymbols.getShortWeekdays();
break;
}
for (num = Calendar.SUNDAY; num <= Calendar.SATURDAY; num++) {
if (strs[num].equalsIgnoreCase(matcher.group(group))) {
isValid = true;
break;
}
}
}
if (!isValid) {
return false;
}
break;
case 'u': // Day number of week (1 = Monday, ..., 7 = Sunday)
num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 7) {
return false;
}
break;
case 'a': // Am/pm marker
isValid = false;
for (int i = 0; i < 2 && !isValid; i++) {
switch (i) {
case 0:
default:
strs = DateFormatSymbols.getInstance(Locale.ENGLISH).getAmPmStrings();
break;
case 1:
strs = dateFormatSymbols.getAmPmStrings();
break;
}
for (num = Calendar.AM; num <= Calendar.PM; num++) {
if (strs[num].equalsIgnoreCase(matcher.group(group))) {
isValid = true;
break;
}
}
}
if (!isValid) {
return false;
}
break;
case 'H': // Hour in day (0-23)
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 23) {
return false;
}
break;
case 'k': // Hour in day (1-24)
num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 24) {
return false;
}
break;
case 'K': // Hour in am/pm (0-11)
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 11) {
return false;
}
break;
case 'h': // Hour in am/pm (1-12)
num = Integer.parseInt(matcher.group(group));
if (num < 1 || num > 12) {
return false;
}
break;
case 'm': // Minute in hour
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 59) {
return false;
}
break;
case 's': // Second in minute
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 60) {
// Include leap sec
return false;
}
break;
case 'S': // Millisecond
num = Integer.parseInt(matcher.group(group));
if (num < 0 || num > 999) {
return false;
}
break;
default:
break;
}
}
return checkDate(day, month, year);
}
/**
* Validate whether the date input is valid.
*/
private static boolean checkDate(final int day, final int month, final int year) {
// Is date valid for month?
if (month == 2) {
// Check for leap year
if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) {
// leap year
if (day > 29) {
return false;
}
} else {
// normal year
if (day > 28) {
return false;
}
}
} else if ((month == 4 || month == 6 || month == 9 || month == 11) && day > 30) {
return false;
}
return true;
}
/**
* Create a regular expression replacing the given date-time format.
* The created regular expression does not allow digit number over.<br><br>
* e.g., "dd/MM/yyyy hh:mm:ss a" -> "^(-?\d{1,2})/(-?\d{1,2})/(-?\d{1,4}) (-?\d{1,2}):(-?\d{1,2}):(-?\d{1,2}) (.+)$"
*
* @param format
* The date-time format to be replaced.
* @param replaced
* The list to keep replaced date-time patterns.
* @return The regular expression.
*/
private static String createRegexFromDateFormat(final String format, final List<String> replaced) {
if (format == null) {
return null;
} else if (replaced != null) {
replaced.clear();
}
// Create regular expression to match date-time patterns
StringBuffer sb = new StringBuffer("'{1,2}");
char[] patternChars = "GyMdkHmsSEDFwWahKzZYuX".toCharArray();
for (char patternChar : patternChars) {
sb.append('|');
sb.append(patternChar);
sb.append('+');
}
// Replace date-time pattern with regular expression in format
Matcher matcher = Pattern.compile(sb.toString()).matcher('^' + Matcher.quoteReplacement(format) + '$');
boolean quoted = false;
StringBuffer replacement = new StringBuffer();
sb = new StringBuffer();
while (matcher.find()) {
char patternChar = matcher.group().charAt(0);
if (patternChar == '\'') {
if (matcher.group().length() == 1) {
// Replace ' with empty string
matcher.appendReplacement(sb, "");
quoted ^= true;
} else if (matcher.group().length() == 2) {
// Replace '' with '
matcher.appendReplacement(sb, "'");
}
continue;
}
if (quoted) {
continue;
}
replacement.setLength(0);
replacement.append('(');
switch (patternChar) {
case 'y': // Year
case 'Y': // Week year
replacement.append("-?\\\\d{1,");
if (matcher.group().length() <= 4) {
// Replace y, yy, yyy, yyyy with \d{1,4}
replacement.append(4);
} else {
// Replace yyyyy, yyyyyy, ... with \d{1,x}
replacement.append(matcher.group().length());
}
replacement.append('}');
break;
case 'M': // Month in year
if (matcher.group().length() <= 2) {
// Replace M or MM with \d{1,2}
replacement.append("-?\\\\d{1,2}");
} else {
// Replace MMM, MMMM, ... with .+
replacement.append(".+");
}
break;
case 'D': // Day in year
case 'S': // Millisecond
replacement.append("-?\\\\d{1,");
if (matcher.group().length() <= 3) {
// Replace D or DD or DDD with \d{1,3}
replacement.append(3);
} else {
// Replace DDDD, DDDDD, ... with \d{1,x}
replacement.append(matcher.group().length());
}
replacement.append('}');
break;
case 'w': // Week in year
case 'd': // Day in month
case 'H': // Hour in day (0-23)
case 'k': // Hour in day (1-24)
case 'K': // Hour in am/pm (0-11)
case 'h': // Hour in am/pm (1-12)
case 'm': // Minute in hour
case 's': // Second in minute
replacement.append("-?\\\\d{1,");
if (matcher.group().length() <= 2) {
// Replace d or dd with \d{1,2}
replacement.append(2);
} else {
// Replace ddd, dddd, ... with \d{1,x}
replacement.append(matcher.group().length());
}
replacement.append('}');
break;
case 'W': // Week in month
case 'F': // Day of week in month
case 'u': // Day number of week (1 = Monday, ..., 7 = Sunday)
replacement.append("-?\\\\d");
if (matcher.group().length() > 1) {
replacement.append("{1," + matcher.group().length() + "}");
}
break;
default:
replacement.append(".+");
break;
}
replacement.append(')');
matcher.appendReplacement(sb, replacement.toString());
if (replaced != null) {
// Keep replaced date-time pattern
replaced.add(matcher.group());
}
}
return matcher.appendTail(sb).toString();
}
}