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