Index: podcasts-app/src/java/org/sakaiproject/tool/podcasts/podHomeBean.java =================================================================== --- podcasts-app/src/java/org/sakaiproject/tool/podcasts/podHomeBean.java (revision 110471) +++ podcasts-app/src/java/org/sakaiproject/tool/podcasts/podHomeBean.java (working copy) @@ -32,6 +32,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Map.Entry; @@ -75,6 +76,7 @@ import org.sakaiproject.tool.api.ToolSession; import org.sakaiproject.tool.cover.SessionManager; import org.sakaiproject.tool.cover.ToolManager; +import org.sakaiproject.tool.podcasts.util.DateUtil; import org.sakaiproject.util.ResourceLoader; import org.sakaiproject.util.Validator; @@ -95,6 +97,9 @@ private static final String DATE_BY_HAND_FORMAT = "date_by_hand_format"; private static final String INTERNAL_DATE_FORMAT = "internal_date_format"; + /** TODO: This is required until date-picker is internationalized. */ + private static final String FIXED_DATE_PICKER_FORMAT = "MM/dd/yyyy hh:mm:ss a"; + private static final String LAST_MODIFIED_TIME_FORMAT = "hh:mm a z"; private static final String LAST_MODIFIED_DATE_FORMAT = "MM/dd/yyyy"; @@ -1225,7 +1230,14 @@ SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT_STRING, rb.getLocale()); dateFormat.setTimeZone(TimeService.getLocalTimeZone()); - convertedDate = dateFormat.parse(inputDate); + try { + convertedDate = dateFormat.parse(inputDate); + } catch (ParseException e) { + // TODO: This is required until date-picker is internationalized. + dateFormat = new SimpleDateFormat(FORMAT_STRING, Locale.ENGLISH); + dateFormat.setTimeZone(TimeService.getLocalTimeZone()); + convertedDate = dateFormat.parse(inputDate); + } return convertedDate; } @@ -1272,7 +1284,7 @@ try { displayDate = convertDateString(date, - getErrorMessageString(DATE_PICKER_FORMAT)); + FIXED_DATE_PICKER_FORMAT); } catch (ParseException e) { @@ -1485,7 +1497,7 @@ try { // SAK-13493: SimpleDateFormat.parse() did not enforce format specified, so // had to call custom method to check if String was valid - if (isValidDate(selectedPodcast.displayDateRevise)) { + if (DateUtil.isValidDate(selectedPodcast.displayDateRevise, getErrorMessageString(DATE_BY_HAND_FORMAT), rb.getLocale())) { displayDateRevise = convertDateString(selectedPodcast.displayDateRevise, getErrorMessageString(DATE_BY_HAND_FORMAT)); } @@ -1497,7 +1509,7 @@ // must have used date picker, so try again if (isValidDate(selectedPodcast.displayDateRevise)) { displayDateRevise = convertDateString(selectedPodcast.displayDateRevise, - getErrorMessageString(DATE_PICKER_FORMAT)); + FIXED_DATE_PICKER_FORMAT); } else { throw new ParseException("Invalid displayDate entered while revising podcast " + selectedPodcast.filename, 0); @@ -1777,7 +1789,8 @@ else { displayNoDateErrMsg = false; - if (isValidDate(date)) { + if (DateUtil.isValidDate(date, getErrorMessageString(DATE_BY_HAND_FORMAT), rb.getLocale()) + || isValidDate(date)) { displayInvalidDateErrMsg = false; } Index: podcasts-app/src/java/org/sakaiproject/tool/podcasts/util/DateUtil.java =================================================================== --- podcasts-app/src/java/org/sakaiproject/tool/podcasts/util/DateUtil.java (revision 0) +++ podcasts-app/src/java/org/sakaiproject/tool/podcasts/util/DateUtil.java (revision 0) @@ -0,0 +1,416 @@ +/********************************************************************************** + * $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.
+ * Note: 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.
+ * 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 replaced = new ArrayList(); + 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.

+ * 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 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(); + } +}