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.marshall;
018
019import java.time.*;
020import java.time.format.*;
021import java.util.*;
022
023import org.apache.juneau.marshall.swaps.*;
024
025/**
026 * Supported wire formats for {@link Date} values.
027 *
028 * <p>
029 * Used by {@link MarshallingContext.Builder#dateFormat(DateFormat)},
030 * {@link Marshalled#dateFormat()},
031 * {@link MarshalledProp#dateFormat()}, and
032 * {@link MarshalledConfig#dateFormat()} to control how {@link Date}
033 * values are written to and read from the wire.
034 *
035 * <p>
036 * The default is {@link #ISO_LOCAL_DATE_TIME} which preserves the historical wire output produced
037 * before this enum existed (note: {@link Date} carries no zone so the default emits a local date-time,
038 * deliberately differing from {@link CalendarFormat#ISO_OFFSET_DATE_TIME}).
039 *
040 * <p>
041 * Numeric formats ({@link #MILLIS}) emit native numeric wire types on binary serializers
042 * (BSON / CBOR / MsgPack) and bare numbers on text formats. Text formats emit a quoted ISO 8601 string for
043 * every other constant.
044 */
045@SuppressWarnings({
046   "java:S2143" // Public API formats/parses Date by design (it's the Date marshalling-format enum); internals already use java.time.
047})
048public enum DateFormat {
049
050   /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */
051   NOT_SET,
052
053   /** {@link DateTimeFormatter#BASIC_ISO_DATE}: <js>"20111203"</js>. */
054   BASIC_ISO_DATE,
055
056   /** {@link DateTimeFormatter#ISO_DATE}: <js>"2011-12-03+01:00"</js> or <js>"2011-12-03"</js>. */
057   ISO_DATE,
058
059   /** {@link DateTimeFormatter#ISO_DATE_TIME}: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>. */
060   ISO_DATE_TIME,
061
062   /** {@link DateTimeFormatter#ISO_INSTANT}: <js>"2011-12-03T10:15:30Z"</js>. */
063   ISO_INSTANT,
064
065   /** {@link DateTimeFormatter#ISO_LOCAL_DATE}: <js>"2011-12-03"</js>. */
066   ISO_LOCAL_DATE,
067
068   /** {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME} (the default): <js>"2011-12-03T10:15:30"</js>. */
069   ISO_LOCAL_DATE_TIME,
070
071   /** {@link DateTimeFormatter#ISO_LOCAL_TIME}: <js>"10:15:30"</js>. */
072   ISO_LOCAL_TIME,
073
074   /** {@link DateTimeFormatter#ISO_OFFSET_DATE}: <js>"2011-12-03+01:00"</js>. */
075   ISO_OFFSET_DATE,
076
077   /** {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME}: <js>"2011-12-03T10:15:30+01:00"</js>. */
078   ISO_OFFSET_DATE_TIME,
079
080   /** {@link DateTimeFormatter#ISO_OFFSET_TIME}: <js>"10:15:30+01:00"</js>. */
081   ISO_OFFSET_TIME,
082
083   /** {@link DateTimeFormatter#ISO_ORDINAL_DATE}: <js>"2012-337"</js>. */
084   ISO_ORDINAL_DATE,
085
086   /** {@link DateTimeFormatter#ISO_TIME}: <js>"10:15:30+01:00"</js> or <js>"10:15:30"</js>. */
087   ISO_TIME,
088
089   /** {@link DateTimeFormatter#ISO_WEEK_DATE}: <js>"2012-W48-6"</js>. */
090   ISO_WEEK_DATE,
091
092   /** {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>. */
093   ISO_ZONED_DATE_TIME,
094
095   /** {@link DateTimeFormatter#RFC_1123_DATE_TIME}: <js>"Tue, 3 Jun 2008 11:05:30 GMT"</js>. */
096   RFC_1123_DATE_TIME,
097
098   /**
099    * Epoch milliseconds as a numeric value.
100    *
101    * <p>
102    * Emitted as a native int64 by binary serializers (BSON / CBOR / MsgPack) and as a bare integer
103    * literal by text serializers.
104    */
105   MILLIS;
106
107   /**
108    * Formats the specified value using this format.
109    *
110    * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>).
111    * @param zoneId The default zone id used to interpret the date.
112    * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>.
113    */
114   public String format(Date value, ZoneId zoneId) {
115      if (value == null)
116         return null;
117      if (this == MILLIS)
118         return Long.toString(value.getTime());
119      var zone = zoneId == null ? ZoneId.systemDefault() : zoneId;
120      return formatter().format(value.toInstant().atZone(zone));
121   }
122
123   /**
124    * Parses the specified wire value using this format.
125    *
126    * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>).
127    * @param zoneId The default zone id used for time-only / unzoned values.
128    * @return The parsed {@link Date}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank.
129    */
130   public Date parse(String value, ZoneId zoneId) {
131      if (value == null)
132         return null;
133      var s = value.trim();
134      if (s.isEmpty())
135         return null;
136      if (this == MILLIS)
137         return new Date(Long.parseLong(s));
138      var zone = zoneId == null ? ZoneId.systemDefault() : zoneId;
139      var ta = new DefaultingTemporalAccessor(formatter().parse(s), zone);
140      return Date.from(ZonedDateTime.from(ta).toInstant());
141   }
142
143   /**
144    * Returns <jk>true</jk> if this format is numeric on the wire.
145    *
146    * @return <jk>true</jk> if this format emits a numeric wire value.
147    */
148   public boolean isNumeric() {
149      return this == MILLIS;
150   }
151
152   private DateTimeFormatter formatter() {
153      return switch (this) {
154         case NOT_SET, ISO_LOCAL_DATE_TIME -> DateTimeFormatter.ISO_LOCAL_DATE_TIME;
155         case BASIC_ISO_DATE -> DateTimeFormatter.BASIC_ISO_DATE;
156         case ISO_DATE -> DateTimeFormatter.ISO_DATE;
157         case ISO_DATE_TIME -> DateTimeFormatter.ISO_DATE_TIME;
158         case ISO_INSTANT -> DateTimeFormatter.ISO_INSTANT;
159         case ISO_LOCAL_DATE -> DateTimeFormatter.ISO_LOCAL_DATE;
160         case ISO_LOCAL_TIME -> DateTimeFormatter.ISO_LOCAL_TIME;
161         case ISO_OFFSET_DATE -> DateTimeFormatter.ISO_OFFSET_DATE;
162         case ISO_OFFSET_DATE_TIME -> DateTimeFormatter.ISO_OFFSET_DATE_TIME;
163         case ISO_OFFSET_TIME -> DateTimeFormatter.ISO_OFFSET_TIME;
164         case ISO_ORDINAL_DATE -> DateTimeFormatter.ISO_ORDINAL_DATE;
165         case ISO_TIME -> DateTimeFormatter.ISO_TIME;
166         case ISO_WEEK_DATE -> DateTimeFormatter.ISO_WEEK_DATE;
167         case ISO_ZONED_DATE_TIME -> DateTimeFormatter.ISO_ZONED_DATE_TIME;
168         case RFC_1123_DATE_TIME -> DateTimeFormatter.RFC_1123_DATE_TIME;
169         case MILLIS -> throw new IllegalStateException("MILLIS does not use a DateTimeFormatter");
170      };
171   }
172}