Hello.
Hello this script, originally written by Bruce R. Barkstrom, lets you get the times for Sunrise, and Sunset and hours of daylight, for a given date, or a range of dates in an unsaved TextEdit Document. It fetches the coordinates you have set in the Time Preferences pane, but lets you override those, should you wish to, by entering new.
Other than that, the script should be fairly robust, and input of dates are localized, so US citizens can enter their dates as mm-dd-yyyy (notice the 4 y’s for a year!).
Nigel has translated this script from CBasic to AppleScript, that once appeared in Byte Magazine, Yvan Koenig has tested, and read through the code I wrote for using it interactively. The fetching of coordinates from the Time Preferences wouldn’t have been possible, without the insights of Adam Bell.
Any bugs you might find, (you really shouldn’t), should be adressed to me, and please do report a bug if you find one. If you don’t want to post one in public, then that is fine with me, but please send me an e-mail about it.
The script is to be built upon with more astronomical data at a later stage, therefore it is important that this skeleton is as robust as possible.
Thanks!
Especially to Nigel Garvey and Yvan Koenig, for everything.
McUsr
-- Version 1.7
-- SUNRISE - SUNSET ::: Requires Snow Leopard or Later.
-- This program is intended to compute the time of sunrise and sunset,
-- as well as the total solar energy incident on the top of the atmos-
-- phere for a given latitude and longitude at a given time of year.
-- Comments are welcome addressed to
-- Bruce R. Barkstrom
-- 111 Pear Avenue
-- Newport News, VA 23607.
-- This program requires about 10k of text storage, and about 3.5k to run
-- when compiled by the CBASIC Version 2 compiler.
-- Original program published in Byte Magazine July 1981.
-- Transcribed to AppleScript by Nigel Garvey 4th/5th August 2013.
-- Requires the Satimage OSAX.
-- Further dabbling 6th-10th August 2013, including:
-- Improvements to FN.Print.Angle|() output formatting.
-- Change of time-zone input from zone number to ± hours from GMT.
-- Fix for rubbish times when time zones straddlle the Greenwich Meridian.
-- Above, and further testing by Nigel Garvey, other testing and code reading
-- of the user interface by Yvan Koenig with his keen eyes.
-- User interface, hopefully improved by McUsr
-- ****************************************
property parent : AppleScript
property |script name| : "SUNRISE - SUNSET"
property |version| : 2.3
property |First.Day.of.Month| : {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}
-- 0-based offsets.
property A : 1
property radians : pi / 180
property Debug : false
-- New properties for saving earlier obtained data
-- The convention is that we always ask to save
-- the last coordinates set Manually.
property savedPresets : false
property presetLongitude : missing value
property presetLatitude : missing value
property presetHourangle : missing value
property us_citizen : false
property decimalSep : missing value
property listSep : missing value
main()
(* Handlers for CBASIC functions which don't exist natively in AppleScript.
Some use the Satimage OSAX. *)
on _INT(N)
-- CBASIC's INT truncates rather than rounding down.
-- It should also return a floating-point number, but we dont want that in the output!
return N div 1
end _INT
on _SGN(N)
if (N < 0) then return -1
if (N = 0) then return 0
return 1
end _SGN
on _ABS(N)
-- CBASIC's ABS should return a floating-point number, but why change the original class?!
if (N < 0) then return -N
return N
end _ABS
on _SIN(R)
return sin R -- Satimage OSAX.
end _SIN
on _COS(R)
return cos R -- Satimage OSAX.
end _COS
on _TAN(R)
return tan R -- Satimage OSAX.
end _TAN
on _ATN(R)
return atan R -- Satimage OSAX
end _ATN
on _SQR(N)
-- CBASIC's SQR() warns when the parameter's negative and makes it positive.
if (N < 0) then
say "Square route of negative attempted."
set N to -N
end if
return N ^ 0.5
end _SQR
on _PRINT(txt)
local A
tell application "TextEdit"
activate
if ((count its documents) is 0) then make new document
set A to (get path of its front document)
try
if A is "" then set A to missing value
on error
set A to missing value
end try
if A is not missing value then make new document
make new paragraph at end of text of front document with data (txt & linefeed)
end tell
return true
end _PRINT
-- A couple of extra handlers to improve the AppleScript experience.
on pad(N)
-- 22. September 2013, improved pad, for the cases where n < 0 and n > 100.
-- Thanks to Yvan Koenig.
-- Fixed a bug
if N < -100 then
return N as text
else if N < 0 then
return ("-" & text -2 thru -1 of ("0" & ((N as number) * -1) as text))
-- error "The pad handler assumes positive values" number 5000
else if N < 100 then
return text 2 thru -1 of ((100 + N) as text)
else
return N as text
end if
end pad
on new_query_continue()
-- McUsr version 2.2 Coalesce the two dialogs into one.
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
return (button returned of (display dialog "Do you wish to see Sunset/Sunrise for another date for this location
or do you wish to get data for another Location?" buttons {"Quit", "Date", "Location"} cancel button 1 default button 2 with title |script name| with icon note) as text)
end tell
end timeout
end new_query_continue
(* The original program's subroutines. *)
-- Compute Julian Date.
on |FN.Julian.Date|(|Month|, |Day|, |Year|)
set |Yrs.since.0| to |Year| + 4712
set |No.of.lp.yrs| to _INT((|Yrs.since.0| - 1) / 4)
set |Julian.Date| to 365 * |Yrs.since.0| + |No.of.lp.yrs|
if (|Year| ≥ 1583) then
set |Julian.Date| to |Julian.Date| - 10
set |No.of.cent.yrs.snc.1583| to _INT((|Year| - 1501) / 100)
set |No.of.cent.lp.yrs.snc.1583| to _INT((|Year| - 1201) / 400)
set |Julian.Date| to |Julian.Date| - |No.of.cent.yrs.snc.1583| + |No.of.cent.lp.yrs.snc.1583|
end if
-- Deal with month and day.
set |Julian.Date| to |Julian.Date| + (item |Month| of |First.Day.of.Month|) + |Day|
if ((4 * (_INT(|Year| / 4)) = |Year|) and (|Month| ≥ 3)) then set |Julian.Date| to |Julian.Date| + 1
if ((|Year| = 1582) and (((|Month| = 10) and (day ≥ 15)) or (month ≥ 11))) then set |Julian.Date| to |Julian.Date| - 10
return |Julian.Date|
end |FN.Julian.Date|
-- Compute Mean Anomaly.
on |FN.M|(T, D)
set M00 to -1.52417 + (1.5E-4 + 3.0E-6 * T) * T * T + 0.985600267 * D
if (M00 > 360) then set M00 to M00 - 360 * (_INT(M00 / 360))
return M00 * radians
end |FN.M|
-- Compute Obliquity of Ecliptic.
on |FN.epsilon|(T)
return (23.452294 - (0.0130125 + (1.64E-6 - 5.03E-7 * T) * T) * T) * radians
end |FN.epsilon|
-- Compute Mean Longitude of Perigee.
on |FN.omega|(T, D)
return (281.22083 + (4.53E-4 + 3.0E-6 * T) * T * T + 4.70684E-5 * D) * radians
end |FN.omega|
-- Compute Eccentricity.
on |FN.eccentricity|(T)
return 0.01675104 - (4.08E-5 + 1.26E-7 * T) * T
end |FN.eccentricity|
-- Compute Longitude of Ascending Node of Lunar Oribt [sic!].
on |FN.Lunar.Long|(T, D)
set |Lunar.Long| to 259.183275 + (0.002078 + 2.0E-6 * T) * T * T
set |Lunar.Long| to |Lunar.Long| - 0.0529539222 * D
return |Lunar.Long| * radians
end |FN.Lunar.Long|
-- Print time or angle in xx:xx:xx.xxx format.
-- The 'Angle' parameter is assumed to be in radians.
on |FN.Print.Angle|(|time.or.angle|, y, Angle)
if (|time.or.angle| is "time") then
set {factor, y0} to {12, " Hours"}
else
set {factor, y0} to {180, " Degrees"}
end if
set x1 to factor * Angle / pi
if (x1 < 0) then
set x1 to -x1
set xSgn to "-"
else
set xSgn to ""
end if
set x2 to _INT(x1)
set x3 to 60 * (x1 - x2)
set x4 to _INT(x3)
set x5 to 60 * (x3 - x4)
set x6 to 1.0E-3 * (_INT(x5 * 1000))
if (|time.or.angle| is "time") then set x2 to pad(x2)
_PRINT(y & tab & tab & xSgn & x2 & ":" & pad(x4) & ":" & pad(x6) & y0)
end |FN.Print.Angle|
(* The original program's main body. *)
on main()
-- Input Location Information.
set Idne2 to true
set Err to false
set decimalSep to text 2 of ((1 / 2) as text)
if decimalSep is "," then
set listSep to ";"
else
set listSep to ","
end if
set inpMethod to "None So Far"
repeat while (Idne2)
repeat
try
if inpMethod is "None So Far" then
if not savedPresets then
-- implemented auto fetch of variables here
set {_latQuartet, _longQuartet} to getLocaleDetails()
set _stdTimeZone to ((time to GMT) / hours) as text
else
set _latQuartet to my presetLatitude
set _longQuartet to my presetLongitude
set {LatDir, LatD, LatM, LatS} to getValsFromQuartet(_latQuartet)
set {LongDir, LonD, LonM, LonS} to getValsFromQuartet(_longQuartet)
set _stdTimeZone to my presetHourangle
set |Std.Time.Zone| to my presetHourangle
end if
set inpMethod to _QUERY_INPUT_METHOD(savedPresets)
end if
if inpMethod is false then
error number -128
else if inpMethod is "From Nearest City" then
set {LatDir, LatD, LatM, LatS} to getValsFromQuartet(_latQuartet)
set {LongDir, LonD, LonM, LonS} to getValsFromQuartet(_longQuartet)
set |Std.Time.Zone| to _stdTimeZone as number
else if inpMethod is "My Saved Properties" then
set _latQuartet to my presetLatitude
set _longQuartet to my presetLongitude
set {LatDir, LatD, LatM, LatS} to getValsFromQuartet(_latQuartet)
set {LongDir, LonD, LonM, LonS} to getValsFromQuartet(_longQuartet)
set _stdTimeZone to my presetHourangle
set |Std.Time.Zone| to my presetHourangle
else
-- we get latitude, normalize any coordinates, and performs the first checks.
tell (_COORDINPUT("N or S latitude ( 0 - 90 Deg,Min,Sec)", (_latQuartet as text), {"N", "S"}, |script name|))
set latTriplet to degreeTriplet of me from it
if item 1 of it is "S" then
if item 1 of latTriplet does not start with "-" then
set item 1 of latTriplet to ("-" & (item 1 of latTriplet)) -- Thanks to Yvan Koenig.
end if
end if
end tell
if not Err then
set normLatTriplet to hexagesNormalize for latTriplet
set _latQuartet to aMapNotationQuartet from normLatTriplet by {"N", "S"}
set {LatDir, LatD, LatM, LatS} to getValsFromQuartet(_latQuartet)
tell (_COORDINPUT("E or W, Longitude (0 - 180 Deg,Min,Sec)", (_longQuartet as text), {"E", "W"}, |script name|))
set longTriplet to degreeTriplet of me from it
if item 1 of it is "W" then
if item 1 of longTriplet does not start with "-" then
set item 1 of longTriplet to ("-" & (item 1 of longTriplet)) -- Thanks to Yvan Koenig.
end if
end if
end tell
if not Err then
set normLongTriplet to hexagesNormalize for longTriplet
set _longQuartet to aMapNotationQuartet from normLongTriplet by {"E", "W"}
-- A normalization, since we have kept the "-", to get the right hemisphere.
set {LongDir, LonD, LonM, LonS} to getValsFromQuartet(_longQuartet)
-- New: the user input for the time zone is now ± hours from GMT instead of a number from 1 to 24.
set {|Std.Time.Zone|} to ¬
_INPUT_HOUR_ANGLE("Your Standard or Summer Time Zone in hours from GMT (-12 to +14). Use " & decimalSep & "5 or " & ¬
decimalSep & "75 where the offset includes an odd 30 or 45 minutes.", _stdTimeZone, |script name|)
set _stdTimeZone to |Std.Time.Zone|
end if
end if
end if
if not Err then
-- 7 sep. 2013 moved down, so the work with standard arguments
set Latitude to LatD + (LatM + (LatS / 60)) / 60
if LatDir is "S" then set Latitude to Latitude * -1
if ((Latitude < -90) or (Latitude > 90)) then set Err to _PRINT("Latitude out of range (-90,90)")
set Longitude to LonD + (LonM + (LonS / 60)) / 60
if ((Longitude < 0.0) or (Longitude > 180)) then set Err to _PRINT("Longitude outside the range (0,180)")
-- New: the allowed time-zone range is now -12 to +14 instead of 1 to 24.
if ((|Std.Time.Zone| < -12) or (|Std.Time.Zone| > 14)) then set Err to ¬
_PRINT("Std Time Zone outside the range (-12,14)")
-- New: convert the ±-hours-from-GMT time-zone input to the original program's 1-to-24,
-- west-from-Greenwich numbering system.
set |Std.Time.Zone| to ((24 - |Std.Time.Zone|) mod 24 + 1)
-- New: if the location's on the other side of the Greenwich Meridian from its nominal time zone,
-- renumber the zone as a range extension on the location's side of the Meridian.
-- This corrects the sunrise and sunset time calculations.
if (LongDir is "W") then -- Zone 24 becomes zone 0; zone 23 becomes zone -1.
if (|Std.Time.Zone| > 22) then set |Std.Time.Zone| to |Std.Time.Zone| - 24
else -- (LongDir is "E") -- Zone 1 becomes zone 25.
if (|Std.Time.Zone| is 1) then set |Std.Time.Zone| to 25
end if
else
error number -128
end if
if (not Err) then exit repeat
on error number -128
error number -128
end try
end repeat
-- Revise longitude and latitude and standard time zone to be consistent.
set Latitude to Latitude * radians
if (Latitude < 0) then
set x to "Antarctic"
else
set x to "Arctic"
end if
if (LongDir is "E") then set Longitude to 360 - Longitude
set Longitude to Longitude * radians
set |Time.Diff| to 12 * Longitude / pi
set |Tot.time.diff| to |Time.Diff| - (|Std.Time.Zone| - 1)
-- Input Date and Check for Correctness.
set Idne to true
if us_citizen then
set dtSpecifyString to "(Month-Day-Year)"
else
set dtSpecifyString to "(Day-Month-Year)"
end if
set _dtNow to _NOW(us_citizen)
repeat while (Idne)
-- Adding localization of date formats here, NOT regarding separator, but regarding the ordering
-- US Citizens are accustomed to enter Month, Day, Year, and not Day Month Year as the rest of
-- The Civil world
if us_citizen then
set {|Month|, |Day|, |Year|} to _INPUT_DATE("Date " & dtSpecifyString, _dtNow, |script name|, true, us_citizen, "PositiveDate")
else
set {|Day|, |Month|, |Year|} to _INPUT_DATE("Date " & dtSpecifyString, _dtNow, |script name|, true, us_citizen, "PositiveDate")
end if
set endRangeList to _INPUT_DATE("Enter end date " & "Date " & dtSpecifyString & " or \"Cancel\" if you just want data for one Date.", _dtNow, |script name|, false, us_citizen, "PositiveDate")
set |AdjustedDay| to |Day| - 0.5 + |Time.Diff| / 24
-- Compute current Julian Date, and time since 1900.
set |J.D_Current| to |FN.Julian.Date|(|Month|, |AdjustedDay|, |Year|)
if endRangeList is not false then
if us_citizen then
set {|EndMonth|, |EndDay|, |EndYear|} to endRangeList
else
set {|EndDay|, |EndMonth|, |EndYear|} to endRangeList
end if
set |AdjustedEndDay| to |EndDay| - 0.5 + |Time.Diff| / 24
set |J.D_EndDay| to |FN.Julian.Date|(|EndMonth|, |AdjustedEndDay|, |EndYear|)
if |J.D_EndDay| < |J.D_Current| then -- swap them
set tmp to |J.D_Current|
set |J.D_Current| to |J.D_EndDay|
set |J.D_EndDay| to tmp
end if
set |J.D_EndDay| to |J.D_EndDay| + 1
else
set |J.D_EndDay| to |J.D_Current| + 1
end if
-- NEW: print the coordinates and date at the top of each entry.
_PRINT("LATITUDE: " & (LatDir & " " & pad(_ABS(LatD)) & ":" & pad(_ABS(LatM)) & ":" & pad(_ABS(LatS)) & tab) ¬
& "LONGITUDE: " & (LongDir & " " & pad(_ABS(LonD)) & ":" & pad(_ABS(LonM)) & ":" & pad(_ABS(LonS)) & linefeed))
repeat while ((|J.D_EndDay| - |J.D_Current|) > 0)
-- START OF REPORT
if us_citizen then
_PRINT("DATE: " & (pad(|Month|) & "/" & pad(|Day|) & "/" & |Year|) & linefeed)
else
_PRINT("DATE: " & (pad(|Day|) & "/" & pad(|Month|) & "/" & |Year|) & linefeed)
end if
set D to |J.D_Current| - |FN.Julian.Date|(1, 1, 1900)
set T to D / 36525
-- Compute solar orbit.
set ecc to |FN.eccentricity|(T)
set M0 to |FN.M|(T, D) -- Radians
set E to M0
repeat 3 times
set E to E + (M0 - (E - ecc * (_SIN(E)))) / (1 - ecc * (_COS(E)))
end repeat
set V to 2 * (_ATN(_SQR((1 + ecc) / (1 - ecc)) * (_TAN(0.5 * E))))
if (V < 0) then set V to V + 2 * pi
set R to A * (1 - ecc * (_COS(E)))
set eps to |FN.epsilon|(T) -- Radians
set omeg to |FN.omega|(T, D) -- Radians
-- Nutation Terms are computed here.
set L1 to |FN.Lunar.Long|(T, D) -- Radians
set |Nutation.of.Obliquity| to (0.0025583333 + 2.5E-7 * T) * (_COS(L1)) * radians
set eps to eps + |Nutation.of.Obliquity|
set |Nutation.of.Longitude| to -(0.0047872222 + 4.72222222E-6 * T) * (_SIN(L1)) * radians
-- Compute solar declination.
set |sine.del| to _SIN(eps) * (_SIN(V + omeg))
set |cosine.del| to _SQR(1 - |sine.del| * |sine.del|)
set del to _ATN(|sine.del| / |cosine.del|)
-- Compute Equation of Time.
set |mean.long| to omeg + M0
if (|mean.long| < 0) then set |mean.long| to |mean.long| + 2 * pi
if (|mean.long| > 2 * pi) then set |mean.long| to |mean.long| - 2 * pi * (_INT(|mean.long| / (2 * pi)))
set y to _TAN(0.5 * eps)
set y to y * y
set y to (1 - y) / (1 + y)
set alpha0 to omeg + V + |Nutation.of.Longitude|
if (alpha0 < 0) then set alpha0 to alpha0 + 2 * pi
if (alpha0 > 2 * pi) then set alpha0 to alpha0 - 2 * pi * (_INT(alpha0 / (2 * pi)))
set alpha to _ATN(y * (_TAN(alpha0)))
set |Eqn.of.time| to alpha - |mean.long|
set |Eqn.of.time| to |Eqn.of.time| - pi * (_INT(|Eqn.of.time| / pi))
if (_ABS(|Eqn.of.time|) > 0.9 * pi) then set |Eqn.of.time| to |Eqn.of.time| - _SGN(|Eqn.of.time|) * pi
set a0 to |Eqn.of.time| + |mean.long|
if (a0 > 2 * pi) then set a0 to a0 - 2 * pi * (_INT(a0 / (2 * pi)))
-- Print various orbital related quantities if desired.
if endRangeList is false then
|FN.Print.Angle|("angle", "mean anomaly ", M0)
_PRINT(linefeed)
|FN.Print.Angle|("angle", "eccentric anom", E)
|FN.Print.Angle|("angle", "true anomaly ", V)
|FN.Print.Angle|("angle", "obliquity ", eps)
|FN.Print.Angle|("angle", "nutation of ob", |Nutation.of.Obliquity|)
|FN.Print.Angle|("angle", "longitude ", alpha0)
|FN.Print.Angle|("angle", "nutation of ln", |Nutation.of.Longitude|)
|FN.Print.Angle|("angle", "solar declin ", del)
|FN.Print.Angle|("time", "solar R.A. ", a0)
|FN.Print.Angle|("time", "equation of tm", |Eqn.of.time|)
_PRINT("eccentricity " & ecc)
_PRINT("r " & R)
_PRINT(linefeed)
end if
-- Length of Day.
set mum to _COS(Latitude - del)
set mun to -(_COS(Latitude + del))
set mua to 0
-- Refraction Effect computed here.
if (Debug and endRangeList is false) then
|FN.Print.Angle|("time", "Total time difference", |Tot.time.diff| * pi / 12)
end if
if (-mum * mun > 0) then
set |Refrac.corr| to 0.0555555556 / (_SQR(-mum * mun))
else
set |Refrac.corr| to 0
_PRINT("The sun's upper limb does not cross the horizon.")
end if
if (Debug and endRangeList is false) then |FN.Print.Angle|("time", "Refraction corr is ", |Refrac.corr| * pi / 12)
if (mun > mua) then set mua to mun
if (mum > mua) then
set x to _SQR((mua - mun) / (mum - mua))
set |frac.of.day.sun.up| to 1 - (2 / pi) * (_ATN(x))
set |basic.sunset| to 12.0 * |frac.of.day.sun.up|
set |basic.sunrise| to |basic.sunset|
set |basic.sunset| to |basic.sunset| + |Refrac.corr| + |Eqn.of.time| * 12 / pi
set |basic.sunrise| to |basic.sunrise| + |Refrac.corr| - |Eqn.of.time| * 12 / pi
set |time.basic.sunset| to 12 + |basic.sunset|
set |time.basic.sunrise| to 12 - |basic.sunrise|
set |time.sunrise| to |time.basic.sunrise| + |Tot.time.diff|
set |time.sunset| to |time.basic.sunset| + |Tot.time.diff|
set |fraction.avail.sun| to 0.5 * ((mum + mun) * |frac.of.day.sun.up| + (mum - mun) * (_SIN(pi * |frac.of.day.sun.up|)) / pi)
-- PRINT:PRINT:\
|FN.Print.Angle|("time", "Sunrise occurs at ", |time.sunrise| * pi / 12)
|FN.Print.Angle|("time", "Sunset occurs at ", |time.sunset| * pi / 12)
|FN.Print.Angle|("time", "Hours of daylight ", (|time.sunset| - |time.sunrise|) * pi / 12)
else
_PRINT("You are in the " & x & " winter - the sun doesn't rise.")
set |fraction.avail.sun| to 0
end if
if (Debug) then _PRINT("Hour angle = " & |Std.Time.Zone|)
if (endRangeList is false) then _PRINT(linefeed & "Sunlight available at the top of the atmosphere is " & 1.188864E+8 * |fraction.avail.sun| & " Joules per square metre.")
_PRINT(linefeed)
set |J.D_Current| to |J.D_Current| + 1
set {|Year|, |Month|, |Day|} to gregorianCalendarTriplet from |J.D_Current|
end repeat
-- END OF REPORT
if inpMethod is "Manually" then
-- Talkative approach, but it is really best to ask if wants to save properties
-- after a result has been displayed.
set btnReply to PROMPT_FOR_SAVING_PROPERTIES()
if btnReply is "Save" then
set my presetLatitude to _latQuartet
set my presetLongitude to _longQuartet
set my presetHourangle to ((24 - (|Std.Time.Zone| - 1)) mod 24)
set my savedPresets to true
else if btnReply is "Quit" then
error number -128
end if
end if
set btnReply to new_query_continue()
if btnReply is "Quit" then
set {Idne, Idne2} to {false, false}
else if btnReply is "Location" then
set inpMethod to "Manually"
set {Idne, Idne2} to {false, true}
else
set inpMethod to "Previous Vals"
-- but we are to enter new dates, so we'll start with the last one entered.
if us_citizen then
set _dtNow to "" & |Month| & "-" & |Day| & "-" & |Year|
else
set _dtNow to "" & |Day| & "-" & |Month| & "-" & |Year|
end if
set {Idne, Idne2} to {true, false}
end if
end repeat
end repeat
end main
-- New handlers, introduced by McUsr in order to "tidy" up the UI.
on _QUERY_INPUT_METHOD(fromSavedProperties)
-- for some reason this won't work from within Script Debugger
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
try
if not fromSavedProperties then
set btnMethod to button returned of (display dialog "Do you want to enter coordinates, or use the ones set for nearest City by your Date and Time Preferences?" with title |script name| buttons {"Cancel", "From Nearest City", "Manually"} cancel button 1 default button 2 with icon note)
else
set btnMethod to button returned of (display dialog "Do you want to enter coordinates, or use the ones which you have previously saved?" with title |script name| buttons {"Cancel", "My Saved Properties", "Manually"} cancel button 1 default button 2 with icon note)
end if
on error
set btnMethod to "Cancel"
end try
end tell
end timeout
if btnMethod = "Cancel" then
return false
else
return btnMethod
end if
end _QUERY_INPUT_METHOD
-- 15th September 2013
-- Now serves as an input handler for both date and hour-angle.
-- Changed a wee-bit, to return an empty string if there wasn't anything to return.
-- 22th September, adding way to return when cancelling input isn't fatal
-- 28th September, added localization of decimal and list separator, and
-- added a parameter for telling about input of degrees, in which all terms
-- should be negative, and not only the first.
-- 28th September, changed one more time, to handle dateinput, where "-" is an
-- eminent separator. the added listType can be one of three:
-- Regular : this means that the number list is treated as a raw list of numbers
-- both negative and positive numbers are valid.
-- PositiveDate : this means that the three numbers can be separated with "-"
-- EveryDate : also negative dates, this means that we can't use the "-" as
-- a separator but have to resort to ";" "," or "/".
-- Triplet : If only one of the numbers is prepended by a "-", then all three numbers
-- are made to be negative quantities. Good for hour min, sec, and deg min sec
-- Changed name of the handler to ListInput
on _LIST_INPUT(txt, defaAns, isWarning, scriptName, stayAlive, listType)
-- The core of the handler originally written by Nigel Garvey.
-- Parameters:
-- txt: Text: The message that is shown in the input dialog.
-- defaAns: Text: The default value that is show in the input field.
-- isWarning: Boolean: If set to true, then a caution icon is shown, not
-- a note icon which is default.
-- ScriptName: Text: Dialog box title, not necessarily a script name.
-- stayAlive: Boolean: If true the script returns missing value, otherwise
-- dies.
-- listType: Word: Word can be: "Regular", "PositiveDate", "EveryDate"
-- or "Triplet","Hours".
-- Returns:
-- A list with values on success.
-- Missing value if the user canceled and stayAlive is true.
-- {""} if the input was bad somehow.
-- Dies directly if the user cancelled and the stayalive was false.
--
-- If you have this handler in a tell block, then you want to wrap
-- That tell block into a try block to "smooth over" the error number -128 call.
local inpt, btnList, isNegative, astid, outpt, digits, param, listSep
-- Thanks to Yvan Koenig for tips on increasing robustness!
if listType is not in {"Regular", "PositiveDate", "EveryDate", "Triplet", "Hours"} then ¬
error "Bad value for listType" number 4015
set inpt to missing value
# Configures list separator based on decimal separator: ";" <-- "," and "," <-- "."
# A list separator always works, as do spaces between elements.
# A "-" works for dates , when dates are restricted to positive dates.
# A "." works for separatring elements of dates and times too.
# A "/" works for separating dates.
# A ":" works for separating hours.
# A list of regular numbers can only be separated with space and the designated list separator.
# Counting of the elements happens in the caller of this handler.
if text 2 of ((1 / 2) as text) is "," then
set listSep to ";"
else
set listSep to ","
end if
# Appropriate buttons for whether we shall stay alive or not.
if stayAlive then
set btnList to {"Cancel", "Ok"}
else
set btnList to {"Quit", "Ok"}
end if
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
if isWarning then
try
set inpt to text returned of (display dialog txt default answer defaAns with title scriptName buttons btnList with icon caution cancel button 1 default button 2)
end try
else
try
set inpt to text returned of (display dialog txt default answer defaAns with title scriptName buttons btnList with icon note cancel button 1 default button 2)
end try
end if
end tell
end timeout
if inpt is missing value then
if not Debug and not stayAlive then -- Need to stay alive here, if we are entering the second date.
error number -128 -- User hit cancel.
else
return missing value -- Thanks to Yvan Koenig.
end if
end if
if inpt is "" then return {}
if listType is "Triplet" then
ignoring white space
set isNegative to (inpt begins with "-")
end ignoring
else if listType is "EveryDate" then
set isNegative to (inpt contains "-")
else
set isNegative to false
end if
log "" & inpt
set astid to AppleScript's text item delimiters
set titems to {"-", listSep, space}
if listType is not in {"PositiveDate", "Triplet"} then set titems to rest of titems
if listType is in {"PositiveDate", "EveryDate"} then set titems to titems & {".", "/"}
if listType is in {"Triplet", "Hours"} then set end of titems to ":"
set AppleScript's text item delimiters to titems
if listType is in {"Triplet", "EveryDate"} then
set outpt to inpt's words
else
set outpt to inpt's text items
end if
# set AppleScript's text item delimiters to " "
# set inpt to inpt as text
set AppleScript's text item delimiters to astid
set reslist to {}
# set outpt to inpt's words
set digits to "1234567890"
repeat with i from 1 to (count outpt)
set param to item i of outpt
if param is not "" and (character 1 of param is in digits or character 1 of param is "-") then
try -- Thanks to Yvan Koenig.
set param to param as number
on error
if listType is not in {"PositiveDate", "EveryDate"} then
# Can't have decimal fraction anyway
try
set AppleScript's text item delimiters to "."
set param to text items of param
set AppleScript's text item delimiters to ","
set param to param as text
set AppleScript's text item delimiters to astid
set param to param as number
on error
return {""} -- this must be resolved by caller
end try
else
return {""}
end if
end try
if (isNegative) then # This works only for triplets
if listType is in {"EveryDate", "Triplet"} then set param to -param
end if
set end of reslist to param
end if
end repeat
return reslist
end _LIST_INPUT
to PROMPT_FOR_SAVING_PROPERTIES()
-- Asks for saving Properties when they have been input manually.
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
try
return (button returned of (display dialog "Do you wish to save the coordinates you have entered as standard coordinates?" buttons {"Quit", "Never Mind", "Save"} cancel button 1 default button 2 with title |script name| with icon note))
on error
return ("Quit")
end try
end tell
end timeout
end PROMPT_FOR_SAVING_PROPERTIES
-- handler that returns a list of latidude and longitude coordinates,
-- from a quartet: there should really be 4 elements in it.
to getValsFromQuartet(strQuartet)
local _tids, mlist
tell (a reference to AppleScript's text item delimiters)
set _tids to contents of it
set contents of it to space
tell (strQuartet)
if (count text items of it) < 4 then
set AppleScript's text item delimiters to _tids
error "Error: There are too few elements in the Quartet:" number 6010
-- Thanks to Yvan Koenig for spelling corrections! :)
else
set mlist to text items of it
end if
end tell
set contents of it to _tids
end tell
return mlist
end getValsFromQuartet
-- returns true if the day appears within a month
-- http://www.celestrak.com/columns/v02n02/
to validDate(dy, mo, YR)
set daynumbers to {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
if mo < 1 or mo > 12 then
return false
else if mo = 2 then
if ((YR mod 4) = 0) and (((YR mod 100) ≠0) or ((YR mod 400) = 0)) and (mo > 2) then
set item 2 of daynumbers to 29
end if
end if
if dy < 1 or dy > item mo of daynumbers then
return false
else
return true
end if
end validDate
-- 22th September:
-- changing the handler to divert between entry of a mandatory date, and one that isn't
-- so we have an easy way, to enter a range of dates, if the user enters a second date,
-- but can resort to the first, if the user defaulted the second dialog.
on _INPUT_DATE(txt, defaAns, scriptName, mandatory, us_origin, dateRange)
# http://wiki.collectiveaccess.org/wiki/Date_and_Time_Formats
local defaulted, missing_args, areNumbers, aValidDate, fullySpecified, positiveNumbers, theDateL
set theDateL to _LIST_INPUT(txt, defaAns, false, scriptName, (not mandatory), dateRange)
-- if the date is a mandatory one, then stayalive is false when calling _INPUT
if theDateL is missing value then
if mandatory then
-- then something is done if Debug is true
if (Debug) then _PRINT("User cancelled during input of a date.")
error number -128 -- User hit cancel.
else
return false
end if
end if
set {areNumbers, aValidDate, fullySpecified, positiveNumbers} to {true, true, true, true}
if theDateL = {} then
set {defaulted, missing_args} to {true, true}
else
set {defaulted, missing_args} to {false, false}
end if
repeat while true
if defaulted then
if missing_args then
set errStr to "A value is mandatory."
set defaultValues to defaAns
else if not areNumbers then
set errStr to "A Date is in numbers only."
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "-"}
set defaultValues to theDateL as text
set AppleScript's text item delimiters to tids
else if not fullySpecified then
if us_origin then
set errStr to "We need Day,Month,Year."
else
set errStr to "We need Month,Day,Year."
end if
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "-"}
set defaultValues to theDateL as text
set AppleScript's text item delimiters to tids
else if not positiveNumbers then
set errStr to "A Date hasn't negative numbers."
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "-"}
set defaultValues to theDateL as text
set AppleScript's text item delimiters to tids
else if not aValidDate then
set errStr to "That date doesn't exist."
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, "-"}
set defaultValues to theDateL as text
set AppleScript's text item delimiters to tids
end if
set theDateL to _LIST_INPUT(errStr & return & return & txt, defaultValues, true, scriptName, (not mandatory), dateRange)
-- if the date is a mandatory one, then stayalive is false when calling _INPUT
if theDateL is missing value then
if mandatory then
-- then something is done if Debug is true
if (Debug) then _PRINT("User cancelled during input of a date.")
error number -128 -- User hit cancel.
else
return false
end if
end if
set {defaulted, missing_args, areNumbers, aValidDate, fullySpecified, positiveNumbers} to {false, false, true, true, true, true}
end if
local probe
if theDateL ≠{} then
if length of theDateL < 3 then set {defaulted, fullySpecified} to {true, false}
if fullySpecified then
repeat with i from 1 to 3
set probe to item i of theDateL as number
try
if dateRange is "PositiveDate" and probe < 0 then
set {defaulted, positiveNumbers} to {true, false}
exit repeat
end if
on error
set {defaulted, areNumbers} to {true, false}
exit repeat
end try
end repeat
if not defaulted then
if not us_origin then
if not validDate(item 1 of theDateL, item 2 of theDateL, item 3 of theDateL) then
set {defaulted, aValidDate} to {true, false}
end if
else
if not validDate(item 2 of theDateL, item 1 of theDateL, item 3 of theDateL) then
set {defaulted, aValidDate} to {true, false}
end if
end if
end if
end if
else
set {defaulted, missing_args} to {true, true}
end if
if not defaulted then exit repeat
end repeat
return theDateL
end _INPUT_DATE
on _INPUT_HOUR_ANGLE(txt, defaAns, scriptName)
local tids, defaulted, missing_args, isNumber, withinRange, theTz, elmList
set theTz to missing value
try
set theTz to _LIST_INPUT(txt, defaAns, false, scriptName, false, "Regular")
end try
if theTz is missing value then
if Debug then _PRINT("User cancelled during input of hour angle.")
error number -128 -- User hit cancel.
else
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, ","}
set elmList to text items of theTz
set theTz to theTz as text
set AppleScript's text item delimiters to tids
end if
if (count elmList) = 1 or text 1 of (item 1 of elmList as text) is not "-" then
set theTz to theTz as number
else
set theTz to ("" & item 1 of elmList & "," & text 2 thru -1 of (item 2 of elmList as text)) as number
end if
set {isNumber, withinRange} to {true, true}
if theTz = "" then
set {defaulted, missing_args} to {true, true}
else
set {defaulted, missing_args} to {false, false}
end if
repeat while true
if defaulted then
if missing_args then
set errStr to "A value is mandatory."
set defaultValues to defaAns
else if not isNumber then
set errStr to "The Time Zone must be a number."
set defaultValues to theTz
else if not withinRange then
set errStr to "Time Zone beyond the valid range."
set defaultValues to theTz
end if
set {theTz} to _LIST_INPUT(errStr & return & return & txt, defaAns, true, scriptName, false, "Regular")
if theTz is missing value then
if Debug then _PRINT("User cancelled during editing of hour angle.")
error number -128 -- User hit cancel.
end if
set {defaulted, missing_args, isNumber, withinRange} to {false, false, true, true}
end if
if theTz ≠"" then
try
set param to theTz as number
on error
set {defaulted, isNumber} to {true, false}
error "couldn't make a number of it" number 4000
end try
if isNumber then
if param < -12 or param > 14 then
set {defaulted, withinRange} to {true, false}
end if
end if
else
set {defaulted, missing_args} to {true, true}
end if
if not defaulted then exit repeat
end repeat
return {theTz}
end _INPUT_HOUR_ANGLE
-- 10 September 2013 : Reworked version of _INPUT above, specialized for handling coordinates.
-- 15 Sepetember 2013 : Enforced robustness.
-- You'll have to get the directions right, and in the right positions,
-- the rest of the arguments must be numbers valid numbers
-- there must be at least one number, ( a degree )
-- the number can't be less than -91 nor greater than 90 if latitude.
-- not be less than -181 nor greater than 180 if longitude.
-- any missing zeroes will be filled in.
on _COORDINPUT(txt, defaAns, directions, scriptName)
local defaulted, missing_args, areNumbers, withinRange, correctDirection, inpt, tids
-- Thanks to Yvan Koenig for tips for increasing the robustness of the dialog.
set inpt to missing value
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
try
set inpt to text returned of (display dialog txt default answer defaAns with title scriptName buttons {"Quit", "OK"} with icon note cancel button 1 default button 2)
end try
end tell
end timeout
if inpt is missing value then
if Debug then _PRINT("User cancelled during first entry of coordinates.")
error number -128 -- User hit cancel.
end if
set {areNumbers, withinRange, correctDirection} to {true, true, true}
if inpt = "" then
set {defaulted, missing_args} to {true, true}
else
set {defaulted, missing_args} to {false, false}
end if
repeat while true
if defaulted then
if missing_args then
set errStr to "Input is Mandatory."
set defaultValues to defaAns
else if not areNumbers then
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
set defaultValues to inpt as text
set AppleScript's text item delimiters to tids
set errStr to "Deg,Min,Sec are numbers."
else if not withinRange then
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
set defaultValues to inpt as text
set AppleScript's text item delimiters to tids
set errStr to "The degrees are not within range:"
if item 1 of directions is "N" then
set errStr to errStr & space & "-91.0 < Deg < 90."
else
set errStr to errStr & space & "-181.0 < Deg < 180."
end if
else if not correctDirection then
set errStr to "The direction can only be: " & item 1 of directions & "/" & item 2 of directions & "."
set {tids, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
set defaultValues to inpt as text
set AppleScript's text item delimiters to tids
end if
with timeout of (10 * minutes) seconds
tell application (path to frontmost application as text)
set inpt to text returned of (display dialog (errStr & return & return & txt) default answer defaultValues with title scriptName buttons {"Quit", "OK"} with icon caution cancel button 1 default button 2)
end tell
end timeout
if inpt is missing value then
if Debug then _PRINT("User cancelled during editing of coordinates.")
error number -128 -- User hit cancel.
end if
set {defaulted, missing_args, areNumbers, withinRange, correctDirection} to {false, false, true, true, true}
end if
if inpt ≠"" then
set tids to AppleScript's text item delimiters
set AppleScript's text item delimiters to {",", ":", "/", ".", space, tab, "|"}
set inpt to inpt's text items
set AppleScript's text item delimiters to tids
set digits to "1234567890"
set outp to {}
set num_inptArgs to (count inpt)
if item num_inptArgs of inpt = "" then set num_inptArgs to num_inptArgs - 1
if num_inptArgs < 2 then
set defaulted to true
set missing_args to true
else
set areNumbers to true
repeat with i from 1 to num_inptArgs
if i = 1 then
if item 1 of inpt is in directions then
set end of outp to item 1 of inpt
else
set correctDirection to false
exit repeat
end if
else if item i of inpt = "" then -- thanks to Yvan Koenig.
else
ignoring white space
set isNegative to (item i of inpt begins with "-")
end ignoring
set param to word 1 of item i of inpt
if (character 1 of param is in digits) then
set param to param as number
-- checks to see if the parameter is within the range
if (isNegative) then
set param to -param
if i = 2 and item 1 of directions = "N" then
if param ≤ -91.0 then
set withinRange to false
exit repeat
end if
else if i = 2 and item 1 of directions = "E" then
if param ≤ -181.0 then
set withinRange to false
exit repeat
end if
end if
else if i = 2 and item 1 of directions = "N" then
if param > 90.0 then
set withinRange to false
exit repeat
end if
else if i = 2 and item 1 of directions = "E" then
if param > 180.0 then
set withinRange to false
exit repeat
end if
end if
if i > 2 and i < 4 then -- We "format" the two intermediary terms of the quartet.
if isNegative then
set end of outp to "-" & my pad(-param) -- thanks to Yvank Koenig.
else if i > 2 then
set end of outp to my pad(param) -- thanks to Yvank Koenig.
-- It may part of a negative triplet.
end if
else
set end of outp to (param as text)
end if
else
set areNumbers to false
end if
end if
end repeat
if areNumbers and withinRange and correctDirection then
repeat with i from (count outp) to 3
set end of outp to "00"
end repeat
exit repeat
else
set defaulted to true
end if
end if
else
set {defaulted, missing_args} to {true, true}
end if
end repeat
return outp
end _COORDINPUT
to getLocaleDetails()
try
set localeDetails to text 1 thru -2 of (do shell script "export pwhere=$(readlink /etc/localtime |sed -n 's_/[^/]*/[^/]*/[^/]*/\\([^/]*\\)/\\([^/]*\\).*_\\1.\\2_p');(sed -ne '/'\"$pwhere\"'/ s/^[^-+]*\\([-+][[:digit:]]*\\)[^+-]*\\([-+][[:digit:]]*\\).*/\\1 \\2/p' </usr/share/zoneinfo/zone.tab ;defaults read .GlobalPreferences AppleLocale 2>/dev/null || echo en_US) |tr '
' ' '")
-- latitude space longitude space locale space
on error
error "Error: Something went uttlerly wrong while retrieving localeDetails:" number 5999
end try
tell (a reference to AppleScript's text item delimiters)
set _tids to contents of it
set contents of it to space
tell (localeDetails)
if (count text items of it) < 3 then
set AppleScript's text item delimiters to _tids
error "Error: There are too few main elements in the localeDetails:" number 6010
-- Thanks to Yvan Koenig for spelling corrections! :)
else
set {LatString, LongString, localeString} to text items of it
end if
end tell
set contents of it to _tids
end tell
if localeString is "en_US" then set my us_citizen to true
return ({(transformCoordString for LatString by 5), (transformCoordString for LongString by 6)})
end getLocaleDetails
to transformCoordString for aRawString by minimumLength
local TheDegree, theMinutes, theSeconds, Direction, degreeString
tell (aRawString)
try
if length of it is minimumLength and minimumLength is 5 then
set {TheDegree, theMinutes} to {(text 1 thru 3 of it), (text 4 thru -1 of it)}
if TheDegree begins with "-" then
set Direction to "S"
else
set Direction to "N"
end if
set degreeString to (Direction & space & text 2 thru -1 of TheDegree & space & theMinutes & space & "00")
else if length of it is minimumLength and minimumLength is 6 then
set {TheDegree, theMinutes} to {(text 1 thru 4 of it), (text 5 thru -1 of it)}
if TheDegree begins with "-" then
set Direction to "W"
else
set Direction to "E"
end if
set degreeString to (Direction & space & text 2 thru -1 of TheDegree & space & theMinutes & space & "00")
else if minimumLength is 5 then -- length of it is 8 Thanks to kel1 fo showing me.
set {TheDegree, theMinutes, theSeconds} to {(text 1 thru 3 of it), (text 4 thru 5 of it), (text 6 thru -1 of it)}
if TheDegree begins with "-" then
set Direction to "S"
else
set Direction to "N"
end if
-- Thanks to Nigel Garvey for pointing out the bug here.
set degreeString to (Direction & space & text 2 thru -1 of TheDegree & space & theMinutes & space & theSeconds)
else
set {TheDegree, theMinutes, theSeconds} to {(text 1 thru 4 of it), (text 5 thru 6 of it), (text 7 thru -1 of it)}
if TheDegree begins with "-" then
set Direction to "W"
else
set Direction to "E"
end if
set degreeString to (Direction & space & text 2 thru -1 of TheDegree & space & theMinutes & space & theSeconds)
end if
on error
error "transformCoordString: something is very wrong, do you have a some customized coord-setting?" number 5000
end try
end tell
return degreeString
end transformCoordString
--
on _NOW(us_origin)
-- Thanks to Yvan Koenig for optimzation.
tell (current date)
if us_origin then
return ((("" & (its month as integer) as text) & "-" & its day as text) & "-" & its year)
else
return ((("" & its day as text) & "-" & (its month as integer) as text) & "-" & its year)
end if
end tell
end _NOW
to degreeTriplet from aMapNotationQuartet
-- length considerations
if (length of aMapNotationQuartet < 4) then
error "degreeTriplet: incomplete quartet, Lacks one or more of: dir, deg ,min, sec (4 terms)." number 4000
end if
tell (aMapNotationQuartet)
set dmsTriplet to rest of it
if item 1 of it is "S" or item 1 of it is "W" then
set item 1 of dmsTriplet to -(item 1 of dmsTriplet) as text -- Thanks to Yvan Koenig.
end if
return dmsTriplet
end tell
end degreeTriplet
-- The idea is simple, the first character in the tuple
-- is for the positive direction, and the other for the
-- negative, it is supposed to work with a normalized
-- Angle, (only first term to right negative, and
-- {0≤d<360, 0≤m<60, 0≤s<60 }
-- If we get a negative coordinate, then we choose the
-- second directon, we remove the negative degrees when
-- we have set the direction.
to aMapNotationQuartet from aDegreeTriplet by aDirectionTuple
if (length of aDegreeTriplet < 3) then ¬
error "aMapNotationQuartet: incomplete triplet, Lacks one or more of: deg ,min, sec (3 terms)." number 4000
if (length of aDirectionTuple < 2) then ¬
error "aMapNotationQuartet: incomplete tuplet, Lacks one or more of: {\"N\",\"S\"} or {\"E\",\"W\"}(2 terms)." number 4000
local probe, newQuartet, mark
set newQuartet to {"", "", "", ""}
tell aDegreeTriplet
set probe to item 1 of it
if probe begins with "-" then
set item 1 of newQuartet to item 2 of aDirectionTuple
set item 2 of newQuartet to text -2 thru -1 of ("0" & (((probe as number) * -1) as text))
set mark to 2
else if probe as number > 0 then
set item 1 of newQuartet to item 1 of aDirectionTuple -- N/E if righthanded sys.
-- coercions may be simplified?
set item 2 of newQuartet to text -2 thru -1 of ("0" & probe)
set mark to 2
else
set probe to item 2 of it
if probe begins with "-" then
set item 1 of newQuartet to item 2 of aDirectionTuple
set item 3 of newQuartet to text -2 thru -1 of ("0" & (((probe as number) * -1) as text))
set mark to 3
else if probe as number > 0 then
set item 1 of newQuartet to item 1 of aDirectionTuple
set item 3 of newQuartet to text -2 thru -1 of ("0" & (probe as text))
set mark to 3
else
set probe to item 3 of it
if probe begins with "-" then
set item 1 of newQuartet to item 2 of aDirectionTuple
set item 4 of newQuartet to text -2 thru -1 of ("0" & (((probe as number) * -1) as text))
set mark to 4
else if probe as number ≥ 0 then
set item 1 of newQuartet to item 1 of aDirectionTuple
set item 4 of newQuartet to text -2 thru -1 of ("0" & (probe as text))
set mark to 4
end if
end if
end if
-- we transmit the rest of the arguments here.
repeat with i from mark to 3
set item (i + 1) of newQuartet to text -2 thru -1 of ("0" & (item i of it as number))
end repeat
end tell
return newQuartet
end aMapNotationQuartet
to hexagesNormalize for degreeTriplet
if (length of degreeTriplet < 3) then ¬
error "coordDegLib's hexagesNormalize: incomplete triplet, Lacks one or more of: nr ,min, sec (3 terms)." number 4000
local normalizedResult, hasNeg
set {hasNeg, normalizedResult} to {false, {0, 0, 0}}
tell normalizedResult
set {item 1 of it, item 2 of it, item 3 of it} ¬
to ¬
{item 1 of degreeTriplet as number, ¬
item 2 of degreeTriplet as number, ¬
item 3 of degreeTriplet as number}
-- -0 = 0 , no way to detect "-0", so we take precautions.
set b to item 1 of degreeTriplet as number
if (item 1 of degreeTriplet as number) = 0 and item 1 of degreeTriplet begins with "-0" then
set hasNeg to true
else if item 1 of degreeTriplet as number = 0 and item 2 of degreeTriplet = 0 and item 2 of degreeTriplet begins with "-0" then
set hasNeg to true
end if
-- Removes any negative seconds
if item 3 of it < 0 then
if item 2 of it ≠0 then
set item 3 of it to (item 3 of it) + 60
set item 2 of it to (item 2 of it) - 1
else if item 1 of it ≠0 then
set item 1 of it to (item 1 of it) - 1
set item 2 of it to 59
set item 3 of it to (item 3 of it) + 60
end if
else if item 3 of it > 60 then
-- if the term is greater than 60, then we update the minutes
-- and subtract the minutes we transferred from the seconds.
set item 2 of it to (item 2 of it) + ((item 3 of it) div 60)
set item 3 to (item 3 of it) mod 60
end if
-- removes any negative minutes if the term isn't the first
if item 2 of it < 0 and item 1 of it ≠0 then
set item 2 of it to (item 2 of it) + 60
set item 1 of it to (item 1 of it) - 1
else if item 2 of it > 60 then
-- if the term is greater than 60, then we update the degrees
-- and subtract the degrees we transferred from the minutes
set item 1 of it to (item 1 of it) + ((item 2 of it) div 60)
set item 2 of it to (item 2 of it) mod 60
end if
repeat with i from 1 to 3
if item i of it < 0 then
if i = 1 and length of (item 1 of it as text) < 3 then
set item i of it to "-" & (text -2 thru -1 of ("0" & ((item i of it) * -1)))
else if i = 1 then
-- We don't know the number of digits
set item i of it to item i of it as text
else
set item i of it to "-" & (text -2 thru -1 of ("0" & ((item i of it) * -1)))
end if
else
set item i of it to text -2 thru -1 of ("0" & (item i of it))
end if
end repeat
-- we must restore any negative signs, that has got lost in the process!
if hasNeg and item 1 as number ≥ 0 and item 2 as number ≥ 0 and item 3 as number ≥ 0 then set item 1 to "-00"
end tell
return normalizedResult
end hexagesNormalize
to gregorianCalendarTriplet from JDN
-- From wikipedia Julian Dates
-- This is an algorithm by Richards to convert a Julian Day Number, J, to a date in the Gregorian calendar
-- (proleptic, when applicable). Richards does not state which dates the algorithm is valid for.
-- it seems to work for dates after, BUT NOT INCLUDING 15th of oct 1582.
-- I have extended it to return a hour, min, sec triplet for the fractional part of the JDN else where.
local y, m, N, R, p, V, s, w, b, c, f, g, h, D, MT, YR, JD
set y to 4716
set j to 1401
set m to 2
set N to 12
set R to 4
set p to 1461
set V to 3
set u to 5
set s to 153
set w to 2
set b to 274277
set c to -38
set JD to JDN as integer
set f to JD + j + (((4 * JD + b) div 146097) * 3) div 4 + c
set E to R * f + V
set g to (E mod p) div R
set h to u * g + w
set D to (h mod s) div u + 1
set MT to (h div s + m) mod N + 1
set YR to E div p - y + (N + m - MT) div N
return {YR, MT, D}
end gregorianCalendarTriplet