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 static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.ThrowableUtils.*; 021 022import java.time.*; 023import java.time.format.*; 024import java.util.*; 025 026import javax.xml.datatype.*; 027 028import org.apache.juneau.marshall.swaps.*; 029 030/** 031 * Supported wire formats for {@link Calendar} values. 032 * 033 * <p> 034 * Used by {@link MarshallingContext.Builder#calendarFormat(CalendarFormat)}, 035 * {@link Marshalled#calendarFormat()}, 036 * {@link MarshalledProp#calendarFormat()}, and 037 * {@link MarshalledConfig#calendarFormat()} to control how 038 * {@link Calendar} values (including {@link GregorianCalendar}) are written to and read from the wire. 039 * 040 * <p> 041 * The default is {@link #ISO_OFFSET_DATE_TIME} which preserves the historical wire output produced 042 * before this enum existed. 043 * 044 * <p> 045 * Numeric formats ({@link #MILLIS}) emit native numeric wire types on binary serializers 046 * (BSON / CBOR / MsgPack) and bare numbers on text formats. Text formats emit a quoted ISO 8601 string for 047 * every other constant. 048 * 049 * <p> 050 * {@link XMLGregorianCalendar} fields always emit {@code toXMLFormat()} regardless of the configured 051 * {@code CalendarFormat}. The {@link #XML_FORMAT} constant exists so that regular {@code Calendar} / 052 * {@code GregorianCalendar} fields can opt into the same wire form for symmetry with their 053 * {@code XMLGregorianCalendar} siblings. 054 */ 055public enum CalendarFormat { 056 057 /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */ 058 NOT_SET, 059 060 /** {@link DateTimeFormatter#BASIC_ISO_DATE}: <js>"20111203"</js>. */ 061 BASIC_ISO_DATE, 062 063 /** {@link DateTimeFormatter#ISO_DATE}: <js>"2011-12-03+01:00"</js> or <js>"2011-12-03"</js>. */ 064 ISO_DATE, 065 066 /** {@link DateTimeFormatter#ISO_DATE_TIME}: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>. */ 067 ISO_DATE_TIME, 068 069 /** {@link DateTimeFormatter#ISO_INSTANT}: <js>"2011-12-03T10:15:30Z"</js>. */ 070 ISO_INSTANT, 071 072 /** {@link DateTimeFormatter#ISO_LOCAL_DATE}: <js>"2011-12-03"</js>. */ 073 ISO_LOCAL_DATE, 074 075 /** {@link DateTimeFormatter#ISO_LOCAL_DATE_TIME}: <js>"2011-12-03T10:15:30"</js>. */ 076 ISO_LOCAL_DATE_TIME, 077 078 /** {@link DateTimeFormatter#ISO_LOCAL_TIME}: <js>"10:15:30"</js>. */ 079 ISO_LOCAL_TIME, 080 081 /** {@link DateTimeFormatter#ISO_OFFSET_DATE}: <js>"2011-12-03+01:00"</js>. */ 082 ISO_OFFSET_DATE, 083 084 /** {@link DateTimeFormatter#ISO_OFFSET_DATE_TIME} (the default): <js>"2011-12-03T10:15:30+01:00"</js>. */ 085 ISO_OFFSET_DATE_TIME, 086 087 /** {@link DateTimeFormatter#ISO_OFFSET_TIME}: <js>"10:15:30+01:00"</js>. */ 088 ISO_OFFSET_TIME, 089 090 /** {@link DateTimeFormatter#ISO_ORDINAL_DATE}: <js>"2012-337"</js>. */ 091 ISO_ORDINAL_DATE, 092 093 /** {@link DateTimeFormatter#ISO_TIME}: <js>"10:15:30+01:00"</js> or <js>"10:15:30"</js>. */ 094 ISO_TIME, 095 096 /** {@link DateTimeFormatter#ISO_WEEK_DATE}: <js>"2012-W48-6"</js>. */ 097 ISO_WEEK_DATE, 098 099 /** {@link DateTimeFormatter#ISO_ZONED_DATE_TIME}: <js>"2011-12-03T10:15:30+01:00[Europe/Paris]"</js>. */ 100 ISO_ZONED_DATE_TIME, 101 102 /** {@link DateTimeFormatter#RFC_1123_DATE_TIME}: <js>"Tue, 3 Jun 2008 11:05:30 GMT"</js>. */ 103 RFC_1123_DATE_TIME, 104 105 /** 106 * Epoch milliseconds as a numeric value. 107 * 108 * <p> 109 * Emitted as a native int64 by binary serializers (BSON / CBOR / MsgPack) and as a bare integer 110 * literal by text serializers. 111 */ 112 MILLIS, 113 114 /** 115 * {@link XMLGregorianCalendar#toXMLFormat() XML Schema dateTime} form. 116 * 117 * <p> 118 * Lets regular {@link Calendar} / {@link GregorianCalendar} fields opt into the same wire form 119 * that {@link XMLGregorianCalendar} fields always use, for symmetry across mixed-type beans. 120 */ 121 XML_FORMAT; 122 123 private static final ZoneId Z = ZoneId.of("Z"); 124 125 private static final DatatypeFactory DATATYPE_FACTORY; 126 static { 127 try { 128 DATATYPE_FACTORY = DatatypeFactory.newInstance(); 129 } catch (DatatypeConfigurationException e) { 130 throw toRex(e); 131 } 132 } 133 134 /** 135 * Formats the specified value using this format. 136 * 137 * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>). 138 * @param zoneId The default zone id used for time-only / unzoned values. 139 * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>. 140 */ 141 public String format(Calendar value, ZoneId zoneId) { 142 if (value == null) 143 return null; 144 if (this == MILLIS) 145 return Long.toString(value.getTimeInMillis()); 146 if (this == XML_FORMAT) { 147 var gc = value instanceof GregorianCalendar value2 ? value2 : GregorianCalendar.from(value.toInstant().atZone(value.getTimeZone().toZoneId())); 148 return DATATYPE_FACTORY.newXMLGregorianCalendar(gc).toXMLFormat(); 149 } 150 var zone = zoneId == null ? value.getTimeZone().toZoneId() : zoneId; 151 var zdt = value instanceof GregorianCalendar value2 ? value2.toZonedDateTime() : value.toInstant().atZone(zone); 152 return formatter().format(zdt); 153 } 154 155 /** 156 * Parses the specified wire value using this format. 157 * 158 * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>). 159 * @param zoneId The default zone id used for time-only / unzoned values. 160 * @return The parsed {@link Calendar}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank. 161 */ 162 public Calendar parse(String value, ZoneId zoneId) { 163 if (value == null) 164 return null; 165 var s = value.trim(); 166 if (s.isEmpty()) 167 return null; 168 var zone = zoneId == null ? ZoneId.systemDefault() : zoneId; 169 if (this == MILLIS) 170 return GregorianCalendar.from(Instant.ofEpochMilli(Long.parseLong(s)).atZone(zone)); 171 if (this == XML_FORMAT) 172 return DATATYPE_FACTORY.newXMLGregorianCalendar(s).toGregorianCalendar(); 173 var ta = new DefaultingTemporalAccessor(formatter().parse(s), zone); 174 return GregorianCalendar.from(ZonedDateTime.from(ta)); 175 } 176 177 /** 178 * Returns <jk>true</jk> if this format is numeric on the wire. 179 * 180 * @return <jk>true</jk> if this format emits a numeric wire value. 181 */ 182 public boolean isNumeric() { 183 return this == MILLIS; 184 } 185 186 private DateTimeFormatter formatter() { 187 return switch (this) { 188 case NOT_SET, ISO_OFFSET_DATE_TIME -> DateTimeFormatter.ISO_OFFSET_DATE_TIME; 189 case BASIC_ISO_DATE -> DateTimeFormatter.BASIC_ISO_DATE; 190 case ISO_DATE -> DateTimeFormatter.ISO_DATE; 191 case ISO_DATE_TIME -> DateTimeFormatter.ISO_DATE_TIME; 192 case ISO_INSTANT -> DateTimeFormatter.ISO_INSTANT; 193 case ISO_LOCAL_DATE -> DateTimeFormatter.ISO_LOCAL_DATE; 194 case ISO_LOCAL_DATE_TIME -> DateTimeFormatter.ISO_LOCAL_DATE_TIME; 195 case ISO_LOCAL_TIME -> DateTimeFormatter.ISO_LOCAL_TIME; 196 case ISO_OFFSET_DATE -> DateTimeFormatter.ISO_OFFSET_DATE; 197 case ISO_OFFSET_TIME -> DateTimeFormatter.ISO_OFFSET_TIME; 198 case ISO_ORDINAL_DATE -> DateTimeFormatter.ISO_ORDINAL_DATE; 199 case ISO_TIME -> DateTimeFormatter.ISO_TIME; 200 case ISO_WEEK_DATE -> DateTimeFormatter.ISO_WEEK_DATE; 201 case ISO_ZONED_DATE_TIME -> DateTimeFormatter.ISO_ZONED_DATE_TIME; 202 case RFC_1123_DATE_TIME -> DateTimeFormatter.RFC_1123_DATE_TIME; 203 case XML_FORMAT, MILLIS -> throw isex("Format %s does not use a DateTimeFormatter", this); 204 }; 205 } 206 207 /** 208 * Returns the {@link ZoneId} used by the {@link #ISO_INSTANT} variant. 209 * 210 * @return The fixed {@code "Z"} (UTC) zone id used by the {@link #ISO_INSTANT} variant. 211 */ 212 public static ZoneId zuluZone() { 213 return Z; 214 } 215}