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