1/* GENERATED SOURCE. DO NOT MODIFY. */
2// © 2016 and later: Unicode, Inc. and others.
3// License & terms of use: http://www.unicode.org/copyright.html#License
4/*
5 *******************************************************************************
6 * Copyright (C) 2001-2010, International Business Machines Corporation and    *
7 * others. All Rights Reserved.                                                *
8 *******************************************************************************
9 */
10
11/**
12 * Port From:   ICU4C v1.8.1 : format : DateFormatRoundTripTest
13 * Source File: $ICU4CRoot/source/test/intltest/dtfmtrtts.cpp
14 **/
15
16package android.icu.dev.test.format;
17
18import java.text.FieldPosition;
19import java.text.ParseException;
20import java.util.Date;
21import java.util.Locale;
22import java.util.Random;
23
24import org.junit.Test;
25
26import android.icu.text.DateFormat;
27import android.icu.text.SimpleDateFormat;
28import android.icu.util.Calendar;
29import android.icu.util.GregorianCalendar;
30import android.icu.util.TimeZone;
31
32/**
33 * Performs round-trip tests for DateFormat
34 **/
35public class DateFormatRoundTripTest extends android.icu.dev.test.TestFmwk {
36    public boolean INFINITE = false;
37    public boolean quick = true;
38    private SimpleDateFormat dateFormat;
39    private Calendar getFieldCal;
40    private int SPARSENESS = 18;
41    private int TRIALS = 4;
42    private int DEPTH = 5;
43    private Random ran;
44
45    // TODO: test is randomly failing depending on the randomly generated date
46    @Test
47    public void TestDateFormatRoundTrip() {
48        dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss.SSS zzz yyyy G");
49        getFieldCal = Calendar.getInstance();
50        ran = createRandom(); // use test framework's random seed
51
52        final Locale[] avail = DateFormat.getAvailableLocales();
53        int locCount = avail.length;
54        logln("DateFormat available locales: " + locCount);
55        if (quick) {
56            if (locCount > 5)
57                locCount = 5;
58            logln("Quick mode: only testing first 5 Locales");
59        }
60        TimeZone tz = TimeZone.getDefault();
61        logln("Default TimeZone:             " + tz.getID());
62
63        if (INFINITE) {
64            // Special infinite loop test mode for finding hard to reproduce errors
65            Locale loc = Locale.getDefault();
66            logln("ENTERING INFINITE TEST LOOP FOR Locale: " + loc.getDisplayName());
67            for (;;) {
68                _test(loc);
69            }
70        } else {
71            _test(Locale.getDefault());
72            for (int i = 0; i < locCount; ++i) {
73                _test(avail[i]);
74            }
75        }
76    }
77
78    private String styleName(int s) {
79        switch (s) {
80            case DateFormat.SHORT :
81                return "SHORT";
82            case DateFormat.MEDIUM :
83                return "MEDIUM";
84            case DateFormat.LONG :
85                return "LONG";
86            case DateFormat.FULL :
87                return "FULL";
88            default :
89                return "Unknown";
90        }
91    }
92
93    private void _test(Locale loc) {
94        if (!INFINITE) {
95            logln("Locale: " + loc.getDisplayName());
96        }
97        // Total possibilities = 24
98        //  4 date
99        //  4 time
100        //  16 date-time
101        boolean[] TEST_TABLE = new boolean[24];
102        int i = 0;
103        for (i = 0; i < 24; ++i)
104            TEST_TABLE[i] = true;
105
106        // If we have some sparseness, implement it here.  Sparseness decreases
107        // test time by eliminating some tests, up to 23.
108        for (i = 0; i < SPARSENESS; i++) {
109            int random = (int) (ran.nextDouble() * 24);
110            if (random >= 0 && random < 24 && TEST_TABLE[i]) {
111                TEST_TABLE[random] = false;
112            }
113        }
114
115        int itable = 0;
116        int style = 0;
117        for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
118            if (TEST_TABLE[itable++]) {
119                logln("Testing style " + styleName(style));
120                DateFormat df = DateFormat.getDateInstance(style, loc);
121                _test(df, false);
122            }
123        }
124
125        for (style = DateFormat.FULL; style <= DateFormat.SHORT; ++style) {
126            if (TEST_TABLE[itable++]) {
127                logln("Testing style " + styleName(style));
128                DateFormat  df = DateFormat.getTimeInstance(style, loc);
129                _test(df, true);
130            }
131        }
132
133        for (int dstyle = DateFormat.FULL; dstyle <= DateFormat.SHORT; ++dstyle) {
134            for (int tstyle = DateFormat.FULL; tstyle <= DateFormat.SHORT; ++tstyle) {
135                if (TEST_TABLE[itable++]) {
136                    logln("Testing dstyle " + styleName(dstyle) + ", tstyle " + styleName(tstyle));
137                    DateFormat df = DateFormat.getDateTimeInstance(dstyle, tstyle, loc);
138                    _test(df, false);
139                }
140            }
141        }
142    }
143
144    private void _test(DateFormat fmt, boolean timeOnly) {
145
146        if (!(fmt instanceof SimpleDateFormat)) {
147            errln("DateFormat wasn't a SimpleDateFormat");
148            return;
149        }
150
151        String pat = ((SimpleDateFormat) fmt).toPattern();
152        logln(pat);
153
154        // NOTE TO MAINTAINER
155        // This indexOf check into the pattern needs to be refined to ignore
156        // quoted characters.  Currently, this isn't a problem with the locale
157        // patterns we have, but it may be a problem later.
158
159        boolean hasEra = (pat.indexOf("G") != -1);
160        boolean hasZoneDisplayName = (pat.indexOf("z") != -1) || (pat.indexOf("v") != -1) || (pat.indexOf("V") != -1);
161        boolean hasTwoDigitYear = pat.indexOf("yy") >= 0 && pat.indexOf("yyy") < 0;
162
163        // Because patterns contain incomplete data representing the Date,
164        // we must be careful of how we do the roundtrip.  We start with
165        // a randomly generated Date because they're easier to generate.
166        // From this we get a string.  The string is our real starting point,
167        // because this string should parse the same way all the time.  Note
168        // that it will not necessarily parse back to the original date because
169        // of incompleteness in patterns.  For example, a time-only pattern won't
170        // parse back to the same date.
171
172        try {
173            for (int i = 0; i < TRIALS; ++i) {
174                Date[] d = new Date[DEPTH];
175                String[] s = new String[DEPTH];
176
177                d[0] = generateDate();
178
179                // We go through this loop until we achieve a match or until
180                // the maximum loop count is reached.  We record the points at
181                // which the date and the string starts to match.  Once matching
182                // starts, it should continue.
183                int loop;
184                int dmatch = 0; // d[dmatch].getTime() == d[dmatch-1].getTime()
185                int smatch = 0; // s[smatch].equals(s[smatch-1])
186                for (loop = 0; loop < DEPTH; ++loop) {
187                    if (loop > 0) {
188                        d[loop] = fmt.parse(s[loop - 1]);
189                    }
190
191                    s[loop] = fmt.format(d[loop]);
192
193                    if (loop > 0) {
194                        if (smatch == 0) {
195                            boolean match = s[loop].equals(s[loop - 1]);
196                            if (smatch == 0) {
197                                if (match)
198                                    smatch = loop;
199                            } else
200                                if (!match)
201                                    errln("FAIL: String mismatch after match");
202                        }
203
204                        if (dmatch == 0) {
205                            // {sfb} watch out here, this might not work
206                            boolean match = d[loop].getTime() == d[loop - 1].getTime();
207                            if (dmatch == 0) {
208                                if (match)
209                                    dmatch = loop;
210                            } else
211                                if (!match)
212                                    errln("FAIL: Date mismatch after match");
213                        }
214
215                        if (smatch != 0 && dmatch != 0)
216                            break;
217                    }
218                }
219                // At this point loop == DEPTH if we've failed, otherwise loop is the
220                // max(smatch, dmatch), that is, the index at which we have string and
221                // date matching.
222
223                // Date usually matches in 2.  Exceptions handled below.
224                int maxDmatch = 2;
225                int maxSmatch = 1;
226                if (dmatch > maxDmatch || smatch > maxSmatch) {
227                    //If the Date is BC
228                    if (!timeOnly && !hasEra && getField(d[0], Calendar.ERA) == GregorianCalendar.BC) {
229                        maxDmatch = 3;
230                        maxSmatch = 2;
231                    }
232                    if (hasZoneDisplayName &&
233                            (fmt.getTimeZone().inDaylightTime(d[0])
234                                    || fmt.getTimeZone().inDaylightTime(d[1])
235                                    || d[0].getTime() < 0L /* before 1970 */
236                                    || hasTwoDigitYear && d[1].getTime() < 0L
237                                       /* before 1970 as the result of 2-digit year parse */)) {
238                        maxSmatch = 2;
239                        if (timeOnly) {
240                            maxDmatch = 3;
241                        }
242                    }
243                }
244
245                if (dmatch > maxDmatch || smatch > maxSmatch) {
246                    SimpleDateFormat sdf = new SimpleDateFormat("EEEE, MMMM d, yyyy HH:mm:ss, z G", Locale.US);
247                    logln("Date = " + sdf.format(d[0]) + "; ms = " + d[0].getTime());
248                    logln("Dmatch: " + dmatch + " maxD: " + maxDmatch + " Smatch:" + smatch + " maxS:" + maxSmatch);
249                    for (int j = 0; j <= loop && j < DEPTH; ++j) {
250                        StringBuffer temp = new StringBuffer("");
251                        FieldPosition pos = new FieldPosition(0);
252                        logln((j > 0 ? " P> " : "    ") + dateFormat.format(d[j], temp, pos)
253                            + " F> " + s[j] + (j > 0 && d[j].getTime() == d[j - 1].getTime() ? " d==" : "")
254                            + (j > 0 && s[j].equals(s[j - 1]) ? " s==" : ""));
255                    }
256                    errln("Pattern: " + pat + " failed to match" + "; ms = " + d[0].getTime());
257                }
258            }
259        } catch (ParseException e) {
260            errln("Exception: " + e.getMessage());
261            logln(e.toString());
262        }
263    }
264
265    private int getField(Date d, int f) {
266        getFieldCal.setTime(d);
267        int ret = getFieldCal.get(f);
268        return ret;
269    }
270
271    private Date generateDate() {
272        double a = ran.nextDouble();
273        // Now 'a' ranges from 0..1; scale it to range from 0 to 8000 years
274        a *= 8000;
275        // Range from (4000-1970) BC to (8000-1970) AD
276        a -= 4000;
277        // Now scale up to ms
278        a *= 365.25 * 24 * 60 * 60 * 1000;
279        return new Date((long)a);
280    }
281}
282