/*
* 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.wicket.datetime;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import org.apache.wicket.Session;
import org.apache.wicket.core.request.ClientInfo;
import org.apache.wicket.protocol.http.request.WebClientInfo;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
import org.apache.wicket.util.string.Strings;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
/**
* Base class for Joda Time based date converters. It contains the logic to parse and format,
* optionally taking the time zone difference between clients and the server into account.
* <p>
* Converters of this class are best suited for per-component use.
* </p>
*
* @author eelcohillenius
*/
public abstract class DateConverter implements IConverter<Date>
{
private static final long serialVersionUID = 1L;
/**
* Whether to apply the time zone difference when interpreting dates.
*/
private final boolean applyTimeZoneDifference;
/**
* Construct. </p> When applyTimeZoneDifference is true, the current time is applied on the
* parsed date, and the date will be corrected for the time zone difference between the server
* and the client. For instance, if I'm in Seattle and the server I'm working on is in
* Amsterdam, the server is 9 hours ahead. So, if I'm inputting say 12/24 at a couple of hours
* before midnight, at the server it is already 12/25. If this boolean is true, it will be
* transformed to 12/25, while the client sees 12/24. </p>
*
* @param applyTimeZoneDifference
* whether to apply the difference in time zones between client and server
*/
public DateConverter(boolean applyTimeZoneDifference)
{
this.applyTimeZoneDifference = applyTimeZoneDifference;
}
/**
* @see org.apache.wicket.util.convert.IConverter#convertToObject(java.lang.String,
* java.util.Locale)
*/
@Override
public Date convertToObject(String value, Locale locale)
{
if (Strings.isEmpty(value))
{
return null;
}
DateTimeFormatter format = getFormat(locale);
if (format == null)
{
throw new IllegalStateException("format must be not null");
}
if (applyTimeZoneDifference)
{
TimeZone zone = getClientTimeZone();
DateTime dateTime;
// set time zone for client
format = format.withZone(getTimeZone());
try
{
// parse date retaining the time of the submission
dateTime = format.parseDateTime(value);
}
catch (RuntimeException e)
{
throw newConversionException(e, locale);
}
// apply the server time zone to the parsed value
if (zone != null)
{
dateTime = dateTime.withZoneRetainFields(DateTimeZone.forTimeZone(zone));
}
return dateTime.toDate();
}
else
{
try
{
DateTime date = format.parseDateTime(value);
return date.toDate();
}
catch (RuntimeException e)
{
throw newConversionException(e, locale);
}
}
}
/**
* Creates a ConversionException and sets additional context information to it.
*
* @param cause
* - {@link RuntimeException} cause
* @param locale
* - {@link Locale} used to set 'format' variable with localized pattern
* @return {@link ConversionException}
*/
private ConversionException newConversionException(RuntimeException cause, Locale locale)
{
return new ConversionException(cause)
.setVariable("format", getDatePattern(locale));
}
/**
* @see org.apache.wicket.util.convert.IConverter#convertToString(java.lang.Object,
* java.util.Locale)
*/
@Override
public String convertToString(Date value, Locale locale)
{
DateTime dt = new DateTime(value.getTime(), getTimeZone());
DateTimeFormatter format = getFormat(locale);
if (applyTimeZoneDifference)
{
TimeZone zone = getClientTimeZone();
if (zone != null)
{
// apply time zone to formatter
format = format.withZone(DateTimeZone.forTimeZone(zone));
}
}
return format.print(dt);
}
/**
* Gets whether to apply the time zone difference when interpreting dates.
*
* </p> When true, the current time is applied on the parsed date, and the date will be
* corrected for the time zone difference between the server and the client. For instance, if
* I'm in Seattle and the server I'm working on is in Amsterdam, the server is 9 hours ahead.
* So, if I'm inputting say 12/24 at a couple of hours before midnight, at the server it is
* already 12/25. If this boolean is true, it will be transformed to 12/25, while the client
* sees 12/24. </p>
*
* @return whether to apply the difference in time zones between client and server
*/
public final boolean getApplyTimeZoneDifference()
{
return applyTimeZoneDifference;
}
/**
* @param locale
* The locale used to convert the value
* @return Gets the pattern that is used for printing and parsing
*/
public abstract String getDatePattern(Locale locale);
/**
* Gets the client's time zone.
*
* @return The client's time zone or null
*/
protected TimeZone getClientTimeZone()
{
ClientInfo info = Session.get().getClientInfo();
if (info instanceof WebClientInfo)
{
return ((WebClientInfo)info).getProperties().getTimeZone();
}
return null;
}
/**
* @param locale
* The locale used to convert the value
*
* @return formatter The formatter for the current conversion
*/
protected abstract DateTimeFormatter getFormat(Locale locale);
/**
* Gets the server time zone. Override this method if you want to fix to a certain time zone,
* regardless of what actual time zone the server is in.
*
* @return The server time zone
*/
protected DateTimeZone getTimeZone()
{
return DateTimeZone.getDefault();
}
}