1 /*
2  * hunt-time: A time library for D programming language.
3  *
4  * Copyright (C) 2015-2018 HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.time.format.DecimalStyle;
13 
14 import hunt.time.util.Common;
15 
16 // import hunt.text.DecimalFormatSymbols;
17 import hunt.collection.Collections;
18 import hunt.collection.HashSet;
19 import hunt.collection.Set;
20 import hunt.collection.HashMap;
21 import hunt.collection.Map;
22 import hunt.Exceptions;
23 import hunt.util.Locale;
24 // import hunt.concurrent.ConcurrentMap;
25 
26 /**
27  * Localized decimal style used _in date and time formatting.
28  * !(p)
29  * A significant part of dealing with dates and times is the localization.
30  * This class acts as a central point for accessing the information.
31  *
32  * @implSpec
33  * This class is immutable and thread-safe.
34  *
35  * @since 1.8
36  */
37 public final class DecimalStyle {
38 
39     /**
40      * The standard set of non-localized decimal style symbols.
41      * !(p)
42      * This uses standard ASCII characters for zero, positive, negative and a dot for the decimal point.
43      */
44     // public __gshared DecimalStyle STANDARD;
45     /**
46      * The cache of DecimalStyle instances.
47      */
48     // __gshared Map!(Locale, DecimalStyle) CACHE;
49 
50     /**
51      * The zero digit.
52      */
53     private  char zeroDigit;
54     /**
55      * The positive sign.
56      */
57     private  char positiveSign;
58     /**
59      * The negative sign.
60      */
61     private  char negativeSign;
62     /**
63      * The decimal separator.
64      */
65     private  char decimalSeparator;
66 
67     // shared static this()
68     // {
69         // STANDARD = new DecimalStyle('0', '+', '-', '.');
70         mixin(MakeGlobalVar!(DecimalStyle)("STANDARD",`new DecimalStyle('0', '+', '-', '.')`));
71         // CACHE = new HashMap!(Locale, DecimalStyle)(16, 0.75f/* , 2 */);
72         mixin(MakeGlobalVar!(Map!(Locale, DecimalStyle))("CACHE",`new HashMap!(Locale, DecimalStyle)(16, 0.75f/* , 2 */)`));
73     // }
74     //-----------------------------------------------------------------------
75     /**
76      * Lists all the locales that are supported.
77      * !(p)
78      * The locale 'en_US' will always be present.
79      *
80      * @return a Set of Locales for which localization is supported
81      */
82      ///@gxc
83     // public static Set!(Locale) getAvailableLocales() {
84     //     Locale[] l = DecimalFormatSymbols.getAvailableLocales();
85     //     Set!(Locale) locales = new HashSet!(Locale)(l.length);
86     //     foreach(d ; l) {
87     //         locales.add(d);
88     //     }
89     //     // Collections.addAll(locales, l);
90     //     return locales;
91     // }
92 
93     /**
94      * Obtains the DecimalStyle for the default
95      * {@link java.util.Locale.Category#FORMAT FORMAT} locale.
96      * !(p)
97      * This method provides access to locale sensitive decimal style symbols.
98      * !(p)
99      * This is equivalent to calling
100      * {@link #of(Locale)
101      *     of(Locale.getDefault(Locale.Category.FORMAT))}.
102      *
103      * @see java.util.Locale.Category#FORMAT
104      * @return the decimal style, not null
105      */
106      ///@gxc
107     // public static DecimalStyle ofDefaultLocale() {
108     //     return of(Locale.getDefault(Locale.Category.FORMAT));
109     // }
110 
111     /**
112      * Obtains the DecimalStyle for the specified locale.
113      * !(p)
114      * This method provides access to locale sensitive decimal style symbols.
115      * If the locale contains "nu" (Numbering System) and/or "rg"
116      * (Region Override) <a href="../../util/Locale.html#def_locale_extension">
117      * Unicode extensions</a>, returned instance will reflect the values specified with
118      * those extensions. If both "nu" and "rg" are specified, the value from
119      * the "nu" extension supersedes the implicit one from the "rg" extension.
120      *
121      * @param locale  the locale, not null
122      * @return the decimal style, not null
123      */
124     public static DecimalStyle of(Locale locale) {
125         assert(locale, "locale");
126         DecimalStyle info = CACHE.get(locale);
127         if (info is null) {
128             info = create(locale);
129             CACHE.putIfAbsent(locale, info);
130             info = CACHE.get(locale);
131         }
132         return info;
133     }
134 
135     private static DecimalStyle create(Locale locale) {
136         // DecimalFormatSymbols oldSymbols = DecimalFormatSymbols.getInstance(locale);
137         // char zeroDigit = oldSymbols.getZeroDigit();
138         // char positiveSign = '+';
139         // char negativeSign = oldSymbols.getMinusSign();
140         // char decimalSeparator = oldSymbols.getDecimalSeparator();
141         // if (zeroDigit == '0' && negativeSign == '-' && decimalSeparator == '.') {
142         //     return STANDARD;
143         // }
144         // return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
145         implementationMissing();
146         return null;
147     }
148 
149     //-----------------------------------------------------------------------
150     /**
151      * Restricted constructor.
152      *
153      * @param zeroChar  the character to use for the digit of zero
154      * @param positiveSignChar  the character to use for the positive sign
155      * @param negativeSignChar  the character to use for the negative sign
156      * @param decimalPointChar  the character to use for the decimal point
157      */
158      this(char zeroChar, char positiveSignChar, char negativeSignChar, char decimalPointChar) {
159         this.zeroDigit = zeroChar;
160         this.positiveSign = positiveSignChar;
161         this.negativeSign = negativeSignChar;
162         this.decimalSeparator = decimalPointChar;
163     }
164 
165     //-----------------------------------------------------------------------
166     /**
167      * Gets the character that represents zero.
168      * !(p)
169      * The character used to represent digits may vary by culture.
170      * This method specifies the zero character to use, which implies the characters for one to nine.
171      *
172      * @return the character for zero
173      */
174     public char getZeroDigit() {
175         return zeroDigit;
176     }
177 
178     /**
179      * Returns a copy of the info with a new character that represents zero.
180      * !(p)
181      * The character used to represent digits may vary by culture.
182      * This method specifies the zero character to use, which implies the characters for one to nine.
183      *
184      * @param zeroDigit  the character for zero
185      * @return  a copy with a new character that represents zero, not null
186      */
187     public DecimalStyle withZeroDigit(char zeroDigit) {
188         if (zeroDigit == this.zeroDigit) {
189             return this;
190         }
191         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
192     }
193 
194     //-----------------------------------------------------------------------
195     /**
196      * Gets the character that represents the positive sign.
197      * !(p)
198      * The character used to represent a positive number may vary by culture.
199      * This method specifies the character to use.
200      *
201      * @return the character for the positive sign
202      */
203     public char getPositiveSign() {
204         return positiveSign;
205     }
206 
207     /**
208      * Returns a copy of the info with a new character that represents the positive sign.
209      * !(p)
210      * The character used to represent a positive number may vary by culture.
211      * This method specifies the character to use.
212      *
213      * @param positiveSign  the character for the positive sign
214      * @return  a copy with a new character that represents the positive sign, not null
215      */
216     public DecimalStyle withPositiveSign(char positiveSign) {
217         if (positiveSign == this.positiveSign) {
218             return this;
219         }
220         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
221     }
222 
223     //-----------------------------------------------------------------------
224     /**
225      * Gets the character that represents the negative sign.
226      * !(p)
227      * The character used to represent a negative number may vary by culture.
228      * This method specifies the character to use.
229      *
230      * @return the character for the negative sign
231      */
232     public char getNegativeSign() {
233         return negativeSign;
234     }
235 
236     /**
237      * Returns a copy of the info with a new character that represents the negative sign.
238      * !(p)
239      * The character used to represent a negative number may vary by culture.
240      * This method specifies the character to use.
241      *
242      * @param negativeSign  the character for the negative sign
243      * @return  a copy with a new character that represents the negative sign, not null
244      */
245     public DecimalStyle withNegativeSign(char negativeSign) {
246         if (negativeSign == this.negativeSign) {
247             return this;
248         }
249         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
250     }
251 
252     //-----------------------------------------------------------------------
253     /**
254      * Gets the character that represents the decimal point.
255      * !(p)
256      * The character used to represent a decimal point may vary by culture.
257      * This method specifies the character to use.
258      *
259      * @return the character for the decimal point
260      */
261     public char getDecimalSeparator() {
262         return decimalSeparator;
263     }
264 
265     /**
266      * Returns a copy of the info with a new character that represents the decimal point.
267      * !(p)
268      * The character used to represent a decimal point may vary by culture.
269      * This method specifies the character to use.
270      *
271      * @param decimalSeparator  the character for the decimal point
272      * @return  a copy with a new character that represents the decimal point, not null
273      */
274     public DecimalStyle withDecimalSeparator(char decimalSeparator) {
275         if (decimalSeparator == this.decimalSeparator) {
276             return this;
277         }
278         return new DecimalStyle(zeroDigit, positiveSign, negativeSign, decimalSeparator);
279     }
280 
281     //-----------------------------------------------------------------------
282     /**
283      * Checks whether the character is a digit, based on the currently set zero character.
284      *
285      * @param ch  the character to check
286      * @return the value, 0 to 9, of the character, or -1 if not a digit
287      */
288     int convertToDigit(char ch) {
289         int val = ch - zeroDigit;
290         return (val >= 0 && val <= 9) ? val : -1;
291     }
292 
293     /**
294      * Converts the input numeric text to the internationalized form using the zero character.
295      *
296      * @param numericText  the text, consisting of digits 0 to 9, to convert, not null
297      * @return the internationalized text, not null
298      */
299     string convertNumberToI18N(string numericText) {
300         if (zeroDigit == '0') {
301             return numericText;
302         }
303         int diff = zeroDigit - '0';
304         char[] array = cast(char[])numericText/* .toCharArray() */;
305         for (int i = 0; i < array.length; i++) {
306             array[i] = cast(char) (array[i] + diff);
307         }
308         return cast(string)(array);
309     }
310 
311     //-----------------------------------------------------------------------
312     /**
313      * Checks if this DecimalStyle is equal to another DecimalStyle.
314      *
315      * @param obj  the object to check, null returns false
316      * @return true if this is equal to the other date
317      */
318     override
319     public bool opEquals(Object obj) {
320         if (this is obj) {
321             return true;
322         }
323         if (cast(DecimalStyle)(obj) !is null) {
324             DecimalStyle other = cast(DecimalStyle) obj;
325             return (zeroDigit == other.zeroDigit && positiveSign == other.positiveSign &&
326                     negativeSign == other.negativeSign && decimalSeparator == other.decimalSeparator);
327         }
328         return false;
329     }
330 
331     /**
332      * A hash code for this DecimalStyle.
333      *
334      * @return a suitable hash code
335      */
336     override
337     public size_t toHash() @trusted nothrow {
338         return zeroDigit + positiveSign + negativeSign + decimalSeparator;
339     }
340 
341     //-----------------------------------------------------------------------
342     /**
343      * Returns a string describing this DecimalStyle.
344      *
345      * @return a string description, not null
346      */
347     override
348     public string toString() {
349         return "DecimalStyle[" ~ zeroDigit ~ positiveSign ~ negativeSign ~ decimalSeparator ~ "]";
350     }
351 
352 }