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.util.*; 021 022/** 023 * Supported wire formats for {@link TimeZone} and {@link ZoneId} values. 024 * 025 * <p> 026 * Used by {@link MarshallingContext.Builder#timeZoneFormat(TimeZoneFormat)}, 027 * {@link Marshalled#timeZoneFormat()}, 028 * {@link MarshalledProp#timeZoneFormat()}, and 029 * {@link MarshalledConfig#timeZoneFormat()} to control how time-zone 030 * identifiers are written to and read from the wire. 031 * 032 * <p> 033 * The default is {@link #ID} which preserves the historical wire output produced before this enum existed 034 * ({@code TimeZone.getID()} / {@code ZoneId.getId()}). 035 */ 036public enum TimeZoneFormat { 037 038 /** Sentinel meaning "no value configured" — falls through to the next-higher precedence level. */ 039 NOT_SET, 040 041 /** The IANA zone id (the default): <js>"America/Los_Angeles"</js>, <js>"UTC"</js>, <js>"GMT-08:00"</js>. */ 042 ID, 043 044 /** Normalized to a {@link ZoneOffset} where possible: <js>"-08:00"</js>; falls back to {@link #ID} for region zones. */ 045 OFFSET, 046 047 /** 048 * Default long display name in {@link Locale#ROOT} (formatting only; not always round-trippable): 049 * <js>"Pacific Standard Time"</js>. 050 * 051 * <p> 052 * Parsing accepts any value that {@link ZoneId#of(String)} understands; localized display names are 053 * <b>not</b> reverse-parsed. 054 */ 055 NAME_LONG, 056 057 /** 058 * Default short display name in {@link Locale#ROOT} (formatting only; not always round-trippable): 059 * <js>"PST"</js>. 060 * 061 * <p> 062 * Parsing accepts any value that {@link ZoneId#of(String)} understands; localized display names are 063 * <b>not</b> reverse-parsed. 064 */ 065 NAME_SHORT; 066 067 /** 068 * Formats the specified {@link TimeZone} using this format. 069 * 070 * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>). 071 * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>. 072 */ 073 public String format(TimeZone value) { 074 if (value == null) 075 return null; 076 return switch (this) { 077 case NAME_LONG -> value.getDisplayName(false, TimeZone.LONG, Locale.ROOT); 078 case NAME_SHORT -> value.getDisplayName(false, TimeZone.SHORT, Locale.ROOT); 079 case OFFSET -> formatOffset(value.toZoneId()); 080 case NOT_SET, ID -> value.getID(); 081 }; 082 } 083 084 /** 085 * Formats the specified {@link ZoneId} using this format. 086 * 087 * @param value The value to format. Can be <jk>null</jk> (returns <jk>null</jk>). 088 * @return The formatted string, or <jk>null</jk> if {@code value} is <jk>null</jk>. 089 */ 090 public String format(ZoneId value) { 091 if (value == null) 092 return null; 093 return switch (this) { 094 case NAME_LONG -> TimeZone.getTimeZone(value).getDisplayName(false, TimeZone.LONG, Locale.ROOT); 095 case NAME_SHORT -> TimeZone.getTimeZone(value).getDisplayName(false, TimeZone.SHORT, Locale.ROOT); 096 case OFFSET -> formatOffset(value); 097 case NOT_SET, ID -> value.getId(); 098 }; 099 } 100 101 /** 102 * Parses the specified wire value as a {@link TimeZone}. 103 * 104 * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>). 105 * @return The parsed {@link TimeZone}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank. 106 */ 107 public static TimeZone parseTimeZone(String value) { 108 if (value == null) 109 return null; 110 var s = value.trim(); 111 if (s.isEmpty()) 112 return null; 113 return TimeZone.getTimeZone(s); 114 } 115 116 /** 117 * Parses the specified wire value as a {@link ZoneId}. 118 * 119 * @param value The wire value. Can be <jk>null</jk> or blank (returns <jk>null</jk>). 120 * @return The parsed {@link ZoneId}, or <jk>null</jk> if {@code value} is <jk>null</jk> or blank. 121 */ 122 public static ZoneId parseZoneId(String value) { 123 if (value == null) 124 return null; 125 var s = value.trim(); 126 if (s.isEmpty()) 127 return null; 128 return ZoneId.of(s, ZoneId.SHORT_IDS); 129 } 130 131 private static String formatOffset(ZoneId zoneId) { 132 try { 133 return ZoneOffset.from(zoneId.getRules().getOffset(Instant.now())).getId(); 134 } catch (@SuppressWarnings("unused") Exception e) { // NOSONAR: ZoneOffset.from throws DateTimeException; fall back to ID 135 return zoneId.getId(); 136 } 137 } 138}