1/*
2 *******************************************************************************
3 * Copyright (C) 1996-2010, International Business Machines Corporation and    *
4 * others. All Rights Reserved.                                                *
5 *******************************************************************************
6 */
7package com.ibm.icu.dev.test.calendar;
8
9// AstroTest
10
11import java.util.Date;
12import java.util.Locale;
13
14import com.ibm.icu.dev.test.TestFmwk;
15import com.ibm.icu.impl.CalendarAstronomer;
16import com.ibm.icu.impl.CalendarAstronomer.Ecliptic;
17import com.ibm.icu.impl.CalendarAstronomer.Equatorial;
18import com.ibm.icu.text.DateFormat;
19import com.ibm.icu.util.Calendar;
20import com.ibm.icu.util.GregorianCalendar;
21import com.ibm.icu.util.SimpleTimeZone;
22import com.ibm.icu.util.TimeZone;
23
24// TODO: try finding next new moon after  07/28/1984 16:00 GMT
25
26public class AstroTest extends TestFmwk {
27    public static void main(String[] args) throws Exception {
28        new AstroTest().run(args);
29    }
30
31    static final double PI = Math.PI;
32
33    public void TestSolarLongitude() {
34        GregorianCalendar gc = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
35        CalendarAstronomer astro = new CalendarAstronomer();
36        // year, month, day, hour, minute, longitude (radians), ascension(radians), declination(radians)
37        final double tests[][] = {
38            { 1980, 7, 27, 00, 00, 2.166442986535465, 2.2070499713207730, 0.3355704075759270 },
39            { 1988, 7, 27, 00, 00, 2.167484927693959, 2.2081183335606176, 0.3353093444275315 },
40        };
41        logln("");
42        for (int i = 0; i < tests.length; i++) {
43            gc.clear();
44            gc.set((int)tests[i][0], (int)tests[i][1]-1, (int)tests[i][2], (int)tests[i][3], (int) tests[i][4]);
45
46            astro.setDate(gc.getTime());
47
48            double longitude = astro.getSunLongitude();
49            if (longitude != tests[i][5]) {
50                if ((float)longitude == (float)tests[i][5]) {
51                    logln("longitude(" + longitude +
52                            ") !=  tests[i][5](" + tests[i][5] +
53                            ") in double for test " + i);
54                } else {
55                    errln("FAIL: longitude(" + longitude +
56                            ") !=  tests[i][5](" + tests[i][5] +
57                            ") for test " + i);
58                }
59            }
60            Equatorial result = astro.getSunPosition();
61            if (result.ascension != tests[i][6]) {
62                if ((float)result.ascension == (float)tests[i][6]) {
63                    logln("result.ascension(" + result.ascension +
64                            ") !=  tests[i][6](" + tests[i][6] +
65                            ") in double for test " + i);
66                } else {
67                    errln("FAIL: result.ascension(" + result.ascension +
68                            ") !=  tests[i][6](" + tests[i][6] +
69                            ") for test " + i);
70                }
71            }
72            if (result.declination != tests[i][7]) {
73                if ((float)result.declination == (float)tests[i][7]) {
74                    logln("result.declination(" + result.declination +
75                            ") !=  tests[i][7](" + tests[i][7] +
76                            ") in double for test " + i);
77                } else {
78                    errln("FAIL: result.declination(" + result.declination +
79                            ") !=  tests[i][7](" + tests[i][7] +
80                            ") for test " + i);
81                }
82            }
83        }
84    }
85
86    public void TestLunarPosition() {
87        GregorianCalendar gc = new GregorianCalendar(new SimpleTimeZone(0, "UTC"));
88        CalendarAstronomer astro = new CalendarAstronomer();
89        // year, month, day, hour, minute, ascension(radians), declination(radians)
90        final double tests[][] = {
91            { 1979, 2, 26, 16, 00, -0.3778379118188744, -0.1399698825594198 },
92        };
93        logln("");
94
95        for (int i = 0; i < tests.length; i++) {
96            gc.clear();
97            gc.set((int)tests[i][0], (int)tests[i][1]-1, (int)tests[i][2], (int)tests[i][3], (int) tests[i][4]);
98            astro.setDate(gc.getTime());
99
100            Equatorial result = astro.getMoonPosition();
101            if (result.ascension != tests[i][5]) {
102                if ((float)result.ascension == (float)tests[i][5]) {
103                    logln("result.ascension(" + result.ascension +
104                            ") !=  tests[i][5](" + tests[i][5] +
105                            ") in double for test " + i);
106                } else {
107                    errln("FAIL: result.ascension(" + result.ascension +
108                            ") !=  tests[i][5](" + tests[i][5] +
109                            ") for test " + i);
110                }
111            }
112            if (result.declination != tests[i][6]) {
113                if ((float)result.declination == (float)tests[i][6]) {
114                    logln("result.declination(" + result.declination +
115                            ") !=  tests[i][6](" + tests[i][6] +
116                            ") in double for test " + i);
117                } else {
118                    errln("FAIL: result.declination(" + result.declination +
119                            ") !=  tests[i][6](" + tests[i][6] +
120                            ") for test " + i);
121                }
122            }
123        }
124    }
125
126    public void TestCoordinates() {
127        CalendarAstronomer astro = new CalendarAstronomer();
128        Equatorial result = astro.eclipticToEquatorial(139.686111 * PI/ 180.0, 4.875278* PI / 180.0);
129        logln("result is " + result + ";  " + result.toHmsString());
130    }
131
132    public void TestCoverage() {
133        GregorianCalendar cal = new GregorianCalendar(1958, Calendar.AUGUST, 15);
134        Date then = cal.getTime();
135        CalendarAstronomer myastro = new CalendarAstronomer(then);
136
137        //Latitude:  34 degrees 05' North
138        //Longitude:  118 degrees 22' West
139        double laLat = 34 + 5d/60, laLong = 360 - (118 + 22d/60);
140        CalendarAstronomer myastro2 = new CalendarAstronomer(laLong, laLat);
141
142        double eclLat = laLat * Math.PI / 360;
143        double eclLong = laLong * Math.PI / 360;
144        Ecliptic ecl = new Ecliptic(eclLat, eclLong);
145        logln("ecliptic: " + ecl);
146
147        CalendarAstronomer myastro3 = new CalendarAstronomer();
148        myastro3.setJulianDay((4713 + 2000) * 365.25);
149
150        CalendarAstronomer[] astronomers = {
151            myastro, myastro2, myastro3, myastro2 // check cache
152
153        };
154
155        for (int i = 0; i < astronomers.length; ++i) {
156            CalendarAstronomer astro = astronomers[i];
157
158            logln("astro: " + astro);
159            logln("   time: " + astro.getTime());
160            logln("   date: " + astro.getDate());
161            logln("   cent: " + astro.getJulianCentury());
162            logln("   gw sidereal: " + astro.getGreenwichSidereal());
163            logln("   loc sidereal: " + astro.getLocalSidereal());
164            logln("   equ ecl: " + astro.eclipticToEquatorial(ecl));
165            logln("   equ long: " + astro.eclipticToEquatorial(eclLong));
166            logln("   horiz: " + astro.eclipticToHorizon(eclLong));
167            logln("   sunrise: " + new Date(astro.getSunRiseSet(true)));
168            logln("   sunset: " + new Date(astro.getSunRiseSet(false)));
169            logln("   moon phase: " + astro.getMoonPhase());
170            logln("   moonrise: " + new Date(astro.getMoonRiseSet(true)));
171            logln("   moonset: " + new Date(astro.getMoonRiseSet(false)));
172            logln("   prev summer solstice: " + new Date(astro.getSunTime(CalendarAstronomer.SUMMER_SOLSTICE, false)));
173            logln("   next summer solstice: " + new Date(astro.getSunTime(CalendarAstronomer.SUMMER_SOLSTICE, true)));
174            logln("   prev full moon: " + new Date(astro.getMoonTime(CalendarAstronomer.FULL_MOON, false)));
175            logln("   next full moon: " + new Date(astro.getMoonTime(CalendarAstronomer.FULL_MOON, true)));
176        }
177
178    }
179
180    static final long DAY_MS = 24*60*60*1000L;
181
182    public void TestSunriseTimes() {
183
184        //        logln("Sunrise/Sunset times for San Jose, California, USA");
185        //        CalendarAstronomer astro = new CalendarAstronomer(-121.55, 37.20);
186        //        TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles");
187
188        // We'll use a table generated by the UNSO website as our reference
189        // From: http://aa.usno.navy.mil/
190        //-Location: W079 25, N43 40
191        //-Rise and Set for the Sun for 2001
192        //-Zone:  4h West of Greenwich
193        int[] USNO = {
194             6,59, 19,45,
195             6,57, 19,46,
196             6,56, 19,47,
197             6,54, 19,48,
198             6,52, 19,49,
199             6,50, 19,51,
200             6,48, 19,52,
201             6,47, 19,53,
202             6,45, 19,54,
203             6,43, 19,55,
204             6,42, 19,57,
205             6,40, 19,58,
206             6,38, 19,59,
207             6,36, 20, 0,
208             6,35, 20, 1,
209             6,33, 20, 3,
210             6,31, 20, 4,
211             6,30, 20, 5,
212             6,28, 20, 6,
213             6,27, 20, 7,
214             6,25, 20, 8,
215             6,23, 20,10,
216             6,22, 20,11,
217             6,20, 20,12,
218             6,19, 20,13,
219             6,17, 20,14,
220             6,16, 20,16,
221             6,14, 20,17,
222             6,13, 20,18,
223             6,11, 20,19,
224        };
225
226        logln("Sunrise/Sunset times for Toronto, Canada");
227        CalendarAstronomer astro = new CalendarAstronomer(-(79+25/60), 43+40/60);
228
229        // As of ICU4J 2.8 the ICU4J time zones implement pass-through
230        // to the underlying JDK.  Because of variation in the
231        // underlying JDKs, we have to use a fixed-offset
232        // SimpleTimeZone to get consistent behavior between JDKs.
233        // The offset we want is [-18000000, 3600000] (raw, dst).
234        // [aliu 10/15/03]
235
236        // TimeZone tz = TimeZone.getTimeZone("America/Montreal");
237        TimeZone tz = new SimpleTimeZone(-18000000 + 3600000, "Montreal(FIXED)");
238
239        GregorianCalendar cal = new GregorianCalendar(tz, Locale.US);
240        GregorianCalendar cal2 = new GregorianCalendar(tz, Locale.US);
241        cal.clear();
242        cal.set(Calendar.YEAR, 2001);
243        cal.set(Calendar.MONTH, Calendar.APRIL);
244        cal.set(Calendar.DAY_OF_MONTH, 1);
245        cal.set(Calendar.HOUR_OF_DAY, 12); // must be near local noon for getSunRiseSet to work
246
247        DateFormat df = DateFormat.getTimeInstance(cal, DateFormat.MEDIUM, Locale.US);
248        DateFormat df2 = DateFormat.getDateTimeInstance(cal, DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.US);
249        DateFormat day = DateFormat.getDateInstance(cal, DateFormat.MEDIUM, Locale.US);
250
251        for (int i=0; i < 30; i++) {
252            astro.setDate(cal.getTime());
253
254            Date sunrise = new Date(astro.getSunRiseSet(true));
255            Date sunset = new Date(astro.getSunRiseSet(false));
256
257            cal2.setTime(cal.getTime());
258            cal2.set(Calendar.SECOND,      0);
259            cal2.set(Calendar.MILLISECOND, 0);
260
261            cal2.set(Calendar.HOUR_OF_DAY, USNO[4*i+0]);
262            cal2.set(Calendar.MINUTE,      USNO[4*i+1]);
263            Date exprise = cal2.getTime();
264            cal2.set(Calendar.HOUR_OF_DAY, USNO[4*i+2]);
265            cal2.set(Calendar.MINUTE,      USNO[4*i+3]);
266            Date expset = cal2.getTime();
267            // Compute delta of what we got to the USNO data, in seconds
268            int deltarise = Math.abs((int)(sunrise.getTime() - exprise.getTime()) / 1000);
269            int deltaset = Math.abs((int)(sunset.getTime() - expset.getTime()) / 1000);
270
271            // Allow a deviation of 0..MAX_DEV seconds
272            // It would be nice to get down to 60 seconds, but at this
273            // point that appears to be impossible without a redo of the
274            // algorithm using something more advanced than Duffett-Smith.
275            final int MAX_DEV = 180;
276            if (deltarise > MAX_DEV || deltaset > MAX_DEV) {
277                if (deltarise > MAX_DEV) {
278                    errln("FAIL: " + day.format(cal.getTime()) +
279                          ", Sunrise: " + df2.format(sunrise) +
280                          " (USNO " + df.format(exprise) +
281                          " d=" + deltarise + "s)");
282                } else {
283                    logln(day.format(cal.getTime()) +
284                          ", Sunrise: " + df.format(sunrise) +
285                          " (USNO " + df.format(exprise) + ")");
286                }
287                if (deltaset > MAX_DEV) {
288                    errln("FAIL: " + day.format(cal.getTime()) +
289                          ", Sunset: " + df2.format(sunset) +
290                          " (USNO " + df.format(expset) +
291                          " d=" + deltaset + "s)");
292                } else {
293                    logln(day.format(cal.getTime()) +
294                          ", Sunset: " + df.format(sunset) +
295                          " (USNO " + df.format(expset) + ")");
296                }
297            } else {
298                logln(day.format(cal.getTime()) +
299                      ", Sunrise: " + df.format(sunrise) +
300                      " (USNO " + df.format(exprise) + ")" +
301                      ", Sunset: " + df.format(sunset) +
302                      " (USNO " + df.format(expset) + ")");
303            }
304            cal.add(Calendar.DATE, 1);
305        }
306
307//        CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60);
308//        cal.clear();
309//        cal.set(cal.YEAR, 1986);
310//        cal.set(cal.MONTH, cal.MARCH);
311//        cal.set(cal.DATE, 10);
312//        cal.set(cal.YEAR, 1988);
313//        cal.set(cal.MONTH, cal.JULY);
314//        cal.set(cal.DATE, 27);
315//        a.setDate(cal.getTime());
316//        long r = a.getSunRiseSet2(true);
317    }
318
319    public void TestBasics() {
320        // Check that our JD computation is the same as the book's (p. 88)
321        CalendarAstronomer astro = new CalendarAstronomer();
322        GregorianCalendar cal3 = new GregorianCalendar(TimeZone.getTimeZone("GMT"), Locale.US);
323        DateFormat d3 = DateFormat.getDateTimeInstance(cal3, DateFormat.MEDIUM,DateFormat.MEDIUM,Locale.US);
324        cal3.clear();
325        cal3.set(Calendar.YEAR, 1980);
326        cal3.set(Calendar.MONTH, Calendar.JULY);
327        cal3.set(Calendar.DATE, 27);
328        astro.setDate(cal3.getTime());
329        double jd = astro.getJulianDay() - 2447891.5;
330        double exp = -3444;
331        if (jd == exp) {
332            logln(d3.format(cal3.getTime()) + " => " + jd);
333        } else {
334            errln("FAIL: " + d3.format(cal3.getTime()) + " => " + jd +
335                  ", expected " + exp);
336        }
337
338
339//        cal3.clear();
340//        cal3.set(cal3.YEAR, 1990);
341//        cal3.set(cal3.MONTH, Calendar.JANUARY);
342//        cal3.set(cal3.DATE, 1);
343//        cal3.add(cal3.DATE, -1);
344//        astro.setDate(cal3.getTime());
345//        astro.foo();
346    }
347
348    public void TestMoonAge(){
349        GregorianCalendar gc = new GregorianCalendar(new SimpleTimeZone(0,"GMT"));
350        CalendarAstronomer calastro = new CalendarAstronomer();
351        // more testcases are around the date 05/20/2012
352        //ticket#3785  UDate ud0 = 1337557623000.0;
353        double testcase[][] = {{2012, 5, 20 , 16 , 48, 59},
354                {2012, 5, 20 , 16 , 47, 34},
355                {2012, 5, 21, 00, 00, 00},
356                {2012, 5, 20, 14, 55, 59},
357                {2012, 5, 21, 7, 40, 40},
358                {2023, 9, 25, 10,00, 00},
359                {2008, 7, 7, 15, 00, 33},
360                {1832, 9, 24, 2, 33, 41 },
361                {2016, 1, 31, 23, 59, 59},
362                {2099, 5, 20, 14, 55, 59}
363        };
364        // Moon phase angle - Got from http://www.moonsystem.to/checkupe.htm
365        double angle[] = {356.8493418421329, 356.8386760059673, 0.09625415252237701, 355.9986960782416, 3.5714026601303317, 124.26906744384183, 59.80247650195558, 357.54163205513123, 268.41779281511094, 4.82340276581624};
366        double precision = PI/32;
367        for(int i=0; i<testcase.length; i++){
368            gc.clear();
369            String testString = "CASE["+i+"]: Year "+(int)testcase[i][0]+" Month "+(int)testcase[i][1]+" Day "+
370                                    (int)testcase[i][2]+" Hour "+(int)testcase[i][3]+" Minutes "+(int)testcase[i][4]+
371                                    " Seconds "+(int)testcase[i][5];
372            gc.set((int)testcase[i][0],(int)testcase[i][1]-1,(int)testcase[i][2],(int)testcase[i][3],(int)testcase[i][4], (int)testcase[i][5]);
373            calastro.setDate(gc.getTime());
374            double expectedAge = (angle[i]*PI)/180;
375            double got = calastro.getMoonAge();
376            logln(testString);
377            if(!(got>expectedAge-precision && got<expectedAge+precision)){
378                errln("FAIL: expected " + expectedAge +
379                        " got " + got);
380            }else{
381                logln("PASS: expected " + expectedAge +
382                        " got " + got);
383            }
384        }
385    }
386}
387