Comprehensive update to the framework's TimeZone handling, including a new TimeZoneAwareLocaleContext and a LocaleContextResolver for Spring MVC

A few noteworthy minor changes: LocaleContext.getLocale() may return null in special cases (not by default), which our own accessing classes are able to handle now. If there is a non-null TimeZone user setting, we're exposing it to all collaborating libraries, in particular to JSTL, Velocity and JasperReports. Our JSR-310 and Joda-Time support falls back to checking the general LocaleContext TimeZone now, adapting it to their time zone types, if no more specific setting has been provided. Our DefaultConversionService has TimeZone<->ZoneId converters registered. And finally, we're using a custom parseTimeZoneString method now that doesn't accept the TimeZone.getTimeZone(String) GMT fallback for an invalid time zone id anymore.

Issue: SPR-1528
This commit is contained in:
Juergen Hoeller
2013-10-04 23:14:08 +02:00
parent 52cca48f40
commit 4574528a27
35 changed files with 1570 additions and 270 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2005 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,14 +26,15 @@ import java.util.Locale;
*
* @author Juergen Hoeller
* @since 1.2
* @see LocaleContextHolder
* @see java.util.Locale
* @see LocaleContextHolder#getLocale()
* @see TimeZoneAwareLocaleContext
*/
public interface LocaleContext {
/**
* Return the current Locale, which can be fixed or determined dynamically,
* depending on the implementation strategy.
* @return the current Locale, or {@code null} if no specific Locale associated
*/
Locale getLocale();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
package org.springframework.context.i18n;
import java.util.Locale;
import java.util.TimeZone;
import org.springframework.core.NamedInheritableThreadLocal;
import org.springframework.core.NamedThreadLocal;
@@ -59,7 +60,11 @@ public abstract class LocaleContextHolder {
/**
* Associate the given LocaleContext with the current thread,
* <i>not</i> exposing it as inheritable for child threads.
* <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
* containing a locale with associated time zone information.
* @param localeContext the current LocaleContext
* @see SimpleLocaleContext
* @see SimpleTimeZoneAwareLocaleContext
*/
public static void setLocaleContext(LocaleContext localeContext) {
setLocaleContext(localeContext, false);
@@ -67,10 +72,14 @@ public abstract class LocaleContextHolder {
/**
* Associate the given LocaleContext with the current thread.
* <p>The given LocaleContext may be a {@link TimeZoneAwareLocaleContext},
* containing a locale with associated time zone information.
* @param localeContext the current LocaleContext,
* or {@code null} to reset the thread-bound context
* @param inheritable whether to expose the LocaleContext as inheritable
* for child threads (using an {@link InheritableThreadLocal})
* @see SimpleLocaleContext
* @see SimpleTimeZoneAwareLocaleContext
*/
public static void setLocaleContext(LocaleContext localeContext, boolean inheritable) {
if (localeContext == null) {
@@ -128,7 +137,13 @@ public abstract class LocaleContextHolder {
/**
* Return the Locale associated with the current thread, if any,
* or the system default Locale else.
* or the system default Locale else. This is effectively a
* replacement for {@link java.util.Locale#getDefault()},
* able to optionally respect a user-level Locale setting.
* <p>Note: This method has a fallback to the system default Locale.
* If you'd like to check for the raw LocaleContext content
* (which may indicate no specific locale through {@code null}, use
* {@link #getLocaleContext()} and call {@link LocaleContext#getLocale()}
* @return the current Locale, or the system default Locale if no
* specific Locale has been associated with the current thread
* @see LocaleContext#getLocale()
@@ -136,7 +151,39 @@ public abstract class LocaleContextHolder {
*/
public static Locale getLocale() {
LocaleContext localeContext = getLocaleContext();
return (localeContext != null ? localeContext.getLocale() : Locale.getDefault());
if (localeContext != null) {
Locale locale = localeContext.getLocale();
if (locale != null) {
return locale;
}
}
return Locale.getDefault();
}
/**
* Return the TimeZone associated with the current thread, if any,
* or the system default TimeZone else. This is effectively a
* replacement for {@link java.util.TimeZone#getDefault()},
* able to optionally respect a user-level TimeZone setting.
* <p>Note: This method has a fallback to the system default Locale.
* If you'd like to check for the raw LocaleContext content
* (which may indicate no specific time zone through {@code null}, use
* {@link #getLocaleContext()} and call {@link TimeZoneAwareLocaleContext#getTimeZone()}
* after downcasting to {@link TimeZoneAwareLocaleContext}.
* @return the current TimeZone, or the system default TimeZone if no
* specific TimeZone has been associated with the current thread
* @see TimeZoneAwareLocaleContext#getTimeZone()
* @see java.util.TimeZone#getDefault()
*/
public static TimeZone getTimeZone() {
LocaleContext localeContext = getLocaleContext();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
if (timeZone != null) {
return timeZone;
}
}
return TimeZone.getDefault();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,14 +18,15 @@ package org.springframework.context.i18n;
import java.util.Locale;
import org.springframework.util.Assert;
/**
* Simple implementation of the {@link LocaleContext} interface,
* always returning a specified {@code Locale}.
*
* @author Juergen Hoeller
* @since 1.2
* @see LocaleContextHolder#setLocaleContext
* @see LocaleContextHolder#getLocale()
* @see SimpleTimeZoneAwareLocaleContext
*/
public class SimpleLocaleContext implements LocaleContext {
@@ -34,11 +35,10 @@ public class SimpleLocaleContext implements LocaleContext {
/**
* Create a new SimpleLocaleContext that exposes the specified Locale.
* Every {@code getLocale()} will return this Locale.
* Every {@link #getLocale()} call will return this Locale.
* @param locale the Locale to expose
*/
public SimpleLocaleContext(Locale locale) {
Assert.notNull(locale, "Locale must not be null");
this.locale = locale;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed 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.springframework.context.i18n;
import java.util.Locale;
import java.util.TimeZone;
/**
* Simple implementation of the {@link TimeZoneAwareLocaleContext} interface,
* always returning a specified {@code Locale} and {@code TimeZone}.
*
* <p>Note: Prefer the use of {@link SimpleLocaleContext} when only setting
* a Locale but no TimeZone.
*
* @author Juergen Hoeller
* @since 4.0
* @see LocaleContextHolder#setLocaleContext
* @see LocaleContextHolder#getTimeZone()
*/
public class SimpleTimeZoneAwareLocaleContext extends SimpleLocaleContext implements TimeZoneAwareLocaleContext {
private final TimeZone timeZone;
/**
* Create a new SimpleTimeZoneAwareLocaleContext that exposes the specified
* Locale and TimeZone. Every {@link #getLocale()} call will return the given
* Locale, and every {@link #getTimeZone()} call will return the given TimeZone.
* @param locale the Locale to expose
* @param timeZone the TimeZone to expose
*/
public SimpleTimeZoneAwareLocaleContext(Locale locale, TimeZone timeZone) {
super(locale);
this.timeZone = timeZone;
}
public TimeZone getTimeZone() {
return this.timeZone;
}
@Override
public String toString() {
return super.toString() + " " + this.timeZone.toString();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2002-2013 the original author or authors.
*
* Licensed 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.springframework.context.i18n;
import java.util.TimeZone;
/**
* Extension of {@link LocaleContext}, adding awareness of the current time zone.
*
* <p>Having this variant of LocaleContext set to {@link LocaleContextHolder} means
* that some TimeZone-aware infrastructure has been configured, even if it may not
* be able to produce a non-null TimeZone at the moment.
*
* @author Juergen Hoeller
* @since 4.0
* @see LocaleContextHolder#getTimeZone()
*/
public interface TimeZoneAwareLocaleContext extends LocaleContext {
/**
* Return the current TimeZone, which can be fixed or determined dynamically,
* depending on the implementation strategy.
* @return the current TimeZone, or {@code null} if no specific TimeZone associated
*/
TimeZone getTimeZone();
}

View File

@@ -16,10 +16,16 @@
package org.springframework.format.datetime.joda;
import java.util.TimeZone;
import org.joda.time.Chronology;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
/**
* A context that holds user-specific Joda-Time settings such as the user's
* Chronology (calendar system) and time zone.
@@ -53,6 +59,11 @@ public class JodaTimeContext {
/**
* Set the user's time zone.
* <p>Alternatively, set a {@link TimeZoneAwareLocaleContext} on
* {@link LocaleContextHolder}. This context class will fall back to
* checking the locale context if no setting has been provided here.
* @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
* @see org.springframework.context.i18n.LocaleContextHolder#setLocaleContext
*/
public void setTimeZone(DateTimeZone timeZone) {
this.timeZone = timeZone;
@@ -80,6 +91,15 @@ public class JodaTimeContext {
if (this.timeZone != null) {
formatter = formatter.withZone(this.timeZone);
}
else {
LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
if (timeZone != null) {
formatter = formatter.withZone(DateTimeZone.forTimeZone(timeZone));
}
}
}
return formatter;
}

View File

@@ -19,6 +19,11 @@ package org.springframework.format.datetime.standard;
import java.time.ZoneId;
import java.time.chrono.Chronology;
import java.time.format.DateTimeFormatter;
import java.util.TimeZone;
import org.springframework.context.i18n.LocaleContext;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.i18n.TimeZoneAwareLocaleContext;
/**
* A context that holds user-specific <code>java.time</code> (JSR-310) settings
@@ -52,6 +57,11 @@ public class DateTimeContext {
/**
* Set the user's time zone.
* <p>Alternatively, set a {@link TimeZoneAwareLocaleContext} on
* {@link LocaleContextHolder}. This context class will fall back to
* checking the locale context if no setting has been provided here.
* @see org.springframework.context.i18n.LocaleContextHolder#getTimeZone()
* @see org.springframework.context.i18n.LocaleContextHolder#setLocaleContext
*/
public void setTimeZone(ZoneId timeZone) {
this.timeZone = timeZone;
@@ -79,6 +89,15 @@ public class DateTimeContext {
if (this.timeZone != null) {
formatter = formatter.withZone(this.timeZone);
}
else {
LocaleContext localeContext = LocaleContextHolder.getLocaleContext();
if (localeContext instanceof TimeZoneAwareLocaleContext) {
TimeZone timeZone = ((TimeZoneAwareLocaleContext) localeContext).getTimeZone();
if (timeZone != null) {
formatter = formatter.withZone(timeZone.toZoneId());
}
}
}
return formatter;
}

View File

@@ -42,6 +42,7 @@ import org.springframework.scheduling.support.ScheduledMethodRunnable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.ReflectionUtils.MethodCallback;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
@@ -192,11 +193,7 @@ public class ScheduledAnnotationBeanPostProcessor
}
TimeZone timeZone;
if (!"".equals(zone)) {
timeZone = TimeZone.getTimeZone(zone);
// Check for that silly TimeZone fallback...
if ("GMT".equals(timeZone.getID()) && !zone.startsWith("GMT")) {
throw new IllegalArgumentException("Invalid time zone id '" + zone + "'");
}
timeZone = StringUtils.parseTimeZoneString(zone);
}
else {
timeZone = TimeZone.getDefault();