\ sun 05.08.11 NAB
\ Sunrise/sunset calculations.

needs calendar
needs ftrig2
needs opg
needs tester

module sun
private:

\ Local latitude and longitude
\ (west and south are negative,
\ east and north are positive):
fvariable latitude
fvariable longitude

\ Sun's zenith for sunrise/sunset:
fvariable zenith

\ Other working variables:
fvariable lngHour
fvariable T
fvariable L
fvariable M
fvariable RA
fvariable sinDec
fvariable cosDec
fvariable cosH
fvariable H

public:

: set-location ( F: long lat -- )
  latitude f!  longitude f! ;

: set-zenith ( F: zenith -- )  zenith f! ;

private:

: zenith: ( F: f -- )
\ Builds zenith-setting words.
  create  here f!  1 floats allot
  does> ( -- )  f@ set-zenith ;

public:

90.83333e zenith: official-zenith
96e zenith: civil-zenith
102e zenith: nautical-zenith
108e zenith: astronomical-zenith

: day-of-year ( d m y -- day )
\ Calculate the day-of-year number
\  of a given date (January 1=day 1).
\  Uses the calendar library.
  dup >r  dmy>date
  1 January r> dmy>date d- drop 1+ ;

{ 20 July 1984 day-of-year -> 202 }

private:

\ The algorithm works in degrees,
\ so we need private versions of the
\ trig functions that operate on
\ degrees rather than radians:
: fsin  deg>rad fsin ;
: fcos  deg>rad fcos ;
: ftan  deg>rad ftan ;
: fasin  fasin rad>deg ;
: facos  facos rad>deg ;
: fatan  fatan rad>deg ;

\ Floating-point helper words:
: f> ( F: f1 f2 -- ) ( -- bool )  fswap f< ;
: ftuck ( F: a b -- b a b )  fswap fover ;
: f>s ( F: f -- ) ( -- n )  f>d d>s ;

: range360 ( F: f1 -- f2 )
\ Adjust so the range is [0,360).
  fdup f0< if  360e f+
  else  fdup 360e f> if  360e f-  then
  then ;

{ 383e range360 f>s -> 23 }
{ -17e range360 f>s -> 343 }

: floor90 ( F: f1 -- f2 )
\ Round down to the nearest multiple
\  of 90.
  90e ftuck f/ floor f* ;

{ 97e floor90 f>s -> 90 }

public:

: time>mh ( F: h.m -- ) ( -- min hour )
\ Convert a floating-point h.m time
\  into integer minutes and hours.
  fdup floor  fover fswap  f-
  60e f*  f>s  floor  f>s ;

{ 3.5e time>mh -> 30 3 }

false constant rising
true constant setting

: UTC-suntime
( d m y set? -- ) ( F: -- h.m )
\ Calculate the UTC sunrise or sunset
\  time for a given day of the year,
\  using the location set in the
\  longitude and latitude fvariables.
  >r  \ preserve rise/set value
  day-of-year  0 d>f  T f!
  let lngHour=longitude/15:
  r@ rising = if
    let T=T+((18-lngHour)/24):
  else \ setting
    let T=T+((6-lngHour)/24):
  then
  let M=(0.9856*T)-3.289:
  let L=range360(
    M+(1.916*sin(M))
    +(0.020*sin(2*M))+282.634):
  let RA=range360(
    atan(0.91764*tan(L))):
  let RA=(RA+
    (floor90(L)-floor90(RA)))/15:
  let sinDec=0.39782*sin(L):
  let cosDec=cos(asin(sinDec)):
  let cosH=(cos(zenith)
    -(sinDec*sin(latitude)))
    /(cosDec*cos(latitude)):
  let abs(cosH): 1e f> -11 and  throw
  let H=acos(cosH)/15:
  r> rising = if  let H=24-H:  then
  let H+RA-(0.06571*T)-6.622
    -lngHour:
;

{  \ Toronto, Canada: 43.6N 79.4W
    -79.4e 43.6e set-location
    official-zenith
    20 July 1989 setting UTC-suntime
    time>mh -> 53 0 }
{ 20 July 1989 rising UTC-suntime
    time>mh -> 54 9 }

private:

: local-offset ( -- local-offset. )
\ Return the total offset in minutes
\  of the timezone and DST settings.
\ Requires PalmOS 4 and above.
  PrefTimeZone >byte
  PrefGetPreference
  PrefDaylightSavingAdjustment
  >byte  PrefGetPreference  d+ ;

: range24 ( F: f1 -- f2 )
\ Adjust so the range is [0,24):
  fdup  24e f> if  24e f-  then
  fdup  f0< if  24e f+  then ;

: local-suntime
( d m y set? -- ) ( F: -- h.m )
\ Calculate sunrise or sunset time
\  for the specified date, adjusting
\  for the local timezone & DST.
  UTC-suntime
\ Convert UTC value to local time:
  local-offset d>f 60e f/ f+ range24 ;

public:

: sunrise ( d m y -- ) ( F: -- h.m )
  rising local-suntime ;
: sunset ( d m y -- ) ( F: -- h.m )
  setting local-suntime ;

end-module
