001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.swaps; 014 015import java.lang.reflect.*; 016import java.time.*; 017import java.time.chrono.*; 018import java.time.format.*; 019import java.time.temporal.*; 020import java.util.*; 021import java.util.concurrent.*; 022 023import org.apache.juneau.*; 024import org.apache.juneau.internal.*; 025import org.apache.juneau.reflect.*; 026import org.apache.juneau.swap.*; 027 028/** 029 * Swap that converts {@link Temporal} objects to strings. 030 * 031 * <p> 032 * Uses the {@link DateTimeFormatter} class for converting {@link Temporal} objects to and from strings. 033 * 034 * <p> 035 * Supports any of the following temporal objects: 036 * <ul class='javatree'> 037 * <li class='jc'>{@link HijrahDate} 038 * <li class='jc'>{@link Instant} 039 * <li class='jc'>{@link JapaneseDate} 040 * <li class='jc'>{@link LocalDate} 041 * <li class='jc'>{@link LocalDateTime} 042 * <li class='jc'>{@link LocalTime} 043 * <li class='jc'>{@link MinguoDate} 044 * <li class='jc'>{@link OffsetDateTime} 045 * <li class='jc'>{@link OffsetTime} 046 * <li class='jc'>{@link ThaiBuddhistDate} 047 * <li class='jc'>{@link Year} 048 * <li class='jc'>{@link YearMonth} 049 * <li class='jc'>{@link ZonedDateTime} 050 * </ul> 051 * 052 * <h5 class='section'>See Also:</h5><ul> 053 * <li class='link'><a class="doclink" href="../../../../index.html#jm.Swaps">Swaps</a> 054 * </ul> 055 */ 056public class TemporalSwap extends StringSwap<Temporal> { 057 058 /** 059 * Default swap to {@link DateTimeFormatter#BASIC_ISO_DATE}. 060 * <p> 061 * Example: <js>"20111203"</js> 062 */ 063 public static class BasicIsoDate extends TemporalSwap { 064 065 /** Default instance.*/ 066 public static final TemporalSwap DEFAULT = new BasicIsoDate(); 067 068 /** Constructor.*/ 069 public BasicIsoDate() { 070 super("BASIC_ISO_DATE", true); 071 } 072 } 073 074 /** 075 * Default swap to {@link DateTimeFormatter#ISO_DATE}. 076 * <p> 077 * Example: <js>"2011-12-03+01:00"</js> or <js>"2011-12-03"</js> 078 */ 079 public static class IsoDate extends TemporalSwap { 080 081 /** Default instance.*/ 082 public static final TemporalSwap DEFAULT = new IsoDate(); 083 084 /** Constructor.*/ 085 public IsoDate() { 086 super("ISO_DATE", true); 087 } 088 } 089 090 /** 091 * Default swap to {@link DateTimeFormatter#ISO_DATE_TIME}. 092 * <p> 093 * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js> 094 */ 095 public static class IsoDateTime extends TemporalSwap { 096 097 /** Default instance.*/ 098 public static final TemporalSwap DEFAULT = new IsoDateTime(); 099 100 /** Constructor.*/ 101 public IsoDateTime() { 102 super("ISO_DATE_TIME", true); 103 } 104 } 105 106 /** 107 * Default swap to {@link DateTimeFormatter#ISO_INSTANT}. 108 * <p> 109 * Example: <js>"2011-12-03T10:15:30Z"</js> 110 */ 111 public static class IsoInstant extends TemporalSwap { 112 113 /** Default instance.*/ 114 public static final TemporalSwap DEFAULT = new IsoInstant(); 115 116 /** Constructor.*/ 117 public IsoInstant() { 118 super("ISO_INSTANT", false); 119 } 120 } 121 122 /** 123 * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE}. 124 * <p> 125 * Example: <js>"2011-12-03"</js> 126 */ 127 public static class IsoLocalDate extends TemporalSwap { 128 129 /** Default instance.*/ 130 public static final TemporalSwap DEFAULT = new IsoLocalDate(); 131 132 /** Constructor.*/ 133 public IsoLocalDate() { 134 super("ISO_LOCAL_DATE", false); 135 } 136 } 137 138 /** 139 * Default swap to {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}. 140 * <p> 141 * Example: <js>"2011-12-03T10:15:30"</js> 142 */ 143 public static class IsoLocalDateTime extends TemporalSwap { 144 145 /** Default instance.*/ 146 public static final TemporalSwap DEFAULT = new IsoLocalDateTime(); 147 148 /** Constructor.*/ 149 public IsoLocalDateTime() { 150 super("ISO_LOCAL_DATE_TIME", true); 151 } 152 } 153 154 /** 155 * Default swap to {@link DateTimeFormatter#ISO_LOCAL_TIME}. 156 * <p> 157 * Example: <js>"10:15:30"</js> 158 */ 159 public static class IsoLocalTime extends TemporalSwap { 160 161 /** Default instance.*/ 162 public static final TemporalSwap DEFAULT = new IsoLocalTime(); 163 164 /** Constructor.*/ 165 public IsoLocalTime() { 166 super("ISO_LOCAL_TIME", true); 167 } 168 } 169 170 /** 171 * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE}. 172 * <p> 173 * Example: <js>"2011-12-03"</js> 174 */ 175 public static class IsoOffsetDate extends TemporalSwap { 176 177 /** Default instance.*/ 178 public static final TemporalSwap DEFAULT = new IsoOffsetDate(); 179 180 /** Constructor.*/ 181 public IsoOffsetDate() { 182 super("ISO_OFFSET_DATE", false); 183 } 184 } 185 186 /** 187 * Default swap to {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}. 188 * <p> 189 * Example: <js>"2011-12-03T10:15:30+01:00"</js> 190 */ 191 public static class IsoOffsetDateTime extends TemporalSwap { 192 193 /** Default instance.*/ 194 public static final TemporalSwap DEFAULT = new IsoOffsetDateTime(); 195 196 /** Constructor.*/ 197 public IsoOffsetDateTime() { 198 super("ISO_OFFSET_DATE_TIME", false); 199 } 200 } 201 202 /** 203 * Default swap to {@link DateTimeFormatter#ISO_OFFSET_TIME}. 204 * <p> 205 * Example: <js>"10:15:30+01:00"</js> 206 */ 207 public static class IsoOffsetTime extends TemporalSwap { 208 209 /** Default instance.*/ 210 public static final TemporalSwap DEFAULT = new IsoOffsetTime(); 211 212 /** Constructor.*/ 213 public IsoOffsetTime() { 214 super("ISO_OFFSET_TIME", false); 215 } 216 } 217 218 /** 219 * Default swap to {@link DateTimeFormatter#ISO_ORDINAL_DATE}. 220 * <p> 221 * Example: <js>"2012-337"</js> 222 */ 223 public static class IsoOrdinalDate extends TemporalSwap { 224 225 /** Default instance.*/ 226 public static final TemporalSwap DEFAULT = new IsoOrdinalDate(); 227 228 /** Constructor.*/ 229 public IsoOrdinalDate() { 230 super("ISO_ORDINAL_DATE", true); 231 } 232 } 233 234 /** 235 * Default swap to {@link DateTimeFormatter#ISO_TIME}. 236 * <p> 237 * Example: <js>"10:15:30+01:00"</js> or <js>"10:15:30"</js> 238 */ 239 public static class IsoTime extends TemporalSwap { 240 241 /** Default instance.*/ 242 public static final TemporalSwap DEFAULT = new IsoTime(); 243 244 /** Constructor.*/ 245 public IsoTime() { 246 super("ISO_TIME", true); 247 } 248 } 249 250 /** 251 * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}. 252 * <p> 253 * Example: <js>"2012-W48-6"</js> 254 */ 255 public static class IsoWeekDate extends TemporalSwap { 256 257 /** Default instance.*/ 258 public static final TemporalSwap DEFAULT = new IsoWeekDate(); 259 260 /** Constructor.*/ 261 public IsoWeekDate() { 262 super("ISO_WEEK_DATE", true); 263 } 264 } 265 266 /** 267 * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}. 268 * <p> 269 * Example: <js>"2011"</js> 270 */ 271 public static class IsoYear extends TemporalSwap { 272 273 /** Default instance.*/ 274 public static final TemporalSwap DEFAULT = new IsoYear(); 275 276 /** Constructor.*/ 277 public IsoYear() { 278 super("uuuu", true); 279 } 280 } 281 282 /** 283 * Default swap to {@link DateTimeFormatter#ISO_WEEK_DATE}. 284 * <p> 285 * Example: <js>"2011-12"</js> 286 */ 287 public static class IsoYearMonth extends TemporalSwap { 288 289 /** Default instance.*/ 290 public static final TemporalSwap DEFAULT = new IsoYearMonth(); 291 292 /** Constructor.*/ 293 public IsoYearMonth() { 294 super("uuuu-MM", true); 295 } 296 } 297 298 /** 299 * Default swap to {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}. 300 * <p> 301 * Example: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js> 302 */ 303 public static class IsoZonedDateTime extends TemporalSwap { 304 305 /** Default instance.*/ 306 public static final TemporalSwap DEFAULT = new IsoZonedDateTime(); 307 308 /** Constructor.*/ 309 public IsoZonedDateTime() { 310 super("ISO_ZONED_DATE_TIME", false); 311 } 312 } 313 314 /** 315 * Default swap to {@link DateTimeFormatter#RFC_1123_DATE_TIME}. 316 * <p> 317 * Example: <js>"Tue, 3 Jun 2008 11:05:30 GMT"</js> 318 */ 319 public static class Rfc1123DateTime extends TemporalSwap { 320 321 /** Default instance.*/ 322 public static final TemporalSwap DEFAULT = new Rfc1123DateTime(); 323 324 /** Constructor.*/ 325 public Rfc1123DateTime() { 326 super("RFC_1123_DATE_TIME", false); 327 } 328 } 329 330 private static final ZoneId Z = ZoneId.of("Z"); 331 private static final Map<Class<? extends Temporal>,Method> FROM_METHODS = new ConcurrentHashMap<>(); 332 333 private static Method findParseMethod(Class<? extends Temporal> c) throws ExecutableException { 334 Method m = FROM_METHODS.get(c); 335 if (m == null) { 336 MethodInfo mi = ClassInfo.of(c).getPublicMethod( 337 x -> x.isStatic() 338 && x.isNotDeprecated() 339 && x.hasName("from") 340 && x.hasReturnType(c) 341 && x.hasParamTypes(TemporalAccessor.class) 342 ); 343 if (mi == null) 344 throw new ExecutableException("Parse method not found on temporal class ''{0}''", c.getSimpleName()); 345 m = mi.inner(); 346 FROM_METHODS.put(c, m); 347 } 348 return m; 349 } 350 351 private final DateTimeFormatter formatter; 352 private final boolean zoneOptional; 353 354 /** 355 * Constructor. 356 * 357 * @param pattern The timestamp format or name of predefined {@link DateTimeFormatter}. 358 * @param zoneOptional <jk>true</jk> if the time zone on the pattern is optional. 359 */ 360 public TemporalSwap(String pattern, boolean zoneOptional) { 361 super(Temporal.class); 362 this.formatter = DateUtils.getFormatter(pattern); 363 this.zoneOptional = zoneOptional; 364 } 365 366 /** 367 * Returns <jk>true</jk> if the time zone on the pattern is optional. 368 * 369 * <p> 370 * If it's not optional, then local dates/times must be converted into zoned times using the session time zone. 371 * Otherwise, local date/times are fine. 372 * 373 * @return <jk>true</jk> if the time zone on the pattern is optional. 374 */ 375 protected boolean zoneOptional() { 376 return zoneOptional; 377 } 378 379 @Override /* ObjectSwap */ 380 public String swap(BeanSession session, Temporal o) throws Exception { 381 if (o == null) 382 return null; 383 o = convertToSerializable(session, o); 384 return formatter.format(o); 385 } 386 387 /** 388 * Converts the specified temporal object to a form suitable to be serialized using any pattern. 389 * 390 * @param session The current bean session. 391 * @param t The temporal object to convert. 392 * @return The converted temporal object. 393 */ 394 protected Temporal convertToSerializable(BeanSession session, Temporal t) { 395 396 ZoneId zoneId = session.getTimeZoneId(); 397 Class<? extends Temporal> tc = t.getClass(); 398 399 // Instant is always serialized in GMT. 400 if (tc == Instant.class) 401 return ZonedDateTime.from(defaulting(t, Z)); 402 403 // These can handle any pattern. 404 if (tc == ZonedDateTime.class || tc == OffsetDateTime.class) 405 return t; 406 407 // Pattern optionally includes a time zone, so zoned and local date-times are good. 408 if (zoneOptional()) { 409 if (tc == LocalDateTime.class) 410 return t; 411 if (tc == OffsetTime.class) 412 return ZonedDateTime.from(defaulting(t, zoneId)); 413 return LocalDateTime.from(defaulting(t, zoneId)); 414 } 415 416 return ZonedDateTime.from(defaulting(t, zoneId)); 417 } 418 419 @SuppressWarnings("unchecked") 420 @Override /* ObjectSwap */ 421 public Temporal unswap(BeanSession session, String f, ClassMeta<?> hint) throws Exception { 422 if (hint == null) 423 hint = session.getClassMeta(Instant.class); 424 Class<? extends Temporal> tc = (Class<? extends Temporal>)hint.getInnerClass(); 425 426 ZoneId offset = session.getTimeZoneId(); 427 428 if (tc == Instant.class) 429 offset = Z; 430 431 Method parseMethod = findParseMethod(tc); 432 433 TemporalAccessor ta = defaulting(formatter.parse(f), offset); 434 return (Temporal)parseMethod.invoke(null, ta); 435 } 436 437 private final TemporalAccessor defaulting(TemporalAccessor t, ZoneId zoneId) { 438 return new DefaultingTemporalAccessor(t, zoneId); 439 } 440}