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; 041 042/** 043 * Configures Joda-Time's formatting system for use with Spring. 044 * 045 * <p><b>NOTE:</b> Spring's Joda-Time support requires Joda-Time 2.x, as of Spring 4.0. 046 * 047 * @author Keith Donald 048 * @author Juergen Hoeller 049 * @author Phillip Webb 050 * @since 3.1 051 * @see #setDateStyle 052 * @see #setTimeStyle 053 * @see #setDateTimeStyle 054 * @see #setUseIsoFormat 055 * @see FormatterRegistrar#registerFormatters 056 * @see org.springframework.format.datetime.DateFormatterRegistrar 057 * @see DateTimeFormatterFactoryBean 058 */ 059public class JodaTimeFormatterRegistrar implements FormatterRegistrar { 060 061 private enum Type {DATE, TIME, DATE_TIME} 062 063 064 /** 065 * User defined formatters. 066 */ 067 private final Map<Type, DateTimeFormatter> formatters = new EnumMap<>(Type.class); 068 069 /** 070 * Factories used when specific formatters have not been specified. 071 */ 072 private final Map<Type, DateTimeFormatterFactory> factories; 073 074 075 public JodaTimeFormatterRegistrar() { 076 this.factories = new EnumMap<>(Type.class); 077 for (Type type : Type.values()) { 078 this.factories.put(type, new DateTimeFormatterFactory()); 079 } 080 } 081 082 083 /** 084 * Set whether standard ISO formatting should be applied to all date/time types. 085 * Default is "false" (no). 086 * <p>If set to "true", the "dateStyle", "timeStyle" and "dateTimeStyle" 087 * properties are effectively ignored. 088 */ 089 public void setUseIsoFormat(boolean useIsoFormat) { 090 this.factories.get(Type.DATE).setIso(useIsoFormat ? ISO.DATE : ISO.NONE); 091 this.factories.get(Type.TIME).setIso(useIsoFormat ? ISO.TIME : ISO.NONE); 092 this.factories.get(Type.DATE_TIME).setIso(useIsoFormat ? ISO.DATE_TIME : ISO.NONE); 093 } 094 095 /** 096 * Set the default format style of Joda {@link LocalDate} objects. 097 * Default is {@link DateTimeFormat#shortDate()}. 098 */ 099 public void setDateStyle(String dateStyle) { 100 this.factories.get(Type.DATE).setStyle(dateStyle + "-"); 101 } 102 103 /** 104 * Set the default format style of Joda {@link LocalTime} objects. 105 * Default is {@link DateTimeFormat#shortTime()}. 106 */ 107 public void setTimeStyle(String timeStyle) { 108 this.factories.get(Type.TIME).setStyle("-" + timeStyle); 109 } 110 111 /** 112 * Set the default format style of Joda {@link LocalDateTime} and {@link DateTime} objects, 113 * as well as JDK {@link Date} and {@link Calendar} objects. 114 * Default is {@link DateTimeFormat#shortDateTime()}. 115 */ 116 public void setDateTimeStyle(String dateTimeStyle) { 117 this.factories.get(Type.DATE_TIME).setStyle(dateTimeStyle); 118 } 119 120 /** 121 * Set the formatter that will be used for objects representing date values. 122 * <p>This formatter will be used for the {@link LocalDate} type. When specified 123 * the {@link #setDateStyle(String) dateStyle} and 124 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 125 * @param formatter the formatter to use 126 * @since 3.2 127 * @see #setTimeFormatter 128 * @see #setDateTimeFormatter 129 */ 130 public void setDateFormatter(DateTimeFormatter formatter) { 131 this.formatters.put(Type.DATE, formatter); 132 } 133 134 /** 135 * Set the formatter that will be used for objects representing time values. 136 * <p>This formatter will be used for the {@link LocalTime} type. When specified 137 * the {@link #setTimeStyle(String) timeStyle} and 138 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 139 * @param formatter the formatter to use 140 * @since 3.2 141 * @see #setDateFormatter 142 * @see #setDateTimeFormatter 143 */ 144 public void setTimeFormatter(DateTimeFormatter formatter) { 145 this.formatters.put(Type.TIME, formatter); 146 } 147 148 /** 149 * Set the formatter that will be used for objects representing date and time values. 150 * <p>This formatter will be used for {@link LocalDateTime}, {@link ReadableInstant}, 151 * {@link Date} and {@link Calendar} types. When specified 152 * the {@link #setDateTimeStyle(String) dateTimeStyle} and 153 * {@link #setUseIsoFormat(boolean) useIsoFormat} properties will be ignored. 154 * @param formatter the formatter to use 155 * @since 3.2 156 * @see #setDateFormatter 157 * @see #setTimeFormatter 158 */ 159 public void setDateTimeFormatter(DateTimeFormatter formatter) { 160 this.formatters.put(Type.DATE_TIME, formatter); 161 } 162 163 164 @Override 165 public void registerFormatters(FormatterRegistry registry) { 166 JodaTimeConverters.registerConverters(registry); 167 168 DateTimeFormatter dateFormatter = getFormatter(Type.DATE); 169 DateTimeFormatter timeFormatter = getFormatter(Type.TIME); 170 DateTimeFormatter dateTimeFormatter = getFormatter(Type.DATE_TIME); 171 172 addFormatterForFields(registry, 173 new ReadablePartialPrinter(dateFormatter), 174 new LocalDateParser(dateFormatter), 175 LocalDate.class); 176 177 addFormatterForFields(registry, 178 new ReadablePartialPrinter(timeFormatter), 179 new LocalTimeParser(timeFormatter), 180 LocalTime.class); 181 182 addFormatterForFields(registry, 183 new ReadablePartialPrinter(dateTimeFormatter), 184 new LocalDateTimeParser(dateTimeFormatter), 185 LocalDateTime.class); 186 187 addFormatterForFields(registry, 188 new ReadableInstantPrinter(dateTimeFormatter), 189 new DateTimeParser(dateTimeFormatter), 190 ReadableInstant.class); 191 192 // In order to retain backwards compatibility we only register Date/Calendar 193 // types when a user defined formatter is specified (see SPR-10105) 194 if (this.formatters.containsKey(Type.DATE_TIME)) { 195 addFormatterForFields(registry, 196 new ReadableInstantPrinter(dateTimeFormatter), 197 new DateTimeParser(dateTimeFormatter), 198 Date.class, Calendar.class); 199 } 200 201 registry.addFormatterForFieldType(Period.class, new PeriodFormatter()); 202 registry.addFormatterForFieldType(Duration.class, new DurationFormatter()); 203 registry.addFormatterForFieldType(YearMonth.class, new YearMonthFormatter()); 204 registry.addFormatterForFieldType(MonthDay.class, new MonthDayFormatter()); 205 206 registry.addFormatterForFieldAnnotation(new JodaDateTimeFormatAnnotationFormatterFactory()); 207 } 208 209 private DateTimeFormatter getFormatter(Type type) { 210 DateTimeFormatter formatter = this.formatters.get(type); 211 if (formatter != null) { 212 return formatter; 213 } 214 DateTimeFormatter fallbackFormatter = getFallbackFormatter(type); 215 return this.factories.get(type).createDateTimeFormatter(fallbackFormatter); 216 } 217 218 private DateTimeFormatter getFallbackFormatter(Type type) { 219 switch (type) { 220 case DATE: return DateTimeFormat.shortDate(); 221 case TIME: return DateTimeFormat.shortTime(); 222 default: return DateTimeFormat.shortDateTime(); 223 } 224 } 225 226 private void addFormatterForFields(FormatterRegistry registry, Printer<?> printer, 227 Parser<?> parser, Class<?>... fieldTypes) { 228 229 for (Class<?> fieldType : fieldTypes) { 230 registry.addFormatterForFieldType(fieldType, printer, parser); 231 } 232 } 233 234}