001/* 002 * Copyright (c) 2007-2013, Stephen Colebourne & Michael Nascimento Santos 003 * 004 * All rights reserved. 005 * 006 * Redistribution and use in source and binary forms, with or without 007 * modification, are permitted provided that the following conditions are met: 008 * 009 * * Redistributions of source code must retain the above copyright notice, 010 * this list of conditions and the following disclaimer. 011 * 012 * * Redistributions in binary form must reproduce the above copyright notice, 013 * this list of conditions and the following disclaimer in the documentation 014 * and/or other materials provided with the distribution. 015 * 016 * * Neither the name of JSR-310 nor the names of its contributors 017 * may be used to endorse or promote products derived from this software 018 * without specific prior written permission. 019 * 020 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 021 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 022 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 023 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 024 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 025 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 026 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 027 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 028 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 029 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 030 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 031 */ 032package org.threeten.bp.format; 033 034import java.text.DecimalFormatSymbols; 035import java.util.Locale; 036import java.util.Objects; 037import java.util.concurrent.ConcurrentHashMap; 038import java.util.concurrent.ConcurrentMap; 039 040/** 041 * Localized symbols used in date and time formatting. 042 * <p> 043 * A significant part of dealing with dates and times is the localization. 044 * This class acts as a central point for accessing the information. 045 * 046 * <h3>Specification for implementors</h3> 047 * This class is immutable and thread-safe. 048 */ 049public final class DateTimeFormatSymbols { 050 051 /** 052 * The standard set of non-localized symbols. 053 * <p> 054 * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point. 055 */ 056 public static final DateTimeFormatSymbols STANDARD = new DateTimeFormatSymbols('0', '+', '-', '.'); 057 /** 058 * The cache of symbols instances. 059 */ 060 private static final ConcurrentMap<Locale, DateTimeFormatSymbols> CACHE = new ConcurrentHashMap<>(16, 0.75f, 2); 061 062 /** 063 * The zero digit. 064 */ 065 private final char zeroDigit; 066 /** 067 * The positive sign. 068 */ 069 private final char positiveSign; 070 /** 071 * The negative sign. 072 */ 073 private final char negativeSign; 074 /** 075 * The decimal separator. 076 */ 077 private final char decimalSeparator; 078 079 //----------------------------------------------------------------------- 080 /** 081 * Lists all the locales that are supported. 082 * <p> 083 * The locale 'en_US' will always be present. 084 * 085 * @return an array of locales for which localization is supported 086 */ 087 public static Locale[] getAvailableLocales() { 088 return DecimalFormatSymbols.getAvailableLocales(); 089 } 090 091 /** 092 * Obtains symbols for the default locale. 093 * <p> 094 * This method provides access to locale sensitive symbols. 095 * 096 * @return the info, not null 097 */ 098 public static DateTimeFormatSymbols ofDefaultLocale() { 099 return of(Locale.getDefault()); 100 } 101 102 /** 103 * Obtains symbols for the specified locale. 104 * <p> 105 * This method provides access to locale sensitive symbols. 106 * 107 * @param locale the locale, not null 108 * @return the info, not null 109 */ 110 public static DateTimeFormatSymbols of(Locale locale) { 111 Objects.requireNonNull(locale, "locale"); 112 DateTimeFormatSymbols info = CACHE.get(locale); 113 if (info == null) { 114 info = create(locale); 115 CACHE.putIfAbsent(locale, info); 116 info = CACHE.get(locale); 117 } 118 return info; 119 } 120 121 private static DateTimeFormatSymbols create(Locale locale) { 122 DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale); 123 char zeroDigit = oldSymbols.getZeroDigit(); 124 char positiveSign = '+'; 125 char negativeSign = oldSymbols.getMinusSign(); 126 char decimalSeparator = oldSymbols.getDecimalSeparator(); 127 if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') { 128 return STANDARD; 129 } 130 return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); 131 } 132 133 //----------------------------------------------------------------------- 134 /** 135 * Restricted constructor. 136 * 137 * @param zeroChar the character to use for the digit of zero 138 * @param positiveSignChar the character to use for the positive sign 139 * @param negativeSignChar the character to use for the negative sign 140 * @param decimalPointChar the character to use for the decimal point 141 */ 142 private DateTimeFormatSymbols(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) { 143 this.zeroDigit = zeroChar; 144 this.positiveSign = positiveSignChar; 145 this.negativeSign = negativeSignChar; 146 this.decimalSeparator = decimalPointChar; 147 } 148 149 //----------------------------------------------------------------------- 150 /** 151 * Gets the character that represents zero. 152 * <p> 153 * The character used to represent digits may vary by culture. 154 * This method specifies the zero character to use, which implies the characters for one to nine. 155 * 156 * @return the character for zero 157 */ 158 public char getZeroDigit() { 159 return zeroDigit; 160 } 161 162 /** 163 * Returns a copy of the info with a new character that represents zero. 164 * <p> 165 * The character used to represent digits may vary by culture. 166 * This method specifies the zero character to use, which implies the characters for one to nine. 167 * 168 * @param zeroDigit the character for zero 169 * @return a copy with a new character that represents zero, not null 170 171 */ 172 public DateTimeFormatSymbols withZeroDigit(char zeroDigit) { 173 if (zeroDigit == this.zeroDigit) { 174 return this; 175 } 176 return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); 177 } 178 179 //----------------------------------------------------------------------- 180 /** 181 * Gets the character that represents the positive sign. 182 * <p> 183 * The character used to represent a positive number may vary by culture. 184 * This method specifies the character to use. 185 * 186 * @return the character for the positive sign 187 */ 188 public char getPositiveSign() { 189 return positiveSign; 190 } 191 192 /** 193 * Returns a copy of the info with a new character that represents the positive sign. 194 * <p> 195 * The character used to represent a positive number may vary by culture. 196 * This method specifies the character to use. 197 * 198 * @param positiveSign the character for the positive sign 199 * @return a copy with a new character that represents the positive sign, not null 200 */ 201 public DateTimeFormatSymbols withPositiveSign(char positiveSign) { 202 if (positiveSign == this.positiveSign) { 203 return this; 204 } 205 return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); 206 } 207 208 //----------------------------------------------------------------------- 209 /** 210 * Gets the character that represents the negative sign. 211 * <p> 212 * The character used to represent a negative number may vary by culture. 213 * This method specifies the character to use. 214 * 215 * @return the character for the negative sign 216 */ 217 public char getNegativeSign() { 218 return negativeSign; 219 } 220 221 /** 222 * Returns a copy of the info with a new character that represents the negative sign. 223 * <p> 224 * The character used to represent a negative number may vary by culture. 225 * This method specifies the character to use. 226 * 227 * @param negativeSign the character for the negative sign 228 * @return a copy with a new character that represents the negative sign, not null 229 */ 230 public DateTimeFormatSymbols withNegativeSign(char negativeSign) { 231 if (negativeSign == this.negativeSign) { 232 return this; 233 } 234 return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); 235 } 236 237 //----------------------------------------------------------------------- 238 /** 239 * Gets the character that represents the decimal point. 240 * <p> 241 * The character used to represent a decimal point may vary by culture. 242 * This method specifies the character to use. 243 * 244 * @return the character for the decimal point 245 */ 246 public char getDecimalSeparator() { 247 return decimalSeparator; 248 } 249 250 /** 251 * Returns a copy of the info with a new character that represents the decimal point. 252 * <p> 253 * The character used to represent a decimal point may vary by culture. 254 * This method specifies the character to use. 255 * 256 * @param decimalSeparator the character for the decimal point 257 * @return a copy with a new character that represents the decimal point, not null 258 */ 259 public DateTimeFormatSymbols withDecimalSeparator(char decimalSeparator) { 260 if (decimalSeparator == this.decimalSeparator) { 261 return this; 262 } 263 return new DateTimeFormatSymbols(zeroDigit, positiveSign, negativeSign, decimalSeparator); 264 } 265 266 //----------------------------------------------------------------------- 267 /** 268 * Checks whether the character is a digit, based on the currently set zero character. 269 * 270 * @param ch the character to check 271 * @return the value, 0 to 9, of the character, or -1 if not a digit 272 */ 273 int convertToDigit(char ch) { 274 int val = ch - zeroDigit; 275 return (val >= 0 && val <= 9) ? val : -1; 276 } 277 278 /** 279 * Converts the input numeric text to the internationalized form using the zero character. 280 * 281 * @param numericText the text, consisting of digits 0 to 9, to convert, not null 282 * @return the internationalized text, not null 283 */ 284 String convertNumberToI18N(String numericText) { 285 if (zeroDigit == '0') { 286 return numericText; 287 } 288 int diff = zeroDigit - '0'; 289 char[] array = numericText.toCharArray(); 290 for (int i = 0; i < array.length; i++) { 291 array[i] = (char) (array[i] + diff); 292 } 293 return new String(array); 294 } 295 296 //----------------------------------------------------------------------- 297 /** 298 * Checks if these symbols equal another set of symbols. 299 * 300 * @param obj the object to check, null returns false 301 * @return true if this is equal to the other date 302 */ 303 @Override 304 public boolean equals(Object obj) { 305 if (this == obj) { 306 return true; 307 } 308 if (obj instanceof DateTimeFormatSymbols) { 309 DateTimeFormatSymbols other = (DateTimeFormatSymbols) obj; 310 return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign && 311 negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator); 312 } 313 return false; 314 } 315 316 /** 317 * A hash code for these symbols. 318 * 319 * @return a suitable hash code 320 */ 321 @Override 322 public int hashCode() { 323 return zeroDigit + positiveSign + negativeSign + decimalSeparator; 324 } 325 326 //----------------------------------------------------------------------- 327 /** 328 * Returns a string describing these symbols. 329 * 330 * @return a string description, not null 331 */ 332 @Override 333 public String toString() { 334 return "Symbols[" + zeroDigit + positiveSign + negativeSign + decimalSeparator + "]"; 335 } 336 337}