1/********************************************************************
2 * COPYRIGHT:
3 * Copyright (c) 1996-2010, International Business Machines Corporation and
4 * others. All Rights Reserved.
5 ********************************************************************/
6
7/* Test CalendarAstronomer for C++ */
8
9#include "unicode/utypes.h"
10#include "string.h"
11#include "unicode/locid.h"
12
13#if !UCONFIG_NO_FORMATTING
14
15#include "astro.h"
16#include "astrotst.h"
17#include "gregoimp.h" // for Math
18#include "unicode/simpletz.h"
19
20
21static const double DAY_MS = 24.*60.*60.*1000.;
22
23#define CASE(id,test) case id: name = #test; if (exec) { logln(#test "---"); logln((UnicodeString)""); test(); } break
24
25AstroTest::AstroTest(): astro(NULL), gc(NULL) {
26}
27
28void AstroTest::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ )
29{
30    if (exec) logln("TestSuite AstroTest");
31    switch (index) {
32      // CASE(0,FooTest);
33      CASE(0,TestSolarLongitude);
34      CASE(1,TestLunarPosition);
35      CASE(2,TestCoordinates);
36      CASE(3,TestCoverage);
37      CASE(4,TestSunriseTimes);
38      CASE(5,TestBasics);
39      CASE(6,TestMoonAge);
40    default: name = ""; break;
41    }
42}
43
44#undef CASE
45
46#define ASSERT_OK(x)   if(U_FAILURE(x)) { dataerrln("%s:%d: %s\n", __FILE__, __LINE__, u_errorName(x)); return; }
47
48
49void AstroTest::initAstro(UErrorCode &status) {
50  if(U_FAILURE(status)) return;
51
52  if((astro != NULL) || (gc != NULL)) {
53    dataerrln("Err: initAstro() called twice!");
54    closeAstro(status);
55    if(U_SUCCESS(status)) {
56      status = U_INTERNAL_PROGRAM_ERROR;
57    }
58  }
59
60  if(U_FAILURE(status)) return;
61
62  astro = new CalendarAstronomer();
63  gc = Calendar::createInstance(TimeZone::getGMT()->clone(), status);
64}
65
66void AstroTest::closeAstro(UErrorCode &/*status*/) {
67  if(astro != NULL) {
68    delete astro;
69    astro = NULL;
70  }
71  if(gc != NULL) {
72    delete gc;
73    gc = NULL;
74  }
75}
76
77void AstroTest::TestSolarLongitude(void) {
78  UErrorCode status = U_ZERO_ERROR;
79  initAstro(status);
80  ASSERT_OK(status);
81
82  struct {
83    int32_t d[5]; double f ;
84  } tests[] = {
85    { { 1980, 7, 27, 0, 00 },  124.114347 },
86    { { 1988, 7, 27, 00, 00 },  124.187732 }
87  };
88
89  logln("");
90  for (uint32_t i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) {
91    gc->clear();
92    gc->set(tests[i].d[0], tests[i].d[1]-1, tests[i].d[2], tests[i].d[3], tests[i].d[4]);
93
94    astro->setDate(gc->getTime(status));
95
96    double longitude = astro->getSunLongitude();
97    //longitude = 0;
98    CalendarAstronomer::Equatorial result;
99    astro->getSunPosition(result);
100    logln((UnicodeString)"Sun position is " + result.toString() + (UnicodeString)";  " /* + result.toHmsString()*/ + " Sun longitude is " + longitude );
101  }
102  closeAstro(status);
103  ASSERT_OK(status);
104}
105
106
107
108void AstroTest::TestLunarPosition(void) {
109  UErrorCode status = U_ZERO_ERROR;
110  initAstro(status);
111  ASSERT_OK(status);
112
113  static const double tests[][7] = {
114    { 1979, 2, 26, 16, 00,  0, 0 }
115  };
116  logln("");
117
118  for (int32_t i = 0; i < (int32_t)(sizeof(tests)/sizeof(tests[0])); i++) {
119    gc->clear();
120    gc->set((int32_t)tests[i][0], (int32_t)tests[i][1]-1, (int32_t)tests[i][2], (int32_t)tests[i][3], (int32_t)tests[i][4]);
121    astro->setDate(gc->getTime(status));
122
123    const CalendarAstronomer::Equatorial& result = astro->getMoonPosition();
124    logln((UnicodeString)"Moon position is " + result.toString() + (UnicodeString)";  " /* + result->toHmsString()*/);
125  }
126
127  closeAstro(status);
128  ASSERT_OK(status);
129}
130
131
132
133void AstroTest::TestCoordinates(void) {
134  UErrorCode status = U_ZERO_ERROR;
135  initAstro(status);
136  ASSERT_OK(status);
137
138  CalendarAstronomer::Equatorial result;
139  astro->eclipticToEquatorial(result, 139.686111 * CalendarAstronomer::PI / 180.0, 4.875278* CalendarAstronomer::PI / 180.0);
140  logln((UnicodeString)"result is " + result.toString() + (UnicodeString)";  " /* + result.toHmsString()*/ );
141  closeAstro(status);
142  ASSERT_OK(status);
143}
144
145
146
147void AstroTest::TestCoverage(void) {
148  UErrorCode status = U_ZERO_ERROR;
149  initAstro(status);
150  ASSERT_OK(status);
151  GregorianCalendar *cal = new GregorianCalendar(1958, UCAL_AUGUST, 15,status);
152  UDate then = cal->getTime(status);
153  CalendarAstronomer *myastro = new CalendarAstronomer(then);
154  ASSERT_OK(status);
155
156  //Latitude:  34 degrees 05' North
157  //Longitude:  118 degrees 22' West
158  double laLat = 34 + 5./60, laLong = 360 - (118 + 22./60);
159  CalendarAstronomer *myastro2 = new CalendarAstronomer(laLong, laLat);
160
161  double eclLat = laLat * CalendarAstronomer::PI / 360;
162  double eclLong = laLong * CalendarAstronomer::PI / 360;
163
164  CalendarAstronomer::Ecliptic ecl(eclLat, eclLong);
165  CalendarAstronomer::Equatorial eq;
166  CalendarAstronomer::Horizon hor;
167
168  logln("ecliptic: " + ecl.toString());
169  CalendarAstronomer *myastro3 = new CalendarAstronomer();
170  myastro3->setJulianDay((4713 + 2000) * 365.25);
171
172  CalendarAstronomer *astronomers[] = {
173    myastro, myastro2, myastro3, myastro2 // check cache
174  };
175
176  for (uint32_t i = 0; i < sizeof(astronomers)/sizeof(astronomers[0]); ++i) {
177    CalendarAstronomer *anAstro = astronomers[i];
178
179    //logln("astro: " + astro);
180    logln((UnicodeString)"   date: " + anAstro->getTime());
181    logln((UnicodeString)"   cent: " + anAstro->getJulianCentury());
182    logln((UnicodeString)"   gw sidereal: " + anAstro->getGreenwichSidereal());
183    logln((UnicodeString)"   loc sidereal: " + anAstro->getLocalSidereal());
184    logln((UnicodeString)"   equ ecl: " + (anAstro->eclipticToEquatorial(eq,ecl)).toString());
185    logln((UnicodeString)"   equ long: " + (anAstro->eclipticToEquatorial(eq, eclLong)).toString());
186    logln((UnicodeString)"   horiz: " + (anAstro->eclipticToHorizon(hor, eclLong)).toString());
187    logln((UnicodeString)"   sunrise: " + (anAstro->getSunRiseSet(TRUE)));
188    logln((UnicodeString)"   sunset: " + (anAstro->getSunRiseSet(FALSE)));
189    logln((UnicodeString)"   moon phase: " + anAstro->getMoonPhase());
190    logln((UnicodeString)"   moonrise: " + (anAstro->getMoonRiseSet(TRUE)));
191    logln((UnicodeString)"   moonset: " + (anAstro->getMoonRiseSet(FALSE)));
192    logln((UnicodeString)"   prev summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), FALSE)));
193    logln((UnicodeString)"   next summer solstice: " + (anAstro->getSunTime(CalendarAstronomer::SUMMER_SOLSTICE(), TRUE)));
194    logln((UnicodeString)"   prev full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), FALSE)));
195    logln((UnicodeString)"   next full moon: " + (anAstro->getMoonTime(CalendarAstronomer::FULL_MOON(), TRUE)));
196  }
197
198  delete myastro2;
199  delete myastro3;
200  delete myastro;
201  delete cal;
202
203  closeAstro(status);
204  ASSERT_OK(status);
205}
206
207
208
209void AstroTest::TestSunriseTimes(void) {
210  UErrorCode status = U_ZERO_ERROR;
211  initAstro(status);
212  ASSERT_OK(status);
213
214  //  logln("Sunrise/Sunset times for San Jose, California, USA");
215  //  CalendarAstronomer *astro2 = new CalendarAstronomer(-121.55, 37.20);
216  //  TimeZone *tz = TimeZone::createTimeZone("America/Los_Angeles");
217
218  // We'll use a table generated by the UNSO website as our reference
219  // From: http://aa.usno.navy.mil/
220  //-Location: W079 25, N43 40
221  //-Rise and Set for the Sun for 2001
222  //-Zone:  4h West of Greenwich
223  int32_t USNO[] = {
224    6,59, 19,45,
225    6,57, 19,46,
226    6,56, 19,47,
227    6,54, 19,48,
228    6,52, 19,49,
229    6,50, 19,51,
230    6,48, 19,52,
231    6,47, 19,53,
232    6,45, 19,54,
233    6,43, 19,55,
234    6,42, 19,57,
235    6,40, 19,58,
236    6,38, 19,59,
237    6,36, 20, 0,
238    6,35, 20, 1,
239    6,33, 20, 3,
240    6,31, 20, 4,
241    6,30, 20, 5,
242    6,28, 20, 6,
243    6,27, 20, 7,
244    6,25, 20, 8,
245    6,23, 20,10,
246    6,22, 20,11,
247    6,20, 20,12,
248    6,19, 20,13,
249    6,17, 20,14,
250    6,16, 20,16,
251    6,14, 20,17,
252    6,13, 20,18,
253    6,11, 20,19,
254  };
255
256  logln("Sunrise/Sunset times for Toronto, Canada");
257  // long = 79 25", lat = 43 40"
258  CalendarAstronomer *astro3 = new CalendarAstronomer(-(79+25/60), 43+40/60);
259
260  // As of ICU4J 2.8 the ICU4J time zones implement pass-through
261  // to the underlying JDK.  Because of variation in the
262  // underlying JDKs, we have to use a fixed-offset
263  // SimpleTimeZone to get consistent behavior between JDKs.
264  // The offset we want is [-18000000, 3600000] (raw, dst).
265  // [aliu 10/15/03]
266
267  // TimeZone tz = TimeZone.getTimeZone("America/Montreal");
268  TimeZone *tz = new SimpleTimeZone(-18000000 + 3600000, "Montreal(FIXED)");
269
270  GregorianCalendar *cal = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
271  GregorianCalendar *cal2 = new GregorianCalendar(tz->clone(), Locale::getUS(), status);
272  cal->clear();
273  cal->set(UCAL_YEAR, 2001);
274  cal->set(UCAL_MONTH, UCAL_APRIL);
275  cal->set(UCAL_DAY_OF_MONTH, 1);
276  cal->set(UCAL_HOUR_OF_DAY, 12); // must be near local noon for getSunRiseSet to work
277
278  DateFormat *df_t  = DateFormat::createTimeInstance(DateFormat::MEDIUM,Locale::getUS());
279  DateFormat *df_d  = DateFormat::createDateInstance(DateFormat::MEDIUM,Locale::getUS());
280  DateFormat *df_dt = DateFormat::createDateTimeInstance(DateFormat::MEDIUM, DateFormat::MEDIUM, Locale::getUS());
281  if(!df_t || !df_d || !df_dt) {
282    dataerrln("couldn't create dateformats.");
283    return;
284  }
285  df_t->adoptTimeZone(tz->clone());
286  df_d->adoptTimeZone(tz->clone());
287  df_dt->adoptTimeZone(tz->clone());
288
289  for (int32_t i=0; i < 30; i++) {
290    logln("setDate\n");
291    astro3->setDate(cal->getTime(status));
292    logln("getRiseSet(TRUE)\n");
293    UDate sunrise = astro3->getSunRiseSet(TRUE);
294    logln("getRiseSet(FALSE)\n");
295    UDate sunset  = astro3->getSunRiseSet(FALSE);
296    logln("end of getRiseSet\n");
297
298    cal2->setTime(cal->getTime(status), status);
299    cal2->set(UCAL_SECOND,      0);
300    cal2->set(UCAL_MILLISECOND, 0);
301
302    cal2->set(UCAL_HOUR_OF_DAY, USNO[4*i+0]);
303    cal2->set(UCAL_MINUTE,      USNO[4*i+1]);
304    UDate exprise = cal2->getTime(status);
305    cal2->set(UCAL_HOUR_OF_DAY, USNO[4*i+2]);
306    cal2->set(UCAL_MINUTE,      USNO[4*i+3]);
307    UDate expset = cal2->getTime(status);
308    // Compute delta of what we got to the USNO data, in seconds
309    int32_t deltarise = (int32_t)uprv_fabs((sunrise - exprise) / 1000);
310    int32_t deltaset = (int32_t)uprv_fabs((sunset - expset) / 1000);
311
312    // Allow a deviation of 0..MAX_DEV seconds
313    // It would be nice to get down to 60 seconds, but at this
314    // point that appears to be impossible without a redo of the
315    // algorithm using something more advanced than Duffett-Smith.
316    int32_t MAX_DEV = 180;
317    UnicodeString s1, s2, s3, s4, s5;
318    if (deltarise > MAX_DEV || deltaset > MAX_DEV) {
319      if (deltarise > MAX_DEV) {
320        errln("FAIL: (rise) " + df_d->format(cal->getTime(status),s1) +
321              ", Sunrise: " + df_dt->format(sunrise, s2) +
322              " (USNO " + df_t->format(exprise,s3) +
323              " d=" + deltarise + "s)");
324      } else {
325        logln(df_d->format(cal->getTime(status),s1) +
326              ", Sunrise: " + df_dt->format(sunrise,s2) +
327              " (USNO " + df_t->format(exprise,s3) + ")");
328      }
329      s1.remove(); s2.remove(); s3.remove(); s4.remove(); s5.remove();
330      if (deltaset > MAX_DEV) {
331        errln("FAIL: (set)  " + df_d->format(cal->getTime(status),s1) +
332              ", Sunset:  " + df_dt->format(sunset,s2) +
333              " (USNO " + df_t->format(expset,s3) +
334              " d=" + deltaset + "s)");
335      } else {
336        logln(df_d->format(cal->getTime(status),s1) +
337              ", Sunset: " + df_dt->format(sunset,s2) +
338              " (USNO " + df_t->format(expset,s3) + ")");
339      }
340    } else {
341      logln(df_d->format(cal->getTime(status),s1) +
342            ", Sunrise: " + df_dt->format(sunrise,s2) +
343            " (USNO " + df_t->format(exprise,s3) + ")" +
344            ", Sunset: " + df_dt->format(sunset,s4) +
345            " (USNO " + df_t->format(expset,s5) + ")");
346    }
347    cal->add(UCAL_DATE, 1, status);
348  }
349
350  //        CalendarAstronomer a = new CalendarAstronomer(-(71+5/60), 42+37/60);
351  //        cal.clear();
352  //        cal.set(cal.YEAR, 1986);
353  //        cal.set(cal.MONTH, cal.MARCH);
354  //        cal.set(cal.DATE, 10);
355  //        cal.set(cal.YEAR, 1988);
356  //        cal.set(cal.MONTH, cal.JULY);
357  //        cal.set(cal.DATE, 27);
358  //        a.setDate(cal.getTime());
359  //        long r = a.getSunRiseSet2(true);
360  delete astro3;
361  delete tz;
362  delete cal;
363  delete cal2;
364  delete df_t;
365  delete df_d;
366  delete df_dt;
367  closeAstro(status);
368  ASSERT_OK(status);
369}
370
371
372
373void AstroTest::TestBasics(void) {
374  UErrorCode status = U_ZERO_ERROR;
375  initAstro(status);
376  if (U_FAILURE(status)) {
377    dataerrln("Got error: %s", u_errorName(status));
378    return;
379  }
380
381  // Check that our JD computation is the same as the book's (p. 88)
382  GregorianCalendar *cal3 = new GregorianCalendar(TimeZone::getGMT()->clone(), Locale::getUS(), status);
383  DateFormat *d3 = DateFormat::createDateTimeInstance(DateFormat::MEDIUM,DateFormat::MEDIUM,Locale::getUS());
384  d3->setTimeZone(*TimeZone::getGMT());
385  cal3->clear();
386  cal3->set(UCAL_YEAR, 1980);
387  cal3->set(UCAL_MONTH, UCAL_JULY);
388  cal3->set(UCAL_DATE, 2);
389  logln("cal3[a]=%.1lf, d=%d\n", cal3->getTime(status), cal3->get(UCAL_JULIAN_DAY,status));
390  {
391    UnicodeString s;
392    logln(UnicodeString("cal3[a] = ") + d3->format(cal3->getTime(status),s));
393  }
394  cal3->clear();
395  cal3->set(UCAL_YEAR, 1980);
396  cal3->set(UCAL_MONTH, UCAL_JULY);
397  cal3->set(UCAL_DATE, 27);
398  logln("cal3=%.1lf, d=%d\n", cal3->getTime(status), cal3->get(UCAL_JULIAN_DAY,status));
399
400  ASSERT_OK(status);
401  {
402    UnicodeString s;
403    logln(UnicodeString("cal3 = ") + d3->format(cal3->getTime(status),s));
404  }
405  astro->setTime(cal3->getTime(status));
406  double jd = astro->getJulianDay() - 2447891.5;
407  double exp = -3444.;
408  if (jd == exp) {
409    UnicodeString s;
410    logln(d3->format(cal3->getTime(status),s) + " => " + jd);
411  } else {
412    UnicodeString s;
413    errln("FAIL: " + d3->format(cal3->getTime(status), s) + " => " + jd +
414          ", expected " + exp);
415  }
416
417  //        cal3.clear();
418  //        cal3.set(cal3.YEAR, 1990);
419  //        cal3.set(cal3.MONTH, Calendar.JANUARY);
420  //        cal3.set(cal3.DATE, 1);
421  //        cal3.add(cal3.DATE, -1);
422  //        astro.setDate(cal3.getTime());
423  //        astro.foo();
424
425  delete cal3;
426  delete d3;
427  ASSERT_OK(status);
428  closeAstro(status);
429  ASSERT_OK(status);
430
431}
432
433void AstroTest::TestMoonAge(void){
434	UErrorCode status = U_ZERO_ERROR;
435	initAstro(status);
436	ASSERT_OK(status);
437
438	// more testcases are around the date 05/20/2012
439	//ticket#3785  UDate ud0 = 1337557623000.0;
440	static const double testcase[][10] = {{2012, 5, 20 , 16 , 48, 59},
441	                {2012, 5, 20 , 16 , 47, 34},
442	                {2012, 5, 21, 00, 00, 00},
443	                {2012, 5, 20, 14, 55, 59},
444	                {2012, 5, 21, 7, 40, 40},
445	                {2023, 9, 25, 10,00, 00},
446	                {2008, 7, 7, 15, 00, 33},
447	                {1832, 9, 24, 2, 33, 41 },
448	                {2016, 1, 31, 23, 59, 59},
449	                {2099, 5, 20, 14, 55, 59}
450	        };
451	// Moon phase angle - Got from http://www.moonsystem.to/checkupe.htm
452	static const double angle[] = {356.8493418421329, 356.8386760059673, 0.09625415252237701, 355.9986960782416, 3.5714026601303317, 124.26906744384183, 59.80247650195558,
453									357.54163205513123, 268.41779281511094, 4.82340276581624};
454	static const double precision = CalendarAstronomer::PI/32;
455	for (int32_t i = 0; i < (int32_t)(sizeof(testcase)/sizeof(testcase[0])); i++) {
456		gc->clear();
457		logln((UnicodeString)"CASE["+i+"]: Year "+(int32_t)testcase[i][0]+" Month "+(int32_t)testcase[i][1]+" Day "+
458		                                    (int32_t)testcase[i][2]+" Hour "+(int32_t)testcase[i][3]+" Minutes "+(int32_t)testcase[i][4]+
459		                                    " Seconds "+(int32_t)testcase[i][5]);
460		gc->set((int32_t)testcase[i][0], (int32_t)testcase[i][1]-1, (int32_t)testcase[i][2], (int32_t)testcase[i][3], (int32_t)testcase[i][4], (int32_t)testcase[i][5]);
461		astro->setDate(gc->getTime(status));
462		double expectedAge = (angle[i]*CalendarAstronomer::PI)/180;
463		double got = astro->getMoonAge();
464		//logln(testString);
465		if(!(got>expectedAge-precision && got<expectedAge+precision)){
466			errln((UnicodeString)"FAIL: expected " + expectedAge +
467					" got " + got);
468		}else{
469			logln((UnicodeString)"PASS: expected " + expectedAge +
470					" got " + got);
471		}
472	}
473	closeAstro(status);
474	ASSERT_OK(status);
475}
476
477
478// TODO: try finding next new moon after  07/28/1984 16:00 GMT
479
480
481#endif
482
483
484
485