1 /*
2  * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 
24 /*
25  * This file is available under and governed by the GNU General Public
26  * License version 2 only, as published by the Free Software Foundation.
27  * However, the following notice accompanied the original version of this
28  * file:
29  *
30  * Copyright (c) 2009-2012, Stephen Colebourne & Michael Nascimento Santos
31  *
32  * All rights reserved.
33  *
34  * Redistribution and use in source and binary forms, with or without
35  * modification, are permitted provided that the following conditions are met:
36  *
37  *  * Redistributions of source code must retain the above copyright notice,
38  *    this list of conditions and the following disclaimer.
39  *
40  *  * Redistributions in binary form must reproduce the above copyright notice,
41  *    this list of conditions and the following disclaimer in the documentation
42  *    and/or other materials provided with the distribution.
43  *
44  *  * Neither the name of JSR-310 nor the names of its contributors
45  *    may be used to endorse or promote products derived from this software
46  *    without specific prior written permission.
47  *
48  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
49  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
50  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
51  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
52  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
53  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
54  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
55  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
56  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
57  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
58  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
59  */
60 
61 /*
62  * @test
63  * @modules jdk.localedata
64  */
65 package test.java.time.format;
66 
67 import android.icu.util.VersionInfo;
68 import java.time.chrono.ChronoLocalDate;
69 import java.time.chrono.Chronology;
70 import java.time.chrono.IsoChronology;
71 import java.time.chrono.JapaneseChronology;
72 import java.time.chrono.JapaneseEra;
73 import java.time.chrono.MinguoChronology;
74 import java.time.chrono.ThaiBuddhistChronology;
75 import java.time.format.DateTimeFormatter;
76 import java.time.format.DateTimeFormatterBuilder;
77 import java.time.format.FormatStyle;
78 import java.time.format.ResolverStyle;
79 import java.time.LocalDate;
80 import java.time.temporal.ChronoField;
81 import java.time.temporal.Temporal;
82 import java.time.temporal.TemporalAccessor;
83 
84 import java.util.HashMap;
85 import java.util.Locale;
86 import java.util.Map;
87 
88 import static org.testng.Assert.assertEquals;
89 
90 import org.testng.annotations.BeforeMethod;
91 import org.testng.annotations.DataProvider;
92 import org.testng.annotations.Test;
93 
94 /**
95  * Test DateTimeFormatterBuilder.
96  */
97 @Test
98 public class TestDateTimeFormatterBuilderWithLocale {
99 
100     private DateTimeFormatterBuilder builder;
101 
102     @BeforeMethod
setUp()103     public void setUp() {
104         builder = new DateTimeFormatterBuilder();
105     }
106 
107     //-----------------------------------------------------------------------
108     @DataProvider(name="patternPrint")
data_patternPrint()109     Object[][] data_patternPrint() {
110         return new Object[][] {
111             {"Q", date(2012, 2, 10), "1"},
112             {"QQ", date(2012, 2, 10), "01"},
113             {"QQQ", date(2012, 2, 10), "Q1"},
114             {"QQQQ", date(2012, 2, 10), "1st quarter"},
115             {"QQQQQ", date(2012, 2, 10), "1"},
116         };
117     }
118 
119     @Test(dataProvider="patternPrint")
test_appendPattern_patternPrint(String input, Temporal temporal, String expected)120     public void test_appendPattern_patternPrint(String input, Temporal temporal, String expected) throws Exception {
121         DateTimeFormatter f = builder.appendPattern(input).toFormatter(Locale.UK);
122         String test = f.format(temporal);
123         assertEquals(test, expected);
124     }
125 
126     //-----------------------------------------------------------------------
127     @DataProvider(name="mapTextLookup")
data_mapTextLookup()128     Object[][] data_mapTextLookup() {
129         return new Object[][] {
130             {IsoChronology.INSTANCE.date(1, 1, 1), Locale.ENGLISH},
131             {JapaneseChronology.INSTANCE.date(JapaneseEra.HEISEI, 1, 1, 8), Locale.ENGLISH},
132             {MinguoChronology.INSTANCE.date(1, 1, 1), Locale.ENGLISH},
133             {ThaiBuddhistChronology.INSTANCE.date(1, 1, 1), Locale.ENGLISH},
134         };
135     }
136 
137     @Test(dataProvider="mapTextLookup")
test_appendText_mapTextLookup(ChronoLocalDate date, Locale locale)138     public void test_appendText_mapTextLookup(ChronoLocalDate date, Locale locale) {
139         final String firstYear = "firstYear";
140         final String firstMonth = "firstMonth";
141         final String firstYearMonth = firstYear + firstMonth;
142         final long first = 1L;
143 
144         DateTimeFormatter formatter = builder
145             .appendText(ChronoField.YEAR_OF_ERA, Map.of(first, firstYear))
146             .appendText(ChronoField.MONTH_OF_YEAR, Map.of(first, firstMonth))
147             .toFormatter(locale)
148             .withResolverStyle(ResolverStyle.STRICT);
149 
150         assertEquals(date.format(formatter), firstYearMonth);
151 
152         TemporalAccessor ta = formatter.parse(firstYearMonth);
153         assertEquals(ta.getLong(ChronoField.YEAR_OF_ERA), first);
154         assertEquals(ta.getLong(ChronoField.MONTH_OF_YEAR), first);
155     }
156 
157 
158     //-----------------------------------------------------------------------
159     @DataProvider(name="localePatterns")
localizedDateTimePatterns()160     Object[][] localizedDateTimePatterns() {
161         // Android-changed: Adapt for changes since old CLDR version this tests were written for.
162         return new Object[][] {
163             // French Locale and ISO Chronology
164             {FormatStyle.FULL, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM y '\u00e0' HH:mm:ss zzzz"},
165             {FormatStyle.LONG, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM y '\u00e0' HH:mm:ss z"},
166             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM y, HH:mm:ss"},
167             {FormatStyle.SHORT, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/y HH:mm"},
168             {FormatStyle.FULL, null, IsoChronology.INSTANCE, Locale.FRENCH, "EEEE d MMMM y"},
169             {FormatStyle.LONG, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMMM y"},
170             {FormatStyle.MEDIUM, null, IsoChronology.INSTANCE, Locale.FRENCH, "d MMM y"},
171             {FormatStyle.SHORT, null, IsoChronology.INSTANCE, Locale.FRENCH, "dd/MM/y"},
172             {null, FormatStyle.FULL, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss zzzz"},
173             {null, FormatStyle.LONG, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss z"},
174             {null, FormatStyle.MEDIUM, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm:ss"},
175             {null, FormatStyle.SHORT, IsoChronology.INSTANCE, Locale.FRENCH, "HH:mm"},
176 
177             // Japanese Locale and JapaneseChronology
178             {FormatStyle.FULL, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5EEEE H\u6642mm\u5206ss\u79d2 zzzz"},
179             {FormatStyle.LONG, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5 H:mm:ss z"},
180             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5 H:mm:ss"},
181             {FormatStyle.SHORT, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy/M/d H:mm"},
182             {FormatStyle.FULL, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5EEEE"},
183             {FormatStyle.LONG, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5"},
184             {FormatStyle.MEDIUM, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "Gy\u5e74M\u6708d\u65e5"},
185             {FormatStyle.SHORT, null, JapaneseChronology.INSTANCE, Locale.JAPANESE, "GGGGGy/M/d"},
186             {null, FormatStyle.FULL, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H\u6642mm\u5206ss\u79d2 zzzz"},
187             {null, FormatStyle.LONG, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss z"},
188             {null, FormatStyle.MEDIUM, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm:ss"},
189             {null, FormatStyle.SHORT, JapaneseChronology.INSTANCE, Locale.JAPANESE, "H:mm"},
190 
191             // Chinese Local and Chronology
192             // Android-changed: Since ICU 70, use 24-hour time format
193             // {FormatStyle.FULL, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE zzzz ah:mm:ss"},
194             // {FormatStyle.LONG, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 z ah:mm:ss"},
195             // {FormatStyle.MEDIUM, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 ah:mm:ss"},
196             {FormatStyle.FULL, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE zzzz HH:mm:ss"},
197             {FormatStyle.LONG, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 z HH:mm:ss"},
198             {FormatStyle.MEDIUM, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5 HH:mm:ss"},
199             // Android-changed: Since ICU 70, use 24-hour time format
200             // {FormatStyle.SHORT, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy/M/d ah:mm"},
201             {FormatStyle.SHORT, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy/M/d HH:mm"},
202             {FormatStyle.FULL, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5EEEE"},
203             {FormatStyle.LONG, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5"},
204             {FormatStyle.MEDIUM, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy\u5e74M\u6708d\u65e5"},
205             // Android-changed: Since ICU 68, use single 'y' to represent year in short form like other format styles
206             // {FormatStyle.SHORT, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gyy/M/d"},
207             // {null, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "zzzz ah:mm:ss"},
208             // {null, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "z ah:mm:ss"},
209             // {null, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "ah:mm:ss"},
210             // {null, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "ah:mm"},
211             {FormatStyle.SHORT, null, MinguoChronology.INSTANCE, Locale.CHINESE, "Gy/M/d"},
212             {null, FormatStyle.FULL, MinguoChronology.INSTANCE, Locale.CHINESE, "zzzz HH:mm:ss"},
213             {null, FormatStyle.LONG, MinguoChronology.INSTANCE, Locale.CHINESE, "z HH:mm:ss"},
214             {null, FormatStyle.MEDIUM, MinguoChronology.INSTANCE, Locale.CHINESE, "HH:mm:ss"},
215             {null, FormatStyle.SHORT, MinguoChronology.INSTANCE, Locale.CHINESE, "HH:mm"},
216         };
217     }
218 
219     @Test(dataProvider="localePatterns")
test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle, Chronology chrono, Locale locale, String expected)220     public void test_getLocalizedDateTimePattern(FormatStyle dateStyle, FormatStyle timeStyle,
221             Chronology chrono, Locale locale, String expected) {
222         // Android-added: The ICU data is different before ICU version 70. http://b/229960530
223         if (VersionInfo.ICU_VERSION.getMajor() < 70) {
224             return;
225         }
226         String actual = DateTimeFormatterBuilder.getLocalizedDateTimePattern(dateStyle, timeStyle, chrono, locale);
227         assertEquals(actual, expected, "Pattern " + convertNonAscii(actual));
228     }
229 
230     /**
231      * Returns a string that includes non-ascii characters after expanding
232      * the non-ascii characters to their Java language \\uxxxx form.
233      * @param input an input string
234      * @return the encoded string.
235      */
convertNonAscii(String input)236     private String convertNonAscii(String input) {
237         StringBuilder sb = new StringBuilder(input.length() * 6);
238         for (int i = 0; i < input.length(); i++) {
239             char ch = input.charAt(i);
240             if (ch < 255) {
241                 sb.append(ch);
242             } else {
243                 sb.append("\\u");
244                 sb.append(Integer.toHexString(ch));
245             }
246         }
247         return sb.toString();
248     }
249 
date(int y, int m, int d)250     private static Temporal date(int y, int m, int d) {
251         return LocalDate.of(y, m, d);
252     }
253 }
254