001/* 002 * Copyright 2002-2017 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.format.datetime.joda; 018 019import java.util.Calendar; 020import java.util.Date; 021import java.util.EnumMap; 022import java.util.Map; 023 024import org.joda.time.DateTime; 025import org.joda.time.Duration; 026import org.joda.time.LocalDate; 027import org.joda.time.LocalDateTime; 028import org.joda.time.LocalTime; 029import org.joda.time.MonthDay; 030import org.joda.time.Period; 031import org.joda.time.ReadableInstant; 032import org.joda.time.YearMonth; 033import org.joda.time.format.DateTimeFormat; 034import org.joda.time.format.DateTimeFormatter; 035 036import org.springframework.format.FormatterRegistrar; 037import org.springframework.format.FormatterRegistry; 038import org.springframework.format.Parser; 039import org.springframework.format.Printer; 040import org.springframework.format.annotation.DateTimeFormat.ISO; 041import org.springframework.util.ClassUtils; 042 043/** 044 * Configures Joda-Time's formatting system for use with Spring. 045 * 046 * <p><b>NOTE:</b> Spring's Joda-Time support requires Joda-Time 2.x, as of Spring 4.0. 047 * 048 * @author Keith Donald 049 * @author Juergen Hoeller 050 * @author Phillip Webb 051 * @since 3.1 052 * @see #setDateStyle 053 * @see #setTimeStyle 054 * @see #setDateTimeStyle 055 * @see #setUseIsoFormat 056 * @see FormatterRegistrar#registerFormatters 057 * @see org.springframework.format.datetime.DateFormatterRegistrar 058 * @see DateTimeFormatterFactoryBean 059 */ 060public class JodaTimeFormatterRegistrar implements FormatterRegistrar { 061 062 private enum Type {DATE, TIME, DATE_TIME} 063 064 065 /** 066 * Strictly speaking, this should not be necessary since we formally require JodaTime 2.x. 067 * However, since Joda-Time formatters are being registered automatically, we defensively 068 * adapt to Joda-Time 1.x when encountered on the classpath. To be removed in Spring 5.0. 069 */ 070 private static final boolean jodaTime2Available = ClassUtils.isPresent( 071 "org.joda.time.YearMonth", JodaTimeFormatterRegistrar.class.getClassLoader()); 072 073 /** 074 * User defined formatters. 075 */ 076 private final Map<Type, DateTimeFormatter> formatters = new EnumMap<Type, DateTimeFormatter>(Type.class); 077 078 /** 079 * Factories used when specific formatters have not been specified. 080 */ 081 private final Map<Type, DateTimeFormatterFactory> factories; 082 083 084 public JodaTimeFormatterRegistrar() { 085 this.factories = new EnumMap<Type, DateTimeFormatterFactory>(Type.class); 086 for (Type type : Type.values()) { 087 this.factories.put(type, new DateTimeFormatterFactory()); 088 } 089 } 090 091 092 /** 093 * Set whether standard ISO formatting should be applied to all date/time types. 094 * Default is "false" (no). 095 * <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle" 096 * properties are effectively ignored. 097 */ 098 public void setUseIsoFormat(boolean useIsoFormat) { 099 this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : null); 100 this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : null); 101 this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : null); 102 } 103 104 /** 105 * Set the default format style of Joda {@link LocalDate} objects. 106 * Default is {@link DateTimeFormat#shortDate()}. 107 */ 108 public void setDateStyle(String dateStyle) { 109 this.factories.get(Type.DATE).setStyle(dateStyle + "-"); 110 } 111 112 /** 113 * Set the default format style of Joda {@link LocalTime} objects. 114 * Default is {@link DateTimeFormat#shortTime()}. 115 */ 116 public void setTimeStyle(String timeStyle) { 117 this.factories.get(Type.TIME).setStyle("-" + timeStyle); 118 } 119 120 /** 121 * Set the default format style of Joda {@link LocalDateTime} and {@link DateTime} objects, 122 * as well as JDK {@link Date} and {@link Calendar} objects. 123 * Default is {@link DateTimeFormat#shortDateTime()}. 124 */ 125 public void setDateTimeStyle(String dateTimeStyle) { 126 this.factories.get(Type.DATE_TIME).setStyle(dateTimeStyle); 127 } 128 129 /** 130 * Set the formatter that will be used for objects representing date values. 131 * <p>This formatter will be used for the {@link LocalDate} type. When specified 132 * the {@link #setDateStyle(String) dateStyle} and 133 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 134 * @param formatter the formatter to use 135 * @since 3.2 136 * @see #setTimeFormatter 137 * @see #setDateTimeFormatter 138 */ 139 public void setDateFormatter(DateTimeFormatter formatter) { 140 this.formatters.put(Type.DATE, formatter); 141 } 142 143 /** 144 * Set the formatter that will be used for objects representing time values. 145 * <p>This formatter will be used for the {@link LocalTime} type. When specified 146 * the {@link #setTimeStyle(String) timeStyle} and 147 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 148 * @param formatter the formatter to use 149 * @since 3.2 150 * @see #setDateFormatter 151 * @see #setDateTimeFormatter 152 */ 153 public void setTimeFormatter(DateTimeFormatter formatter) { 154 this.formatters.put(Type.TIME, formatter); 155 } 156 157 /** 158 * Set the formatter that will be used for objects representing date and time values. 159 * <p>This formatter will be used for {@link LocalDateTime}, {@link ReadableInstant}, 160 * {@link Date} and {@link Calendar} types. When specified 161 * the {@link #setDateTimeStyle(String) dateTimeStyle} and 162 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 163 * @param formatter the formatter to use 164 * @since 3.2 165 * @see #setDateFormatter 166 * @see #setTimeFormatter 167 */ 168 public void setDateTimeFormatter(DateTimeFormatter formatter) { 169 this.formatters.put(Type.DATE_TIME, formatter); 170 } 171 172 173 @Override 174 public void registerFormatters(FormatterRegistry registry) { 175 JodaTimeConverters.registerConverters(registry); 176 177 DateTimeFormatter dateFormatter = getFormatter(Type.DATE); 178 DateTimeFormatter timeFormatter = getFormatter(Type.TIME); 179 DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME); 180 181 addFormatterForFields(registry, 182 new ReadablePartialPrinter(dateFormatter), 183 new LocalDateParser(dateFormatter), 184 LocalDate.class); 185 186 addFormatterForFields(registry, 187 new ReadablePartialPrinter(timeFormatter), 188 new LocalTimeParser(timeFormatter), 189 LocalTime.class); 190 191 addFormatterForFields(registry, 192 new ReadablePartialPrinter(dateTimeFormatter), 193 new LocalDateTimeParser(dateTimeFormatter), 194 LocalDateTime.class); 195 196 addFormatterForFields(registry, 197 new ReadableInstantPrinter(dateTimeFormatter), 198 new DateTimeParser(dateTimeFormatter), 199 ReadableInstant.class); 200 201 // In order to retain backwards compatibility we only register Date/Calendar 202 // types when a user defined formatter is specified (see SPR-10105) 203 if (this.formatters.containsKey(Type.DATE_TIME)) { 204 addFormatterForFields(registry, 205 new ReadableInstantPrinter(dateTimeFormatter), 206 new DateTimeParser(dateTimeFormatter), 207 Date.class, Calendar.class); 208 } 209 210 registry.addFormatterForFieldType(Period.class, new PeriodFormatter()); 211 registry.addFormatterForFieldType(Duration.class, new DurationFormatter()); 212 if (jodaTime2Available) { 213 JodaTime2Delegate.registerAdditionalFormatters(registry); 214 } 215 216 registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory()); 217 } 218 219 private DateTimeFormatter getFormatter(Type type) { 220 DateTimeFormatter formatter = this.formatters.get(type); 221 if (formatter != null) { 222 return formatter; 223 } 224 DateTimeFormatter fallbackFormatter = getFallbackFormatter(type); 225 return this.factories.get(type).createDateTimeFormatter(fallbackFormatter); 226 } 227 228 private DateTimeFormatter getFallbackFormatter(Type type) { 229 switch (type) { 230 case DATE: return DateTimeFormat.shortDate(); 231 case TIME: return DateTimeFormat.shortTime(); 232 default: return DateTimeFormat.shortDateTime(); 233 } 234 } 235 236 private void addFormatterForFields(FormatterRegistry registry, Printer<?> printer, 237 Parser<?> parser, Class<?>... fieldTypes) { 238 239 for (Class<?> fieldType : fieldTypes) { 240 registry.addFormatterForFieldType(fieldType, printer, parser); 241 } 242 } 243 244 245 /** 246 * Inner class to avoid a hard dependency on Joda-Time 2.x. 247 */ 248 private static class JodaTime2Delegate { 249 250 public static void registerAdditionalFormatters(FormatterRegistry registry) { 251 registry.addFormatterForFieldType(YearMonth.class, new YearMonthFormatter()); 252 registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter()); 253 } 254 } 255 256}