001    /*
002     *  Copyright 2001-2005 Stephen Colebourne
003     *
004     *  Licensed under the Apache License, Version 2.0 (the "License");
005     *  you may not use this file except in compliance with the License.
006     *  You may obtain a copy of the License at
007     *
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     *
010     *  Unless required by applicable law or agreed to in writing, software
011     *  distributed under the License is distributed on an "AS IS" BASIS,
012     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     *  See the License for the specific language governing permissions and
014     *  limitations under the License.
015     */
016    package org.joda.time.chrono.gj;
017    
018    import java.util.Random;
019    
020    import junit.framework.TestCase;
021    
022    import org.joda.time.Chronology;
023    import org.joda.time.DateTime;
024    import org.joda.time.DateTimeField;
025    import org.joda.time.chrono.GregorianChronology;
026    import org.joda.time.chrono.JulianChronology;
027    
028    /**
029     * Tests either the Julian or Gregorian chronology from org.joda.time.chrono.gj
030     * against the implementations in this package. It tests all the date fields
031     * against their principal methods.
032     * <p>
033     * Randomly generated values are fed into the DateTimeField methods and the
034     * results are compared between the two chronologies. If any result doesn't
035     * match, an error report is generated and the program exits. Each time this
036     * test program is run, the pseudo random number generator is seeded with the
037     * same value. This ensures consistent results between test runs.
038     * <p>
039     * The main method accepts three optional arguments: iterations, mode, seed. By
040     * default, iterations is set to 1,000,000. The test will take several minutes
041     * to run, depending on the computer's performance. Every 5 seconds a progress
042     * message is printed.
043     * <p>
044     * The mode can be either 'g' for proleptic gregorian (the default) or 'j' for
045     * proleptic julian. To override the default random number generator seed, pass
046     * in a third argument which accepts a long signed integer.
047     *
048     * @author Brian S O'Neill
049     */
050    public class MainTest extends TestCase {
051        public static final int GREGORIAN_MODE = 0;
052        public static final int JULIAN_MODE = 1;
053    
054        private static final long MILLIS_PER_YEAR = (long)365.2425 * 24 * 60 * 60 * 1000;
055        private static final long _1000_YEARS = 1000 * MILLIS_PER_YEAR;
056        private static final long _500_YEARS = 500 * MILLIS_PER_YEAR;
057        private static final long MAX_MILLIS = (10000 - 1970) * MILLIS_PER_YEAR;
058        private static final long MIN_MILLIS = (-10000 - 1970) * MILLIS_PER_YEAR;
059    
060        // Show progess reports every 5 seconds.
061        private static final long UPDATE_INTERVAL = 5000;
062    
063        /**
064         * Arguments: iterations [mode [seed]]
065         */
066        public static void main(String[] args) throws Exception {
067            int iterations = 1000000;
068            int mode = GREGORIAN_MODE;
069            long seed = 1345435247779935L;
070    
071            if (args.length > 0) {
072                iterations = Integer.parseInt(args[0]);
073                if (args.length > 1) {
074                    if (args[1].startsWith("g")) {
075                        mode = GREGORIAN_MODE;
076                    } else if (args[1].startsWith("j")) {
077                        mode = JULIAN_MODE;
078                    } else {
079                        throw new IllegalArgumentException
080                            ("Unknown mode: " + args[1]);
081                    }
082                    if (args.length > 2) {
083                        seed = Long.parseLong(args[2]);
084                    }
085                }
086            }
087    
088            new MainTest(iterations, mode, seed).testChronology();
089        }
090    
091        //-----------------------------------------------------------------------
092        private final int iIterations;
093        private final int iMode;
094        private final long iSeed;
095        private final Chronology iTest;
096        private final Chronology iActual;
097    
098        /**
099         * @param iterations number of test iterations to perform
100         * @param mode GREGORIAN_MODE or JULIAN_MODE,0=Gregorian, 1=Julian
101         * @param seed seed for random number generator
102         */
103        public MainTest(int iterations, int mode, long seed) {
104            super("testChronology");
105            iIterations = iterations;
106            iMode = mode;
107            iSeed = seed;
108            if (mode == GREGORIAN_MODE) {
109                iTest = new TestGregorianChronology();
110                iActual = GregorianChronology.getInstanceUTC();
111            } else {
112                iTest = new TestJulianChronology();
113                iActual = JulianChronology.getInstanceUTC();
114            }
115        }
116    
117        //-----------------------------------------------------------------------
118        /**
119         * Main junit test
120         */
121        public void testChronology() {
122            int iterations = iIterations;
123            long seed = iSeed;
124            String modeStr;
125            if (iMode == GREGORIAN_MODE) {
126                modeStr = "Gregorian";
127            } else {
128                modeStr = "Julian";
129            }
130    
131            System.out.println("\nTesting " + modeStr + " chronology over " + iterations + " iterations");
132    
133            Random rnd = new Random(seed);
134            long updateMillis = System.currentTimeMillis() + UPDATE_INTERVAL;
135    
136            for (int i=0; i<iterations; i++) {
137                long now = System.currentTimeMillis();
138                if (now >= updateMillis) {
139                    updateMillis = now + UPDATE_INTERVAL;
140                    double complete = ((int)((double)i / iterations * 1000.0)) / 10d;
141                    if (complete < 100) {
142                        System.out.println("" + complete + "% complete (i=" + i + ")");
143                    }
144                }
145    
146                long millis = randomMillis(rnd);
147                int value = rnd.nextInt(200) - 100;
148                // millis2 is used for difference tests.
149                long millis2 = millis + rnd.nextLong() % _1000_YEARS - _500_YEARS;
150    
151                try {
152                    testFields(millis, value, millis2);
153                } catch (RuntimeException e) {
154                    System.out.println("Failure index: " + i);
155                    System.out.println("Test millis: " + millis);
156                    System.out.println("Test value: " + value);
157                    System.out.println("Test millis2: " + millis2);
158                    fail(e.getMessage());
159                }
160            }
161    
162            System.out.println("100% complete (i=" + iterations + ")");
163        }
164    
165        //-----------------------------------------------------------------------
166        private void testFields(long millis, int value, long millis2) {
167            testField(iTest.year(), iActual.year(), millis, value, millis2);
168            testField(iTest.monthOfYear(), iActual.monthOfYear(), millis, value, millis2);
169            testField(iTest.dayOfMonth(), iActual.dayOfMonth(), millis, value, millis2);
170            testField(iTest.weekyear(), iActual.weekyear(), millis, value, millis2);
171            testField(iTest.weekOfWeekyear(),
172                      iActual.weekOfWeekyear(), millis, value, millis2);
173            testField(iTest.dayOfWeek(), iActual.dayOfWeek(), millis, value, millis2);
174            testField(iTest.dayOfYear(), iActual.dayOfYear(), millis, value, millis2);
175        }
176    
177        private void testField(DateTimeField fieldA, DateTimeField fieldB, long millis,
178                               int value, long millis2)
179        {
180            int a, b;
181            long x, y;
182            boolean m, n;
183    
184            // get test
185            a = fieldA.get(millis);
186            b = fieldB.get(millis);
187            testValue(fieldA, fieldB, "get", millis, a, b);
188    
189            // getMaximumValue test
190            // Restrict this test to the fields that matter.
191            Class fieldClass = fieldA.getClass();
192            if (fieldClass == TestGJDayOfYearField.class ||
193                fieldClass == TestGJDayOfMonthField.class ||
194                fieldClass == TestGJWeekOfWeekyearField.class) {
195                
196                a = fieldA.getMaximumValue(millis);
197                b = fieldB.getMaximumValue(millis);
198                testValue(fieldA, fieldB, "getMaximumValue", millis, a, b);
199            }
200    
201            // set test
202            a = getWrappedValue
203                (value, fieldA.getMinimumValue(millis), fieldA.getMaximumValue(millis));
204            b = getWrappedValue
205                (value, fieldB.getMinimumValue(millis), fieldB.getMaximumValue(millis));
206            if (iMode == JULIAN_MODE && a == 0
207                && (fieldA.getName().equals("year") || fieldA.getName().equals("weekyear"))) {
208                // Exclude setting Julian year of zero.
209            } else {
210                x = fieldA.set(millis, a);
211                y = fieldB.set(millis, b);
212                testMillis(fieldA, fieldB, "set", millis, x, y, a, b);
213            }
214    
215            // roundFloor test
216            x = fieldA.roundFloor(millis);
217            y = fieldB.roundFloor(millis);
218            testMillis(fieldA, fieldB, "roundFloor", millis, x, y);
219    
220            // roundCeiling test
221            x = fieldA.roundCeiling(millis);
222            y = fieldB.roundCeiling(millis);
223            testMillis(fieldA, fieldB, "roundCeiling", millis, x, y);
224    
225            // roundHalfFloor test
226            x = fieldA.roundHalfFloor(millis);
227            y = fieldB.roundHalfFloor(millis);
228            testMillis(fieldA, fieldB, "roundHalfFloor", millis, x, y);
229    
230            // roundHalfEven test
231            x = fieldA.roundHalfEven(millis);
232            y = fieldB.roundHalfEven(millis);
233            testMillis(fieldA, fieldB, "roundHalfEven", millis, x, y);
234    
235            // remainder test
236            x = fieldA.remainder(millis);
237            y = fieldB.remainder(millis);
238            testMillis(fieldA, fieldB, "remainder", millis, x, y);
239    
240            // add test
241            x = fieldA.add(millis, value);
242            y = fieldB.add(millis, value);
243            testMillis(fieldA, fieldB, "add", millis, x, y);
244    
245            // addWrapField test
246            x = fieldA.addWrapField(millis, value);
247            y = fieldB.addWrapField(millis, value);
248            testMillis(fieldA, fieldB, "addWrapField", millis, x, y);
249    
250            // getDifference test
251            x = fieldA.getDifference(millis, millis2);
252            y = fieldB.getDifference(millis, millis2);
253            try {
254                testValue(fieldA, fieldB, "getDifference", millis, x, y);
255            } catch (RuntimeException e) {
256                System.out.println("Test datetime 2: " + makeDatetime(millis2));
257                throw e;
258            }
259    
260            // isLeap test
261            m = fieldA.isLeap(millis);
262            n = fieldB.isLeap(millis);
263            testBoolean(fieldA, fieldB, "isLeap", millis, m, n);
264    
265            // getLeapAmount test
266            a = fieldA.getLeapAmount(millis);
267            b = fieldB.getLeapAmount(millis);
268            testValue(fieldA, fieldB, "getLeapAmount", millis, a, b);
269        }
270    
271        private int getWrappedValue(int value, int minValue, int maxValue) {
272            if (minValue >= maxValue) {
273                throw new IllegalArgumentException("MIN > MAX");
274            }
275    
276            int wrapRange = maxValue - minValue + 1;
277            value -= minValue;
278    
279            if (value >= 0) {
280                return (value % wrapRange) + minValue;
281            }
282    
283            int remByRange = (-value) % wrapRange;
284    
285            if (remByRange == 0) {
286                return 0 + minValue;
287            }
288            return (wrapRange - remByRange) + minValue;
289        }
290    
291        private void testValue(DateTimeField fieldA, DateTimeField fieldB,
292                               String method, long millis, long valueA, long valueB) {
293            if (valueA != valueB) {
294                failValue(fieldA, fieldB, method, millis, valueA, valueB);
295            }
296        }
297    
298        private void testMillis(DateTimeField fieldA, DateTimeField fieldB,
299                                String method, long millis, long millisA, long millisB) {
300            if (millisA != millisB) {
301                failMillis(fieldA, fieldB, method, millis, millisA, millisB);
302            }
303        }
304    
305        private void testMillis(DateTimeField fieldA, DateTimeField fieldB,
306                                String method, long millis, long millisA, long millisB,
307                                int valueA, int valueB) {
308            if (millisA != millisB) {
309                failMillis(fieldA, fieldB, method, millis, millisA, millisB, valueA, valueB);
310            }
311        }
312    
313        private void testBoolean(DateTimeField fieldA, DateTimeField fieldB,
314                                 String method, long millis, boolean boolA, boolean boolB) {
315            if (boolA != boolB) {
316                failBoolean(fieldA, fieldB, method, millis, boolA, boolB);
317            }
318        }
319    
320        private void failValue(DateTimeField fieldA, DateTimeField fieldB,
321                               String method, long millis, long valueA, long valueB) {
322            System.out.println("Failure on " + makeName(fieldA, fieldB) + "." + method);
323            System.out.println(fieldA.getClass().getName() + "\n\tvs. "
324                               + fieldB.getClass().getName());
325            System.out.println("Datetime: " + makeDatetime(millis));
326            System.out.println("Millis from 1970: " + millis);
327            System.out.println(valueA + " != " + valueB);
328            throw new RuntimeException();
329        }
330    
331        private void failMillis(DateTimeField fieldA, DateTimeField fieldB,
332                                String method, long millis, long millisA, long millisB) {
333            System.out.println("Failure on " + makeName(fieldA, fieldB) + "." + method);
334            System.out.println(fieldA.getClass().getName() + "\n\tvs. "
335                               + fieldB.getClass().getName());
336            System.out.println("Datetime: " + makeDatetime(millis));
337            System.out.println("Millis from 1970: " + millis);
338            System.out.println(makeDatetime(millisA) + " != " + makeDatetime(millisB));
339            System.out.println(millisA + " != " + millisB);
340            System.out.println("Original value as reported by first field: " +
341                               fieldA.get(millis));
342            System.out.println("Original value as reported by second field: " +
343                               fieldB.get(millis));
344            System.out.println("First new value as reported by first field: " +
345                               fieldA.get(millisA));
346            System.out.println("First new value as reported by second field: " +
347                               fieldB.get(millisA));
348            System.out.println("Second new value as reported by first field: " +
349                               fieldA.get(millisB));
350            System.out.println("Second new value as reported by second field: " +
351                               fieldB.get(millisB));
352            throw new RuntimeException();
353        }
354    
355        private void failMillis(DateTimeField fieldA, DateTimeField fieldB,
356                                String method, long millis, long millisA, long millisB,
357                                int valueA, int valueB) {
358            System.out.println("Failure on " + makeName(fieldA, fieldB) + "." + method);
359            System.out.println(fieldA.getClass().getName() + "\n\tvs. "
360                               + fieldB.getClass().getName());
361            System.out.println("Datetime: " + makeDatetime(millis));
362            System.out.println("Millis from 1970: " + millis);
363            System.out.println(makeDatetime(millisA) + " != " + makeDatetime(millisB));
364            System.out.println(millisA + " != " + millisB);
365            System.out.println("Original value as reported by first field: " +
366                               fieldA.get(millis));
367            System.out.println("Original value as reported by second field: " +
368                               fieldB.get(millis));
369            System.out.println("First new value as reported by first field: " +
370                               fieldA.get(millisA));
371            System.out.println("First new value as reported by second field: " +
372                               fieldB.get(millisA));
373            System.out.println("Second new value as reported by first field: " +
374                               fieldA.get(millisB));
375            System.out.println("Second new value as reported by second field: " +
376                               fieldB.get(millisB));
377            System.out.println("Value to set for first field: " + valueA);
378            System.out.println("Value to set for second field: " + valueB);
379            throw new RuntimeException();
380        }
381    
382        private void failBoolean(DateTimeField fieldA, DateTimeField fieldB,
383                                 String method, long millis, boolean boolA, boolean boolB) {
384            System.out.println("Failure on " + makeName(fieldA, fieldB) + "." + method);
385            System.out.println(fieldA.getClass().getName() + "\n\tvs. "
386                               + fieldB.getClass().getName());
387            System.out.println("Datetime: " + makeDatetime(millis));
388            System.out.println("Millis from 1970: " + millis);
389            System.out.println(boolA + " != " + boolB);
390            throw new RuntimeException();
391        }
392    
393        private String makeName(DateTimeField fieldA, DateTimeField fieldB) {
394            if (fieldA.getName().equals(fieldB.getName())) {
395                return fieldA.getName();
396            } else {
397                return fieldA.getName() + "/" + fieldB.getName();
398            }
399        }
400    
401        private String makeDatetime(long millis) {
402            return makeDatetime(millis, iActual);
403        }
404    
405        private String makeDatetime(long millis, Chronology chrono) {
406            return chrono.dayOfWeek().getAsShortText(millis) + " "
407                + new DateTime(millis, chrono).toString() + " / " +
408                chrono.weekyear().get(millis) + "-W" + chrono.weekOfWeekyear().get(millis) +
409                "-" + chrono.dayOfWeek().get(millis);
410        }
411    
412        private String makeDate(long millis) {
413            return makeDate(millis, iActual);
414        }
415    
416        private String makeDate(long millis, Chronology chrono) {
417            return chrono.dayOfWeek().getAsShortText(millis) + " "
418                + new DateTime(millis, chrono).toString("yyyy-MM-dd") + " / " +
419                chrono.weekyear().get(millis) + "-W" + chrono.weekOfWeekyear().get(millis) +
420                "-" + chrono.dayOfWeek().get(millis);
421        }
422    
423        //-----------------------------------------------------------------------
424        private static long randomMillis(Random rnd) {
425            long millis = rnd.nextLong();
426            if (millis >= 0) {
427                millis = millis % MAX_MILLIS;
428            } else {
429                millis = millis % -MIN_MILLIS;
430            }
431            return millis;
432        }
433    
434        private static void dump(Chronology chrono, long millis) {
435            System.out.println("year:           " + chrono.year().get(millis));
436            System.out.println("monthOfYear:    " + chrono.monthOfYear().get(millis));
437            System.out.println("dayOfMonth:     " + chrono.dayOfMonth().get(millis));
438            System.out.println("weekyear:       " + chrono.weekyear().get(millis));
439            System.out.println("weekOfWeekyear: " + chrono.weekOfWeekyear().get(millis));
440            System.out.println("dayOfWeek:      " + chrono.dayOfWeek().get(millis));
441            System.out.println("dayOfYear:      " + chrono.dayOfYear().get(millis));
442        }
443    
444    }