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