/*************************************************************************** ** ** SUN.C Version 1.0 Michael Schwartz December 25, 1984 ** ** This program uses the USNO algorithm to calculate sunrise and sunset ** times from standard lattitudes and longitudes. It has been set up as ** system independently as possible, with the exception being a function ** which gets the current (default) date and day of the year. It is meant ** to be particularly useful for Jewish persons in out-of-the-way locations ** who might need to know the time of candle_lighting or havdalah. With ** little difficulty, z'man k'riath shema can be calculated and added to the ** routines. ** ** Inquiries can reach me at PO BOX 24536, Denver, Colorado 80224 ** (a place no longer 'out of the way') ** ** Ref: Almanac for Computers, pub. by US NAVAL OBSERVATORY ** Usage: ** sun [-c] [-l[lat]] [-L[long]] [-M] [-m[1-12|a]] [-d[day]] ** [-t] [-<12|24>] ** Latitude in deg,min'sec" N; Longitude in deg,min'sec" W ** ** Accuracy: The USNO claims accuracy withing 2 minutes except at extreme ** northern or southern lattitudes. Comparison to local NWS charts for ** sunrise and sunset (which are cheap and easy to come by) shows that with ** the double precision calculations, the charts produced by this program ** are no more than 1 minute removed from those charts in lattitudes lower ** than 41 degrees. Candle lighting times agree with those on popular ** calendars also to the 1 minute accuracy. ** ** The program, unlike its fortran predecessor has a number of ** important options and defaults. It is capable of getting today's ** sunrise and sunset at a default location (now Denver, of course), ** producing a calendar-like table of a month or a year, and allowing the ** user to produce reams of data without reinvoking the program. ** ** Permission is granted to distribute and use this program as desired. ** ** Bugs: Automatic calculation of non-default time zones will not produce ** correct local times (using the APPROXIMATE_TZ define). Otherwise, ** when lattitudes and longitudes are specified, the result is in Zulu (UT). ** No method of calculating DST or aligning years to Fridays/Saturdays ** or holidays has been provided in the MS-DOS version: However, ** as more MS-DOS compilers support the ANSI time standards, the MS-DOS ** option may not be necessary for your compiler. **************************************************************************/ #include #ifdef UNIX #define LONG int #ifdef MSDOS #undef MSDOS #endif #else #define LONG long #define MSDOS #define FUNC_8086 sysint21 #define INT_8086 bdos /* ** The last two are currently set for the CI-86 compiler. They are the ** machine interrupt for function calls (function 21) and the general ** interrupt with return from AL respectively. Change for other compilers. ** In fact, modern compilers should all be able to use the UNIX def. */ #endif #define TWELVE 1 #define TWENTYFOUR 0 #define SUNSET 0 #define CIVIL_TWILIGHT 1 #define NAUTICAL_TWILIGHT 2 #define ASTRONOMICAL_TWILIGHT 3 int myclock = TWELVE; /* Chooses a 12 hour clock */ static char *location = "Denver, Colorado (East Side)"; /* Defines the location for the report header */ int longdeg=104, longmin=54, longsec=16, latdeg=39, latmin=41, latsec=45; /* Lattitude and longitude of east Denver, Colorado */ int TZ = -7; /* This is the difference between MST and GMT */ #define BAD_ARG -1 #define NOT_IMPLEMENTED -2 #define BAD_MONTH -3 #define BAD_DATE -4 #define NORMAL 0 #define NO_SUNSET 1 #define NO_SUNRISE 2 static char *months[] = { "Nonesuch", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static int count[] = { 1,32,60,91,121,152,182,213,244,274,305,335,366 }; /* ** Note -- this is for a 365 day year. The best possible approximation. ** However, if one asks for 2/29, the sheet will still show it. */ int cal_opt, month_opt, day_opt, multi_opt, lat_opt, long_opt, ang_opt=0; int j_opt; int month_num, month_day, first_day, last_day; main(argc,argv) int argc; char *argv[]; { double xsunrise, xsunset; int j=0, day, day_adj, status; /* ** day_adj and status are used for those embarrasing places where the sun ** doesn't rise and set once a day as in the middle latitudes. ** day_adj will be -1 if today's event really happened yesterday, and ** 1 if today's event won't happen until tomorrow. ** e.g, if today's sunrise occurred at 11:00PM yesterday. ** status will be 0 if NORMAL, NO_SUNRISE or NO_SUNSET if that event doesn't ** happen at all on that day (the range of an acos has been violated) */ char *sunrise, *sunset, *candle_light, *havdalah, *timeadj(); char *get_date(); /* Puts current date in a string of fixed length */ #define LIGHT -18 #define HAV 42 /* The offsets from sunset for candle lighting and havdalah */ /* Default is today */ first_day = last_day = get_current_day(); /* Handle args */ while (++j < argc) { if (argv[j][0] != '-') { prerror(BAD_ARG,argv[j]); prusage(); } else { switch (argv[j][1]) { case 'c': cal_opt++; break; case 'M': multi_opt++; day_opt++; first_day = last_day = get_day("\0"); break; case 'm': month_opt++; month_num = get_month(argv[j]+2); break; case 'd': day_opt++; first_day = last_day = get_day(argv[j]+2); break; case 'l': lat_opt++; get_angle("Latitude",&latdeg,&latmin,&latsec,argv[j]+2); break; case 'L': long_opt++; get_angle("Longitude",&longdeg,&longmin,&longsec,argv[j]+2); #ifdef APPROXIMATE_TZ TZ = -((longdeg*60 + longmin) + 450)/900; /* Approximation */ #else TZ = 0; /* Use GMT */ #endif break; case 'j': j_opt++; break; case '1': /* Assume arg is -12 */ myclock = TWELVE; break; case '2': /* Assume arg is -24 */ myclock = TWENTYFOUR; break; case 't': /* Alternate sun angle for sunrise/set calculation */ if (argv[j][2] == 'S') /* Normal */ ang_opt=SUNSET; else if (argv[j][2] == 'C') /* Civil Twilight */ ang_opt=CIVIL_TWILIGHT; else if (argv[j][2] == 'N') ang_opt=NAUTICAL_TWILIGHT; else if (argv[j][2] == 'A') ang_opt=ASTRONOMICAL_TWILIGHT; else prerror(BAD_ARG,argv[j]); break; default: prerror(BAD_ARG,argv[j]); } } } if (cal_opt && (lat_opt || long_opt) && !multi_opt) /* New location */ get_new_location_name(); if (cal_opt && !multi_opt) prheader(); for (day=first_day;day<=last_day;day++) { status = suntime(&xsunrise,&xsunset,day); /* Note: an "adjusted hourlength" is (xsunset - xsunrise)/12, unless this is ** less than 0; in that case, add 2. (24/12) */ if (status & NO_SUNRISE) sunrise = "No sunrise today"; else sunrise = timeadj("Sunrise at", xsunrise,0 ,&day_adj); if (status & NO_SUNSET) { sunset = "No sunset today"; if (j_opt) { candle_light = "Candle lighting at **:**"; havdalah = "Havdalah at **:**"; } } else { sunset = timeadj("Sunset at", xsunset ,0 ,&day_adj); if (j_opt) { candle_light = timeadj("Candle lighting at",xsunset ,LIGHT,&day_adj); havdalah = timeadj("Havdalah at", xsunset ,HAV ,&day_adj); } } if (cal_opt) { if (j_opt) printf("%-12s %s %s %s %s\n",get_date(), sunrise, candle_light, sunset, havdalah); else printf("%-12s %s %s\n",get_date(),sunrise,sunset); } else { if (j_opt) printf("%s %d\n\t%s %s\n\t%s %s\n", months[month_num],month_day,sunrise, sunset, candle_light, havdalah); else printf("%s %2d\n\t%s\t\t%s\n", months[month_num],month_day,sunrise,sunset); } if (day == count[month_num]-1) /* We did last day of a month */ { month_num++; month_day=1; if (cal_opt && !multi_opt) { putchar('\f'); prheader(); } } else { if (cal_opt && !multi_opt) { if (!(month_day%10)) { for (j=0;j<80;j++) putchar('-'); putchar('\n'); } } month_day++; } if (!(status & NO_SUNRISE)) free(sunrise); if (!(status & NO_SUNSET)) { free(sunset); if (j_opt) { free(candle_light); free(havdalah); } } } if (multi_opt) exit(main(argc,argv)); } /****************************************************************** C SUBROUTINES C ***************************************************************** c this program will calCULATE SUNSET TIMES FOR EAST DENVER. C LAMBDA = 104,54'30" W Longitude C PHI = 39,42'0"N Latitude C FOR 1978, M=.985600t -3.251 -- M is the Sun's mean anomaly C L=M + 1.916 SIN(M) + .020SIN(2M) + 282.565 -- L is Sun's TRUE long. C TANa = .91746 TAN L -- a is Sun's right ascension C SIN DELTA = .39782 SIN L -- Sun's declination C COS h = (COSz - SIN DELTA*SIN PHI)/(COS DELTA*COS PHI) C T = h + a - 0.065710t -6.620 -- h is local hour angle, a now in hours C UT = T + LAMBDA --UT is GMT. LAMBDA is now in hours (=deg/15) C C WHERE Z is the zenith distance at phenomena in question. Samples: c Z=90,50' REGULAR SUNRISE/SET (Atmospheric refraction + disk diameter) C Z=96, CIVIL TWILIGHT C Z=102 NAUTICAL TWILIGHT C Z=106 ASTRONOMICAL TWILIGHT *************************************************************************/ #define PI 3.14159265 #define TODEC(a,b,c) ((double)a + (double)b/60.0 + (double)c/3600.0) #define DEGRAD (PI/180.0) #define RADDEG (180.0/PI) #define D 0.91746 #define E 0.39782 #define M(x) (0.985600*x - 3.251) #define L(x) (x + 1.916*sin(DEGRAD*x) + 0.020*sin(2*DEGRAD*x) + 282.565) #define ADJ(x) (-0.065710*x - 6.620) suntime(sunrise,sunset,day) double *sunrise, *sunset; int day; /****************************************************************** ** --- This is where the REAL work is done --- ** This routine will seem FORTRANy. An attempt has been made to ** mirror the procedure outlined in the USGS publication; very little ** optimization has been done, so that the connection between the ** publication and the software will be extremely clear. ** Thus, all angles are preserved in degrees (degrees are converted to ** hours by the observation that 360 degrees = 24 hours -- so 15.0 ** degrees are 1 hour). ******************************************************************/ { double lohr, lambda, phi, xm, xl, zm, zl, a, aa, ahr; double sind, sindel, cosd, cosdel, h, hh, hhr, hphr, t, tprime; double hprime, tt, ttt, ut, uut, aahr; double cosphi, sinphi, cosz; double sin(),cos(),acos(),tan(),atan(),sqrt(); double fabs(); int retval=NORMAL; /********************************************************************* ** lambda - longitude in degrees phi - lattitude in degrees ** [sin|cos][d|del] - functions of pseudo-angle delta **********************************************************************/ switch(ang_opt) { case SUNSET: cosz = cos(DEGRAD*TODEC(90,50,0)); break; case CIVIL_TWILIGHT: cosz = cos(DEGRAD*TODEC(96,0,0)); break; case NAUTICAL_TWILIGHT: cosz = cos(DEGRAD*TODEC(102,0,0)); break; case ASTRONOMICAL_TWILIGHT: cosz = cos(DEGRAD*TODEC(106,0,0)); break; } lambda = TODEC(longdeg,longmin,longsec); lohr = lambda/15.0; phi = TODEC(latdeg,latmin,latsec); cosphi = cos(DEGRAD*phi); sinphi = sin(DEGRAD*phi); t = (double)day + (18.0 + lohr)/24.0; tprime = (double)day + (6.0 + lohr)/24.0; xm = M(t); zm = M(tprime); xl = L(xm); zl = L(zm); /* XM,XL ARE BOTH IN DEGREES. */ a = RADDEG*atan( D*tan(DEGRAD*xl) ); /* Conversion to and from radians */ aa = RADDEG*atan( D*tan(DEGRAD*zl) ); /******* readjustment of angles a and aa *****************/ if ( fabs(a+360.0-xl) > 90.0 ) a += 180.0; if (a > 360.0) a -= 360.0; if ( fabs(aa+360.0-zl) > 90.0 ) aa += 180.0; if (aa > 360.0) aa -= 360.0; ahr = a/15.0; sindel = E*sin(DEGRAD*xl); cosdel = sqrt(1.0 - sindel*sindel); /* cos delta must ALWAYS be >0 */ h = (cosz - sindel*sinphi)/( cosdel*cosphi ); aahr= aa/15.0; sind = E*sin(DEGRAD*zl); cosd = sqrt(1.0 - sind*sind); hh= (cosz - sind *sinphi)/( cosd *cosphi ); if (fabs(h) <= 1.0) h = RADDEG*acos(h); else retval |= NO_SUNSET; if (fabs(hh) <= 1.0) hh= RADDEG*acos(hh); else retval |= NO_SUNRISE; hhr = h/15.0; tt = hhr + ahr + ADJ(t); ut = tt + lohr; hprime = 360.0 - hh; /* Puts sunrise in correct quadrant */ hphr= hprime/15.0; ttt = hphr+ aahr + ADJ(tprime); uut = ttt + lohr; #ifdef DEBUG fprintf(stderr,"DEBUG: phi %f lambda %f\n",phi,lambda); fprintf(stderr,"DEBUG: t %f tprime %f\n",t,tprime); fprintf(stderr,"DEBUG: xl %f zl %f\n",xl,zl); fprintf(stderr,"DEBUG: a %f aa %f\n",a,aa); fprintf(stderr,"DEBUG: h %f hh %f hprime %f\n",h, hh, hprime ); fprintf(stderr,"DEBUG: hhr %f hphr %f\n",hhr,hphr); fprintf(stderr,"DEBUG: tt %f ttt %f\n",tt,ttt); fprintf(stderr,"DEBUG: ut %f uut %f\n",ut,uut); #endif *sunset = ut + TZ; *sunrise = uut+ TZ; return(retval); } /********************************************************************** SUBROUTINE TIMEADJ(TIME,HR,MIN,MINADJ,DAYADJ) **********************************************************************/ char *timeadj(mess,time,minadj,dayadj) char *mess; double time; int minadj, *dayadj; { char *calloc(); static char *str; int hour, min, strlen(); int num = strlen(mess)+9; time += (double)minadj/60.0; if (time < 0) { time += 24.0; *dayadj -= 1; } hour = time; /* Type conversion causes truncation */ min = (int)(( time - (double)hour )*60.0 + 0.5); if (min >= 60) { hour += 1; min -= 60; } if (hour > 24) { hour -= 24; *dayadj += 1; } if (myclock==TWELVE) /* Check for 12 hour clock */ if ( hour > 12) hour -= 12; str = calloc(num,sizeof(char)); sprintf(str,"%s %2d:%02d",mess,hour,min); return(str); } /*************** New functions ***************/ get_current_day() /* This is a system dependent function which will get the current day of ** the year, and initialize the month-num and month-day */ { int day; #ifdef MSDOS struct regval { int ax,bx,cx,dx,si,di,ds,es; } regs; regs.ax = 0x2a00; /* MS-DOS get date function request */ FUNC_8086(®s,®s); /* Now DH has month, DL has day */ month_num = regs.dx>>8; month_day = regs.dx&0xff; day = count[month_num-1] - 1 + month_day; #endif #ifdef UNIX #include time_t tp; struct tm *localtime(),*t; time(&tp); t = localtime(&tp); month_num = t->tm_mon + 1; month_day = t->tm_mday; day = t->tm_yday; if (!lat_opt && !long_opt) { TZ = -timezone / 3600; /* Timezone is in seconds */ if (t->tm_isdst) TZ++; } #endif return(day); } #include get_day(argin) char *argin; /* Prompt if *argin NULL. Otherwise parse for mm/dd or num */ /* Gets the day of interest, possibly parsed from argin--if not, prompts */ /* Also used as the final exit function if multi_opt */ { char temp[6], *cp; int tnum,j; LONG atoi(); if (*argin == NULL) /* Prompt for input */ { argin = temp; get_string(argin, "Enter date in mm/dd format: ( to exit): ",5); /* This is the exit for multi_opt */ if (!*argin) exit(0); /* If we got a NULL then exit */ } cp = argin; tnum = 0; while (isdigit(*cp) && *cp) tnum = 10*tnum + *cp++ - '0'; if (*cp) /* We have a non-digit literal */ { month_num = tnum; month_day = atoi(cp+1); if (month_num < 1 || month_num > 12) prerror(BAD_DATE,argin); if (month_day < 1 || month_day > 366) prerror(BAD_DATE,argin); return(count[tnum-1] - 1 + month_day); } else { /* The date is a single integer, and thus the number of a day of the year */ month_num = 1; for (j=0; j<12; j++) if (tnum >= count[j]) month_num = j+1; month_day = tnum - count[month_num-1] + 1; if (tnum < 1 || tnum > 366) prerror(BAD_DATE,argin); return (tnum); } } prerror(errnum,mess) int errnum; char *mess; { static char *messages[] = { "***Not used***", "Bad argument |%s|\n", "Option |%s| not yet implemented\n", "Bad argument |%s| to -m[onth] flag: **Fatal**\n", "Bad date |%s|. Heap may be corrupted.\n" }; fprintf(stderr,messages[-errnum],mess); return(errnum); } get_angle(mess,degrees,minutes,seconds,argin) char *mess, *argin; int *degrees, *minutes, *seconds; /* Gets the angle of interest, either parsed from argin or by prompts */ { char temp[12],*cc,*degp,*minp,*secp,*strchr(); static char *zero = "0"; LONG atoi(); if (*argin == NULL) /* Must prompt for angle */ { fprintf(stderr,"Enter %s as deg,min'sec\": ",mess); fflush(stderr); /* Use stderr so as not to mess up a redirection of output */ get_string(temp," ",11); temp[11]='\0'; /* That's right, I don't trust myself */ argin = temp; } if ( (cc = strchr(argin,',') ) > 0) { degp = argin; *cc = '\0'; minp = cc+1; } else { minp = argin; degp = zero; } if ( (cc = strchr(minp,'\'') ) > 0) { *cc = '\0'; secp = cc + 1; } else { secp = minp; minp = zero; } if ( (cc = strchr(secp,'"') ) > 0) { *cc = '\0'; } else { secp = zero; } *degrees = atoi(degp); *minutes = atoi(minp); *seconds = atoi(secp); } static char only_date_space[16]; char *get_date() /* Converts mm/dd into a string of month day of fixed length */ { sprintf(only_date_space,"%s %2d",months[month_num],month_day); return(only_date_space); } prheader() /* Used to put page headers for the cal_opt */ { printf("\n "); printf("Sunrise and Sunset times for %s\n\n\n",location); } get_month(argin) char *argin; { LONG atoi(); if (*argin == NULL) { first_day = count[month_num-1]; last_day = count[month_num] -1; month_day = 1; return(month_num); } if (*argin == 'a') { first_day = 1; last_day = 366; month_num = 1; month_day = 1; return(month_num); } month_num = atoi(argin); if ((month_num < 1) || (month_num > 12) ) { prerror(BAD_MONTH,argin); exit(-1); } first_day = count[month_num-1]; last_day = count[month_num] - 1; month_day = 1; return(month_num); } get_new_location_name() { char *calloc(); location = calloc(51,sizeof(char)); /* Don't use up this space unless we need it (For multi, use realloc?) */ get_string(location, "Enter new location (Title for report): ",50); } prusage() { printf("Usage:\n"); printf("sun [-d[day]] [-c] [-M] [-l[lat]] [-L[long]]\n"); printf(" [-m[1-12|a]] [-<12|24>] [-t] [-j]\n\n"); printf(" -m month\t-c calendar output\t-M prompt multiple days\n"); printf(" -12 - twelve hour clock. -24 - twenty four hour clock\n"); printf(" -t: S regular S-nrise/set. C Civil. N Nautical. A Astronomical\n"); printf(" -j: Jewish - print candle lighting and havdalah times\n"); printf("If no values are specified, the d, l and L options will prompt\n"); printf("Default for date is today, default for m is this month.\n"); printf("Default is for a 12 hour clock, regular sunrise/set.\n"); exit(-1); } get_string(buffer,mess,maxlen) char *buffer, *mess; int maxlen; { /* Returns 1 if stuff read, 0 if read had error or EOF */ /* Common routine should work for both MS-DOS and UNIX */ int n_write; /* In case we wish to check status */ char *start=buffer; char c; n_write = write(2,mess,strlen(mess)); /* STDERR, so not mess up output */ while (read(0,&c,1) == 1) /* Until EOF or error */ { if (c == '\b' || c == 127) /* BS or DEL */ { if (buffer-start) buffer--; } else if (c == '\t') /* HT */ *buffer++ = c; else if (c == '\n' || c == '\r') /* Don't care about mode for NL */ { *buffer = '\0'; return(1); } else if ( (buffer - start) >= maxlen || c < 32) continue; /* Ignore CTRLs and flush overage to NL */ else *buffer++ = c; } return(0); /* Get here only if read fails */ }