blob: c19b49205cf1b3f4da150c04349f5a53fd9329fe [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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.apache.org/licenses/LICENSE-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.apache.myfaces.html5.renderkit.input.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.faces.FacesException;
import org.apache.myfaces.html5.renderkit.util.HTML5;
/**
* Utility class for parsing and formatting date values.
*
* @author Ali Ok
*
*/
public class Html5DateTimeFormatUtils
{
/**
* Parses the value and converts it to date time considering the type.
*
* @throws ParseException
* if the input is not parsable for the given type.
*/
public static Date parseDateTime(String value, String type) throws ParseException
{
if (value == null || value.isEmpty())
return null;
if (HTML5.INPUT_TYPE_DATETIME.equals(type))
{
return _parseGlobalDateTime(value);
}
else if (HTML5.INPUT_TYPE_DATETIME_LOCAL.equals(type))
{
return _parseLocalDateTime(value);
}
else if (HTML5.INPUT_TYPE_DATE.equals(type))
{
return _parseDate(value);
}
else if (HTML5.INPUT_TYPE_TIME.equals(type))
{
return _parseTime(value);
}
else if (HTML5.INPUT_TYPE_MONTH.equals(type))
{
return _parseMonth(value);
}
else if (HTML5.INPUT_TYPE_WEEK.equals(type))
{
return _parseWeek(value);
}
else
{
throw new FacesException("Type " + type + " is not applicable.");
}
}
/**
* Formats datetime for given type.
*
* @throws FacesException
*/
public static String formatDateTime(Date value, String type) throws FacesException
{
if (value == null)
return null;
if (HTML5.INPUT_TYPE_DATETIME.equals(type))
{
return _formatGlobalDateTime(value);
}
else if (HTML5.INPUT_TYPE_DATETIME_LOCAL.equals(type))
{
return _formatLocalDateTime(value);
}
else if (HTML5.INPUT_TYPE_DATE.equals(type))
{
return _formatDate(value);
}
else if (HTML5.INPUT_TYPE_TIME.equals(type))
{
return _formatTime(value);
}
else if (HTML5.INPUT_TYPE_MONTH.equals(type))
{
return _formatMonth(value);
}
else if (HTML5.INPUT_TYPE_WEEK.equals(type))
{
return _formatWeek(value);
}
else
{
throw new FacesException("Type " + type + " is not applicable.");
}
}
private static final String GLOBAL_DATETIME_UTC_PATTERN = "yyyy-MM-dd'T'HH:mm'Z'";
private static final String GLOBAL_DATETIME_UTC_PATTERN_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss'Z'";
private static final String GLOBAL_DATETIME_UTC_PATTERN_WITH_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
// note that HTML5 spec has colon char in the time zone, but it is not applicable for SimpleDateFormat.
// so, if a non UTC string is being parsed, the last colon will be removed.
private static final String GLOBAL_DATETIME_NONUTC_PATTERN = "yyyy-MM-dd'T'HH:mmZZZZ";
private static final String GLOBAL_DATETIME_NONUTC_PATTERN_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ssZZZZ";
private static final String GLOBAL_DATETIME_NONUTC_PATTERN_WITH_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZ";
private static final String LOCAL_DATETIME_PATTERN = "yyyy-MM-dd'T'HH:mm";
private static final String LOCAL_DATETIME_PATTERN_WITH_SECONDS = "yyyy-MM-dd'T'HH:mm:ss";
private static final String LOCAL_DATETIME_PATTERN_WITH_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS";
private static final String DATE_PATTERN = "yyyy-MM-dd";
private static final String TIME_PATTERN = "HH:mm";
private static final String TIME_PATTERN_WITH_SECONDS = "HH:mm:ss";
private static final String TIME_PATTERN_WITH_MILLIS = "HH:mm:ss.SSS";
private static final String MONTH_PATTERN = "yyyy-MM";
private static final String WEEK_PATTERN = "yyyy-'W'ww";
private Html5DateTimeFormatUtils()
{
}
// Samples: 0037-12-13T00:00Z, 1979-10-14T12:00:00.001-04:00, 8592-01-01T02:09+02:09
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-global-date-and-time-string
private static Date _parseGlobalDateTime(String value) throws ParseException
{
if (value == null || value.isEmpty())
return null;
boolean utcDateTime = value.charAt(value.length() - 1) == 'Z';
if (!utcDateTime)
{
/*
* TimeZoned datetime values of Html5 have to define the time zone with colon, but that is not applicable
* with SimpleDateFormat. SimpleDateFormat is unable to use a pattern like "yyyy-MM-dd'T'HH:mm:ZZ:ZZ". It
* doesn't like the last colon, since it breaks the timezone. Since it is unable to parse a value like
* "1992-01-01T02:09+02:00" using "yyyy-MM-dd'T'HH:mm:ZZZZ" pattern, we're removing the last comma of the
* value to convert it to "1992-01-01T02:09+0200"
*/
value = _deleteLastColonOfGlobalNonUTCDateTime(value);
}
boolean hasMillis = value.indexOf('.') != -1;
int lastIndexOfT = value.lastIndexOf('T');
if (lastIndexOfT == -1)
throw new ParseException("Value has no time information, since it does not have 'T' symbol : " + value, 0);
boolean doesntHaveMillisButHasSeconds = _hasSeconds(value.substring(lastIndexOfT));
if (utcDateTime)
{
if (hasMillis)
{
return new SimpleDateFormat(GLOBAL_DATETIME_UTC_PATTERN_WITH_MILLIS).parse(value);
}
else if (doesntHaveMillisButHasSeconds)
{
return new SimpleDateFormat(GLOBAL_DATETIME_UTC_PATTERN_WITH_SECONDS).parse(value);
}
else
{
return new SimpleDateFormat(GLOBAL_DATETIME_UTC_PATTERN).parse(value);
}
}
else
{
if (hasMillis)
{
return new SimpleDateFormat(GLOBAL_DATETIME_NONUTC_PATTERN_WITH_MILLIS).parse(value);
}
else if (doesntHaveMillisButHasSeconds)
{
return new SimpleDateFormat(GLOBAL_DATETIME_NONUTC_PATTERN_WITH_SECONDS).parse(value);
}
else
{
return new SimpleDateFormat(GLOBAL_DATETIME_NONUTC_PATTERN).parse(value);
}
}
}
// Samples: 0037-12-13T00:00, 1979-10-14T12:00:00.001, 8592-01-01T02:09
// Html5 spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
private static Date _parseLocalDateTime(String value) throws ParseException
{
if (value == null || value.isEmpty())
return null;
boolean hasMillis = value.indexOf('.') != -1;
int lastIndexOfT = value.lastIndexOf('T');
if (lastIndexOfT == -1)
throw new ParseException("Value has no time information, since it does not have 'T' symbol : " + value, 0);
boolean doesntHaveMillisButHasSeconds = _hasSeconds(value.substring(lastIndexOfT));
if (hasMillis)
{
return new SimpleDateFormat(LOCAL_DATETIME_PATTERN_WITH_MILLIS).parse(value);
}
else if (doesntHaveMillisButHasSeconds)
{
return new SimpleDateFormat(LOCAL_DATETIME_PATTERN_WITH_SECONDS).parse(value);
}
else
{
return new SimpleDateFormat(LOCAL_DATETIME_PATTERN).parse(value);
}
}
// Samples: 0037-12-13, 1979-10-14, 8592-01-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
private static Date _parseDate(String value) throws ParseException
{
return new SimpleDateFormat(DATE_PATTERN).parse(value);
}
// Samples: 0037-12-13, 1979-10-14, 8592-01-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
private static Date _parseTime(String value) throws ParseException
{
boolean hasMillis = value.indexOf('.') != -1;
boolean hasSeconds = _hasSeconds(value);
if (hasMillis)
return new SimpleDateFormat(TIME_PATTERN_WITH_MILLIS).parse(value);
else if (hasSeconds)
return new SimpleDateFormat(TIME_PATTERN_WITH_SECONDS).parse(value);
else
return new SimpleDateFormat(TIME_PATTERN).parse(value);
}
// Samples: 0037-12, 1979-10, 8592-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-month-string
private static Date _parseMonth(String value) throws ParseException
{
return new SimpleDateFormat(MONTH_PATTERN).parse(value);
}
// Samples: 2005-W52, 1979-W10, 8592-W01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-week-string
private static Date _parseWeek(String value) throws ParseException
{
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(WEEK_PATTERN);
/*
* A week starts on Monday according to Html5 spec. see:
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#concept-week
*/
simpleDateFormat.getCalendar().setFirstDayOfWeek(Calendar.MONDAY);
return simpleDateFormat.parse(value);
}
// Samples: 0037-12-13T00:00Z, 1979-10-14T12:00:00.001-04:00, 8592-01-01T02:09+02:09
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-global-date-and-time-string
private static String _formatGlobalDateTime(Date value)
{
return new SimpleDateFormat(GLOBAL_DATETIME_UTC_PATTERN_WITH_MILLIS).format(value);
}
// Samples: 0037-12-13T00:00, 1979-10-14T12:00:00.001, 8592-01-01T02:09
// Html5 spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-local-date-and-time-string
private static String _formatLocalDateTime(Date value)
{
return new SimpleDateFormat(LOCAL_DATETIME_PATTERN_WITH_MILLIS).format(value);
}
// Samples: 0037-12-13, 1979-10-14, 8592-01-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-date-string
private static String _formatDate(Date value)
{
return new SimpleDateFormat(DATE_PATTERN).format(value);
}
// Samples: 0037-12-13, 1979-10-14, 8592-01-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-time-string
private static String _formatTime(Date value)
{
return new SimpleDateFormat(TIME_PATTERN_WITH_MILLIS).format(value);
}
// Samples: 0037-12, 1979-10, 8592-01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-month-string
private static String _formatMonth(Date value)
{
return new SimpleDateFormat(MONTH_PATTERN).format(value);
}
// Samples: 2005-W52, 1979-W10, 8592-W01
// Html5 Spec:
// http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-week-string
private static String _formatWeek(Date value)
{
/*
* We cannot use the pattern "yyyy-'W'ww" here since it SimpleDateFormat evalates 'ww' w/o considering the year.
* So, for date Fri Dec 31 00:00:00 EET 1999, it returns "1999-W01" instead of "2000-W01".
*
* Tomahawk has implemented its own formatter, but I didn't want to use it, since it is complicated.
* http://myfaces
* .apache.org/tomahawk-project/tomahawk/apidocs/org/apache/myfaces/dateformat/SimpleDateFormatter.html
*
* So, here is the workaround.
*/
Calendar cal = Calendar.getInstance();
cal.setTime(value);
/*
* A week starts on Monday according to Html5 spec. see:
* http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#concept-week
*/
cal.setFirstDayOfWeek(Calendar.MONDAY);
int year = cal.get(Calendar.YEAR);
int week = cal.get(Calendar.WEEK_OF_YEAR);
if (cal.get(Calendar.MONTH) == Calendar.DECEMBER)
{
if (week == 1)
{
year++;
}
}
return String.format("%04d-W%02d", year, week);
}
private static boolean _hasSeconds(String value) throws ParseException
{
// example strings : '22:03', '22:03:05', '22:03:05.455'
int indexOfMinuteBeginning = value.indexOf(':');
// if the second information is present, then lastIndexOfColon must be the beginning position of the seconds(for
// example '1979-10-14T12:00:00-0400').
// else it must be the beginning position of the minutes(for example: '8592-01-01T02:09Z' ).
int lastIndexOfColon = value.lastIndexOf(':');
return lastIndexOfColon > indexOfMinuteBeginning;
}
private static String _deleteLastColonOfGlobalNonUTCDateTime(String value) throws ParseException
{
int indexOfTimeZoneSign = value.lastIndexOf(':');
if (indexOfTimeZoneSign == -1)
throw new ParseException("A valid nonUTC date time must contain time zone information.", 0);
value = value.substring(0, indexOfTimeZoneSign) + value.substring(indexOfTimeZoneSign + 1, value.length());
return value;
}
}