diff --git a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java index 7c41f82a00..6803a29341 100644 --- a/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java +++ b/spring-context/src/main/java/org/springframework/context/i18n/LocaleContextHolder.java @@ -35,6 +35,7 @@ import org.springframework.core.NamedThreadLocal; * MessageSourceAccessor automatically use that Locale. * * @author Juergen Hoeller + * @author Nicholas Williams * @since 1.2 * @see LocaleContext * @see org.springframework.context.support.MessageSourceAccessor @@ -62,7 +63,8 @@ public abstract class LocaleContextHolder { * not exposing it as inheritable for child threads. *

The given LocaleContext may be a {@link TimeZoneAwareLocaleContext}, * containing a locale with associated time zone information. - * @param localeContext the current LocaleContext + * @param localeContext the current LocaleContext, + * or {@code null} to reset the thread-bound context * @see SimpleLocaleContext * @see SimpleTimeZoneAwareLocaleContext */ @@ -110,28 +112,43 @@ public abstract class LocaleContextHolder { } /** - * Associate the given Locale with the current thread. + * Associate the given Locale with the current thread, + * preserving any TimeZone that may have been set already. *

Will implicitly create a LocaleContext for the given Locale, * not exposing it as inheritable for child threads. * @param locale the current Locale, or {@code null} to reset - * the thread-bound context - * @see SimpleLocaleContext#SimpleLocaleContext(java.util.Locale) + * the locale part of thread-bound context + * @see #setTimeZone(TimeZone) + * @see SimpleLocaleContext#SimpleLocaleContext(Locale) */ public static void setLocale(Locale locale) { setLocale(locale, false); } /** - * Associate the given Locale with the current thread. + * Associate the given Locale with the current thread, + * preserving any TimeZone that may have been set already. *

Will implicitly create a LocaleContext for the given Locale. * @param locale the current Locale, or {@code null} to reset - * the thread-bound context + * the locale part of thread-bound context * @param inheritable whether to expose the LocaleContext as inheritable * for child threads (using an {@link InheritableThreadLocal}) - * @see SimpleLocaleContext#SimpleLocaleContext(java.util.Locale) + * @see #setTimeZone(TimeZone, boolean) + * @see SimpleLocaleContext#SimpleLocaleContext(Locale) */ public static void setLocale(Locale locale, boolean inheritable) { - LocaleContext localeContext = (locale != null ? new SimpleLocaleContext(locale) : null); + LocaleContext localeContext = getLocaleContext(); + TimeZone timeZone = (localeContext instanceof TimeZoneAwareLocaleContext ? + ((TimeZoneAwareLocaleContext) localeContext).getTimeZone() : null); + if (timeZone != null) { + localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone); + } + else if (locale != null) { + localeContext = new SimpleLocaleContext(locale); + } + else { + localeContext = null; + } setLocaleContext(localeContext, inheritable); } @@ -160,6 +177,46 @@ public abstract class LocaleContextHolder { return Locale.getDefault(); } + /** + * Associate the given TimeZone with the current thread, + * preserving any Locale that may have been set already. + *

Will implicitly create a LocaleContext for the given Locale, + * not exposing it as inheritable for child threads. + * @param timeZone the current TimeZone, or {@code null} to reset + * the time zone part of the thread-bound context + * @see #setLocale(Locale) + * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone) + */ + public static void setTimeZone(TimeZone timeZone) { + setTimeZone(timeZone, false); + } + + /** + * Associate the given TimeZone with the current thread, + * preserving any Locale that may have been set already. + *

Will implicitly create a LocaleContext for the given Locale. + * @param timeZone the current TimeZone, or {@code null} to reset + * the time zone part of the thread-bound context + * @param inheritable whether to expose the LocaleContext as inheritable + * for child threads (using an {@link InheritableThreadLocal}) + * @see #setLocale(Locale, boolean) + * @see SimpleTimeZoneAwareLocaleContext#SimpleTimeZoneAwareLocaleContext(Locale, TimeZone) + */ + public static void setTimeZone(TimeZone timeZone, boolean inheritable) { + LocaleContext localeContext = getLocaleContext(); + Locale locale = (localeContext != null ? localeContext.getLocale() : null); + if (timeZone != null) { + localeContext = new SimpleTimeZoneAwareLocaleContext(locale, timeZone); + } + else if (locale != null) { + localeContext = new SimpleLocaleContext(locale); + } + else { + localeContext = null; + } + setLocaleContext(localeContext, inheritable); + } + /** * Return the TimeZone associated with the current thread, if any, * or the system default TimeZone else. This is effectively a diff --git a/spring-context/src/test/java/org/springframework/context/i18n/LocaleContextHolderTests.java b/spring-context/src/test/java/org/springframework/context/i18n/LocaleContextHolderTests.java new file mode 100644 index 0000000000..9a1a6578c4 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/i18n/LocaleContextHolderTests.java @@ -0,0 +1,155 @@ +/* + * 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; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Juergen Hoeller + */ +public class LocaleContextHolderTests { + + @Test + public void testSetLocaleContext() { + LocaleContext lc = new SimpleLocaleContext(Locale.GERMAN); + LocaleContextHolder.setLocaleContext(lc); + assertSame(lc, LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + + lc = new SimpleLocaleContext(Locale.GERMANY); + LocaleContextHolder.setLocaleContext(lc); + assertSame(lc, LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + + LocaleContextHolder.resetLocaleContext(); + assertNull(LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + } + + @Test + public void testSetTimeZoneAwareLocaleContext() { + LocaleContext lc = new SimpleTimeZoneAwareLocaleContext(Locale.GERMANY, TimeZone.getTimeZone("GMT+1")); + LocaleContextHolder.setLocaleContext(lc); + assertSame(lc, LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), LocaleContextHolder.getTimeZone()); + + LocaleContextHolder.resetLocaleContext(); + assertNull(LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + } + + @Test + public void testSetLocale() { + LocaleContextHolder.setLocale(Locale.GERMAN); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertFalse(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocaleContext().getLocale()); + + LocaleContextHolder.setLocale(Locale.GERMANY); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertFalse(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocaleContext().getLocale()); + + LocaleContextHolder.setLocale(null); + assertNull(LocaleContextHolder.getLocaleContext()); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + } + + @Test + public void testSetTimeZone() { + LocaleContextHolder.setTimeZone(TimeZone.getTimeZone("GMT+1")); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertNull(LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setTimeZone(TimeZone.getTimeZone("GMT+2")); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertNull(LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setTimeZone(null); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertNull(LocaleContextHolder.getLocaleContext()); + } + + @Test + public void testSetLocaleAndSetTimeZoneMixed() { + LocaleContextHolder.setLocale(Locale.GERMANY); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertFalse(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocaleContext().getLocale()); + + LocaleContextHolder.setTimeZone(TimeZone.getTimeZone("GMT+1")); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMANY, LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setLocale(Locale.GERMAN); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+1"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setTimeZone(null); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertFalse(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocaleContext().getLocale()); + + LocaleContextHolder.setTimeZone(TimeZone.getTimeZone("GMT+2")); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertEquals(Locale.GERMAN, LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setLocale(null); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), LocaleContextHolder.getTimeZone()); + assertTrue(LocaleContextHolder.getLocaleContext() instanceof TimeZoneAwareLocaleContext); + assertNull(LocaleContextHolder.getLocaleContext().getLocale()); + assertEquals(TimeZone.getTimeZone("GMT+2"), ((TimeZoneAwareLocaleContext) LocaleContextHolder.getLocaleContext()).getTimeZone()); + + LocaleContextHolder.setTimeZone(null); + assertEquals(Locale.getDefault(), LocaleContextHolder.getLocale()); + assertEquals(TimeZone.getDefault(), LocaleContextHolder.getTimeZone()); + assertNull(LocaleContextHolder.getLocaleContext()); + } + +}