1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.myfaces.tobago.convert;
21
22 import org.apache.myfaces.tobago.internal.util.StringUtils;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 import javax.faces.component.UIComponent;
27 import javax.faces.component.UIInput;
28 import javax.faces.context.FacesContext;
29 import javax.faces.convert.ConverterException;
30 import java.lang.invoke.MethodHandles;
31 import java.text.ParseException;
32 import java.text.SimpleDateFormat;
33 import java.time.LocalDate;
34 import java.time.LocalDateTime;
35 import java.time.LocalTime;
36 import java.time.OffsetDateTime;
37 import java.time.OffsetTime;
38 import java.time.ZonedDateTime;
39 import java.time.format.DateTimeFormatter;
40 import java.time.temporal.TemporalAccessor;
41 import java.util.Calendar;
42 import java.util.Locale;
43 import java.util.TimeZone;
44
45 import static org.apache.myfaces.tobago.convert.DateTimeConverter.CONVERTER_ID;
46
47 @org.apache.myfaces.tobago.apt.annotation.Converter(id = CONVERTER_ID)
48 public class DateTimeConverter extends javax.faces.convert.DateTimeConverter {
49
50 private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
51
52 public static final String CONVERTER_ID = "org.apache.myfaces.tobago.DateTime";
53
54 private static final String TYPE_DATE = "date";
55 private static final String TYPE_TIME = "time";
56 private static final String TYPE_BOTH = "both";
57 private static final String TYPE_CALENDAR = "calendar";
58 private static final String TYPE_LOCAL_DATE = "localDate";
59 private static final String TYPE_LOCAL_TIME = "localTime";
60 private static final String TYPE_LOCAL_DATE_TIME = "localDateTime";
61 private static final String TYPE_OFFSET_TIME = "offsetTime";
62 private static final String TYPE_OFFSET_DATE_TIME = "offsetDateTime";
63 private static final String TYPE_ZONED_DATE_TIME = "zonedDateTime";
64
65 @Override
66 public Object getAsObject(FacesContext facesContext, UIComponent component, String string) throws ConverterException {
67 if (StringUtils.isBlank(string)) {
68 return null;
69 } else {
70 final String type = getType();
71 if (TYPE_DATE.equals(type) || TYPE_TIME.equals(type) || TYPE_BOTH.equals(type)) {
72 return super.getAsObject(facesContext, component, string);
73 } else if (TYPE_CALENDAR.equals(type)) {
74 final Locale locale = getLocale();
75 final String pattern = getPattern();
76 final TimeZone timeZone = getTimeZone();
77 final Calendar calendar;
78 if (component instanceof UIInput && ((UIInput) component).getValue() != null) {
79 calendar = (Calendar) ((UIInput) component).getValue();
80 } else {
81 if (timeZone != null && locale != null) {
82 calendar = Calendar.getInstance(timeZone, locale);
83 } else if (locale != null) {
84 calendar = Calendar.getInstance(locale);
85 } else if (timeZone != null) {
86 calendar = Calendar.getInstance(timeZone);
87 } else {
88 calendar = Calendar.getInstance();
89 }
90 }
91
92 SimpleDateFormat sdf = locale != null ? new SimpleDateFormat(pattern, locale) : new SimpleDateFormat(pattern);
93 try {
94 calendar.setTime(sdf.parse(string));
95 } catch (ParseException e) {
96 throw new ConverterException("string='" + string + "'", e);
97 }
98
99 return calendar;
100 } else if (TYPE_LOCAL_DATE.equals(type)) {
101 logMandatoryPatterns("yMd");
102 return getDateTimeFormatter().parse(string, LocalDate::from);
103 } else if (TYPE_LOCAL_TIME.equals(type)) {
104 logMandatoryPatterns("H");
105 return getDateTimeFormatter().parse(string, LocalTime::from);
106 } else if (TYPE_LOCAL_DATE_TIME.equals(type)) {
107 logMandatoryPatterns("yMdH");
108 return getDateTimeFormatter().parse(string, LocalDateTime::from);
109 } else if (TYPE_OFFSET_TIME.equals(type)) {
110 logMandatoryPatterns("HZ");
111 return getDateTimeFormatter().parse(string, OffsetTime::from);
112 } else if (TYPE_OFFSET_DATE_TIME.equals(type)) {
113 logMandatoryPatterns("yMdHZ");
114 return getDateTimeFormatter().parse(string, OffsetDateTime::from);
115 } else if (TYPE_ZONED_DATE_TIME.equals(type)) {
116 logMandatoryPatterns("yMdHZ");
117 return getDateTimeFormatter().parse(string, ZonedDateTime::from);
118 } else {
119 throw new ConverterException("invalid type '" + type + "'");
120 }
121 }
122 }
123
124 @Override
125 public String getAsString(FacesContext facesContext, UIComponent component, Object object) throws ConverterException {
126 if (object == null) {
127 return null;
128 } else {
129 final String type = getType();
130 if (TYPE_DATE.equals(type) || TYPE_TIME.equals(type) || TYPE_BOTH.equals(type)) {
131 return super.getAsString(facesContext, component, object);
132 } else if (TYPE_CALENDAR.equals(type)) {
133 Calendar calendar = (Calendar) object;
134 final Locale locale = getLocale();
135 final String pattern = getPattern();
136
137 SimpleDateFormat sdf = new SimpleDateFormat(pattern, locale);
138 return sdf.format(calendar.getTime());
139 } else if (TYPE_LOCAL_DATE.equals(type)
140 || TYPE_LOCAL_TIME.equals(type)
141 || TYPE_LOCAL_DATE_TIME.equals(type)
142 || TYPE_OFFSET_TIME.equals(type)
143 || TYPE_OFFSET_DATE_TIME.equals(type)
144 || TYPE_ZONED_DATE_TIME.equals(type)) {
145 return getDateTimeFormatter().format((TemporalAccessor) object);
146 } else {
147 throw new ConverterException("invalid type '" + type + "'");
148 }
149 }
150 }
151
152 private void logMandatoryPatterns(String mandatoryChars) {
153 logMandatoryPattern(mandatoryChars, "y", "year");
154 logMandatoryPattern(mandatoryChars, "M", "month");
155 logMandatoryPattern(mandatoryChars, "d", "day");
156 logMandatoryPattern(mandatoryChars, "H", "hour");
157 logMandatoryPattern(mandatoryChars, "Z", "offset");
158 }
159
160 private void logMandatoryPattern(String mandatoryChars, String c, String name) {
161 final String pattern = getPattern();
162 if (pattern != null && mandatoryChars.contains(c) && !pattern.contains(c)) {
163 LOG.error("No char for " + name + " ('" + c + "') in pattern: " + pattern);
164 }
165 }
166
167 private DateTimeFormatter getDateTimeFormatter() {
168 final String pattern = getPattern();
169 if (!StringUtils.isBlank(pattern)) {
170 final Locale locale = getLocale();
171 return locale != null ? DateTimeFormatter.ofPattern(pattern, locale) : DateTimeFormatter.ofPattern(pattern);
172 } else {
173 throw new ConverterException("no pattern set");
174 }
175 }
176 }