/**
* Copyright (C) 2011 JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jtalks.jcommune.web.tags;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.contrib.jsptag.FormatTag;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import java.util.concurrent.TimeUnit;
/**
* This tag replaces the existing Joda Time format tag to take
* into account the local time offset set in cookies.
* Although the library has out-of-box timezone support,
* we cannot use it since we don't know the timezone.
* Automatic timezone detection is a complicated task:
* - ip-based detection is inaccurate for some regions
* - bare time offset cannot be properly mapped on the timezone,
* 'cause timezones have different DST settings
* <p/>
* We also could ask the user himself for the timezone, but it's
* as boring, as irritating.
*
* @author Evgeniy Naumenko
*/
public class FormattedDate extends FormatTag {
/**
* Serializable class should define it
*/
private static final long serialVersionUID = 34588L;
public static final String GMT_COOKIE_NAME = "GMT";
/**
* Example: 01 Jan 2011 05:13
* Localized month names are to be inserted by magic
*/
public static final String DEFAULT_DATE_FORMAT_PATTERN = "dd MMM yyyy HH:mm";
private long offset = DEFAULT_OFFSET;
public static final int DEFAULT_OFFSET = 0;
public FormattedDate() {
super();
// override parent default
pattern = DEFAULT_DATE_FORMAT_PATTERN;
}
/**
* {@inheritDoc}
*/
@Override
public void setPageContext(PageContext pageContext) {
Cookie[] cookies = ((HttpServletRequest) pageContext.getRequest()).getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(GMT_COOKIE_NAME)) {
offset = this.convertTimeZoneOffset(cookie.getValue());
break;
}
}
}
super.setPageContext(pageContext);
this.setDateFormattingOptions();
}
/**
* This method adds the corresponding offset to make the date and time
* match local user's timezone. If value is not a DateTime it's
* formatted as is
*
* @param value value to be formatted as date
* @throws JspTagException only from super() call
*/
@Override
public void setValue(Object value) throws JspTagException {
if (value != null) {
DateTime time = (DateTime) value;
DateTimeZone zone = time.getZone();
long utcTime = zone.convertLocalToUTC(time.getMillis(), false);
time = new DateTime(utcTime + offset);
super.setValue(time);
} else {
super.setValue(null);
}
}
/**
* Converts timezone offset representation to millisecond offset.
* If timezone offset representation is incorrect, then GMT
* timezone value is used.
*
* @param jsRepresentation time difference between GMT and
* local time, in minutes. Example: "-120"
* @return signed millisecond timezone offset
*/
private long convertTimeZoneOffset(String jsRepresentation) {
try {
int offsetInMinutes = -Integer.parseInt(jsRepresentation);
return TimeUnit.MINUTES.toMillis(offsetInMinutes);
} catch (NumberFormatException e) {
// someone has passed wrong offset in cookie, use GMT
return DEFAULT_OFFSET;
}
}
/**
* Figures out the the current locale and defines the date formatting pattern.
* Change the pattern here to affect all the dates formatting on pages.
*/
private void setDateFormattingOptions() {
try {
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);
this.setLocale(localeResolver.resolveLocale(request));
} catch (JspTagException e) {
throw new IllegalStateException("Error while rendering the date", e);
}
}
}