cleaner date formatting

Good morning!

I have a question about AppleScript date formatting and wonder if there is something cleaner
below is the script

basically, I found a script for setting multiple desktop wallopers based on time of day found here:
https://github.com/pipwerks/OS-X-Wallpaper-Changer
and adapted it for my own use

that worked great until the days started getting darker and I didn’t want to continually change the formatting manually
so added a API call to get the days sunset/sunrise details (using jsonhelper to parse the return)
from http://api.sunrise-sunset.org

that returns the time info as UTC,
so in trying to do a conversion to local time, it gets kinda messy
am wondering if thee is a better way
it is my first attempt at writing a AppleScript so am sure there are other built in routines that I am missing

thanks
Darrin



set morningEarly to "Morning Early"
set morningLate to "Morning Late"
set afternoonEarly to "Afternoon Early"
set afternoonLate to "Afternoon Late"
set eveningEarly to "Evening Early"
set eveningLate to "Evening Late"

-- get current days details from API
tell application "JSON Helper"
	set dayDetails to fetch JSON from "http://api.sunrise-sunset.org/json?lat=47.6768927&lng=-122.2059833&formatted=1"
	set sunrise to sunrise of results of dayDetails as string
	set sunset to sunset of results of dayDetails as string
	
	log words of sunrise
	set sunriseH to word 1 of sunrise
	set sunriseM to word 2 of sunrise
	if (word 4 of sunrise = "PM") then
		set sunriseH to sunriseH + 12
	end if
	set sunriseH to sunriseH - 8
	set sunriseToday to (sunriseH + sunriseM / 100)
	
	log words of sunset
	set sunsetH to word 1 of sunset
	set sunsetM to word 2 of sunset
	if (word 4 of sunset = "PM") then
		set sunsetH to sunsetH + 12
	end if
	set sunsetH to sunsetH + 4
	set sunsetToday to (sunsetH + sunsetM / 100)
end tell

-- get current hour
set h to hours of (current date)
set m to minutes of (current date)
set rightnow to (h + m / 100)

if (rightnow ≥ sunriseToday - 1 and rightnow < sunriseToday + 1) then
	set periodOfDay to morningEarly
else if (rightnow ≥ sunriseToday + 1 and rightnow < 12) then
	set periodOfDay to morningLate
else if (rightnow ≥ 12 and rightnow < 13) then
	set periodOfDay to afternoonEarly
else if (rightnow ≥ 13 and rightnow < sunsetToday - 2) then
	set periodOfDay to afternoonLate
else if (rightnow ≥ sunsetToday - 2 and rightnow < sunsetToday) then
	set periodOfDay to eveningEarly
else if (rightnow ≥ sunsetToday or rightnow < sunriseToday - 1) then
	set periodOfDay to eveningLate
end if

on getImageRight(folderName)
	tell application "Finder"
		return some file of folder ("Pictures:Wallpapers:right:Time of Day:" & folderName) of home as text
	end tell
end getImageRight

on getImageLeft(folderName)
	tell application "Finder"
		return some file of folder ("Pictures:Wallpapers:left:Time of Day:" & folderName) of home as text
	end tell
end getImageLeft

log periodOfDay
tell application "Finder"
	try
		set mainDisplayPicture to my getImageRight(periodOfDay)
		tell application "System Events"
			set theDesktops to a reference to every desktop
			log (count theDesktops)
			if ((count theDesktops) > 1) then
				repeat with x from 2 to (count theDesktops)
					set secondaryDisplayPicture to my getImageLeft(periodOfDay)
					set picture of item x of the theDesktops to secondaryDisplayPicture
				end repeat
			end if
			
		end tell
		set desktop picture to mainDisplayPicture
	end try
end tell

I would make a few changes so that it would be easier to adjust to user’s needs.

set morningEarly to "Morning Early"
set morningLate to "Morning Late"
set afternoonEarly to "Afternoon Early"
set afternoonLate to "Afternoon Late"
set eveningEarly to "Evening Early"
set eveningLate to "Evening Late"

set lat to 43.578 # was 47.6768927 # ADDED
set lng to 7.0545 # was -122.2059833 # ADDED
-- get current days details from API

tell application "JSON Helper"
	set dayDetails to fetch JSON from "http://api.sunrise-sunset.org/json?lat=" & lat & "&lng=" & lng & "&formatted=1" # EDITED
# to be continued

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) dimanche 11 décembre 2016 17:49:10

You might get some useful suggestions here:

macscripter.net/viewtopic.php?id=45195

I think this works in El Capitan or later. It assumes that the computer’s set up for the same time zone as the coordinates used:

use AppleScript version "2.5" -- Mac OS 10.11 (El Capitan) or later.
use framework "Foundation"

set morningEarly to "Morning Early"
set morningLate to "Morning Late"
set afternoonEarly to "Afternoon Early"
set afternoonLate to "Afternoon Late"
set eveningEarly to "Evening Early"
set eveningLate to "Evening Late"

set |⌘| to current application

-- Fetch and convert the JSONData and extract the sunset and sunrise strings (UTC times).
set downloadURL to |⌘|'s class "NSURL"'s URLWithString:("http://api.sunrise-sunset.org/json?lat=47.6768927&lng=-122.2059833&formatted=1")
set JSONData to |⌘|'s class "NSData"'s dataWithContentsOfURL:(downloadURL)
set dataAsDictionary to |⌘|'s class "NSJSONSerialization"'s JSONObjectWithData:(JSONData) options:(0) |error|:(missing value)
set sunsetTimeString to dataAsDictionary's valueForKeyPath:("results.sunset")
set sunriseTimeString to dataAsDictionary's valueForKeyPath:("results.sunrise")

-- Append the strings to the current UTC date.
set rightNow to |⌘|'s class "NSDate"'s |date|()
set UTCBaseDateString to rightNow's |description|()'s substringWithRange:({0, 11})
set sunsetDateString to (UTCBaseDateString's stringByAppendingString:(sunsetTimeString))'s stringByAppendingString:(" +0000")
set sunriseDateString to (UTCBaseDateString's stringByAppendingString:(sunriseTimeString))'s stringByAppendingString:(" +0000")

-- Create new UTC dates from the results and coerce to AppleScript dates. The results should be in the local time.
set dateFormatter to |⌘|'s class "NSDateFormatter"'s new()
tell dateFormatter to setDateFormat:("yyyy-MM-dd hh:mm:ss a z")
set sunset to (dateFormatter's dateFromString:(sunsetDateString)) as date
set sunrise to (dateFormatter's dateFromString:(sunriseDateString)) as date

-- Get the times in hours as reals.
set sunsetToday to (sunset's time) / hours
set sunriseToday to (sunrise's time) / hours
-- get current hour
set rightNow to ((rightNow as date)'s time) / hours

if (rightNow ≥ sunsetToday or rightNow < sunriseToday - 1) then
	set periodOfDay to eveningLate
else if (rightNow < sunriseToday + 1) then
	set periodOfDay to morningEarly
else if (rightNow < 12) then
	set periodOfDay to morningLate
else if (rightNow < 13) then
	set periodOfDay to afternoonEarly
else if (rightNow < sunsetToday - 2) then
	set periodOfDay to afternoonLate
else
	set periodOfDay to eveningEarly
end if

-- Rest of script here.

Edits: Original longitude correctly restored! " +0000" now properly in the sunrise and sunset date strings instead of the formatter string used to turn them into NSDates.

Edit: If it has to work in Yosemite as well, you could try replacing this section .

-- Create new UTC dates from the results and coerce to AppleScript dates. The results should be in the local time.
set dateFormatter to |⌘|'s class "NSDateFormatter"'s new()
tell dateFormatter to setDateFormat:("yyyy-MM-dd hh:mm:ss a z")
set sunset to (dateFormatter's dateFromString:(sunsetDateString)) as date
set sunrise to (dateFormatter's dateFromString:(sunriseDateString)) as date

-- Get the times in hours as reals.
set sunsetToday to (sunset's time) / hours
set sunriseToday to (sunrise's time) / hours
-- get current hour
set rightNow to ((rightNow as date)'s time) / hours

. with this:

-- Create new UTC dates from the results.
set dateFormatter to |⌘|'s class "NSDateFormatter"'s new()
tell dateFormatter to setDateFormat:("yyyy-MM-dd hh:mm:ss a z")
set sunsetDate to dateFormatter's dateFromString:(sunsetDateString)
set sunriseDate to dateFormatter's dateFromString:(sunriseDateString)

-- Get the local times in hours as reals.
set localCalendar to |⌘|'s class "NSCalendar"'s currentCalendar()
set HMFlags to (|⌘|'s NSHourCalendarUnit as integer) + (|⌘|'s NSMinuteCalendarUnit as integer)
tell (localCalendar's components:(HMFlags) fromDate:(sunsetDate)) to set sunsetToday to (its hour()) + (its minute()) / 60
tell (localCalendar's components:(HMFlags) fromDate:(sunriseDate)) to set sunriseToday to (its hour()) + (its minute()) / 60
tell (localCalendar's components:(HMFlags) fromDate:(rightNow)) to set rightNow to (its hour()) + (its minute()) / 60

Also, of course, change this .

use AppleScript version "2.5" -- Mac OS 10.11 (El Capitan) or later.

. to this .

use AppleScript version "2.4" -- Mac OS 10.10 (Yosemite) or later.

You could modify it to run under 10.10 (or 10.9 in a library) by modifying the latter parts something like this:

tell dateFormatter to setDateFormat:("yyyy-MM-dd hh:mm:ss a +0000")
set sunset to dateFormatter's dateFromString:(sunsetDateString)
set sunrise to dateFormatter's dateFromString:(sunriseDateString)

set rightNow to current application's NSDate's |date|()
set middayToday to current application's NSCalendar's currentCalendar()'s dateBySettingHour:12 minute:0 |second|:0 ofDate:rightNow options:0
set hoursAfterSunrise to (rightNow's timeIntervalSinceDate:sunrise) / hours
set hoursBeforeSunset to (sunset's timeIntervalSinceDate:rightNow) / hours
set hoursAfterMidday to (rightNow's timeIntervalSinceDate:middayToday) / hours

if (hoursBeforeSunset < 0 or hoursAfterSunrise < -1) then
	set periodOfDay to eveningLate
else if (hoursAfterSunrise < 1) then
	set periodOfDay to morningEarly
else if (hoursAfterMidday < 0) then
	set periodOfDay to morningLate
else if (hoursAfterMidday < 1) then
	set periodOfDay to afternoonEarly
else if (hoursBeforeSunset < 2) then
	set periodOfDay to afternoonLate
else
	set periodOfDay to eveningEarly
end if

I think I have the latter logic right…

Phew, Shane! Talking about turning the problem inside out! :cool:

I make it:

if (hoursBeforeSunset <= 0 or hoursAfterSunrise < -1) then
	set periodOfDay to eveningLate
else if (hoursAfterSunrise < 1) then
	set periodOfDay to morningEarly
else if (hoursAfterMidday < 0) then
	set periodOfDay to morningLate
else if (hoursAfterMidday < 1) then
	set periodOfDay to afternoonEarly
else if (hoursBeforeSunset > 2) then
	set periodOfDay to afternoonLate
else
	set periodOfDay to eveningEarly
end if

Hello Shane and Nigel

I’m puzzled because with
set lat to 43.578 # was 47.6768927 # ADDED
set lng to 7.0545 # was -122.2059833 # ADDED

Your two versions return “Evening Early” at 14:42:0
while the original script respond “Afternoon Late” at 14:45:00

Your versions return sunset = date lundi 12 décembre 2016 à 15:57:37
while the original code returns : 19:57
The 4 hours gap match the difference.
Honestly I doubt that the sunset is at 15:57:37 :rolleyes:

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) lundi 12 décembre 2016 15:07:09

Hi Yvan.

The times returned from the Web site are the UTC times for the local sunrise and sunset at the given coordinates. darrisden’s original script uses those times and he was asking how to convert them to local time. My version(s) and Shane’s variation transpose them to the time zone of the computer running the scripts. That’s the idea, anyway…

I saw that Nigel but the returned value :
sunset = date lundi 12 décembre 2016 à 15:57:37
is a bit surprising. Here we are in winter time with time to GMT = 3600.

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) lundi 12 décembre 2016 15:43:56

Thank you for the help guys!

there is a lot to go through but I was able to clean up the time formatting a bit
still need to do a little tweaking though :)a

Hi Yvan.

Looking through the code I posted, I see I restored the wrong longitude after testing with my own. I’ve now restored the original. Does it give you less worrying results now?

The scripts which I ran use my local parameters:

set lat to 43.578 # was 47.6768927 # ADDED
set lng to 7.0545 # was -122.2059833 # ADDED
-- get current days details from API
set |⌘| to current application

-- Fetch and convert the JSONData and extract the sunset and sunrise strings (UTC times).
set downloadURL to |⌘|'s class "NSURL"'s URLWithString:("http://api.sunrise-sunset.org/json?lat=" & lat & "&lng=" & lng & "&formatted=1")

The values returned have one hour offset from the correct ones - those which I may find in numerous web pages.

My understanding is that there is a problem with the time to GMT value which here is 3600 but I have no idea of which instruction must be edited to take that in account except using brute force to add one to : hoursBeforeSunset, hoursAfterSunrise and hoursAfterMidday

Looking in an alternate site I found slightly different values for lat and lng
set lat to 43.58056
set lng to 7.05389

but of course they change nothing.

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) lundi 12 décembre 2016 16:56:41

The times returned from the Web site are UTC. Are you saying the scripts don’t correctly transpose them to your own time zone?

Exactly

You take the local date, drop its time component then concatenate it the returned sunsetTimeString and sunriseTimeString.

For you Englishman it’s OK

For me it’s wrong.

My understanding is that the correct scheme would be :
extract the values from the site:
sunsetTimeString = 3:57:37 PM*)
(*sunriseTimeString = 6:54:39 AM
calculate the localDate whose day is 12, month is 12, year is 2016
You get 2016-12-12 and append the strings getting :
2016-12-12 3:57:37 PM
2016-12-12 6:54:39 AM

For me it would be 2016-12-12 1:00:00
then add it the sunsetTime or the sunriseTime
so it would give
2016-12-12 4:57:37 PM
2016-12-12 7:54:39 AM

I guess that Shane which doesn’t live with a time to GMT equal to 0000 will be able to test what I describe easily.

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) lundi 12 décembre 2016 18:34:05

According to my tests and Shane’s book, the descriptions of NSDates are always in GMT/UCT:

use framework "Foundation"

set now to current application's class "NSDate"'s |date|()
now's |description|() as text
--> "2016-12-12 18:45:53 +0000"

-- OK. GMT's in force anyway at the moment where I am, so:
set aBSTDate to date "Saturday 1 July 2017 at 00:00:00" -- A date in British Summer Time.
set aBSTNSDate to current application's class "NSDate"'s dateWithTimeInterval:(0.0) sinceDate:(aBSTDate)
set BSTNSDateDescription to aBSTNSDate's |description|() as text
--> "2017-06-30 23:00:00 +0000" -- Result in GMT.

It’s on that understanding that I used a |date|() description as the basis for the UCT sunrise and sunset dates. At the moment, I don’t know why you’re getting something different.

Edit: After a quick walk round the block, it’s occurred to me that the " +0000" should be in the date strings used to create the sunrise and sunset dates, not in the format string. It does make a difference. I’ll change it above.

Bingo, I was not dreaming.

I was ready to post these instructions when I saw your edited message.

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

log "currentDate = " & (current date) (*currentDate = lundi 12 décembre 2016 à 20:54:10*)
log hours of (current date) --> 20
set theNSDate to current application's NSDate's |date|()
set theNSDateAsString to (theNSDate's |description|() as text)
log "theNSDate = " & theNSDateAsString (*theNSDate = 2016-12-12 19:54:10 +0000*)

It clearly make the difference between what is in the AppleScript dateString - here 20 as the hours value, and what is in the NSDate as string - here 19.

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) lundi 12 décembre 2016 21:02:13

To make it clear, the description property of NSDate prints the time in UTC but depending on your time zone.

For example January 1, 2017 in a time zone of UTC+2 will be printed as "2016-12-31 22:00:00 +0000

However the output of NSDateFormatter considers the time zone of the current locale.

What I have found is that the times returned by api.sunrise-sunset.org don’t make any allowance for GMT offset. So in my case, depending on the time of day it will return the values for yesterday rather than today.

One more try. This should work for 10.10 and above, or 10.9 in a script library:

use AppleScript version "2.3"
use framework "Foundation"

set morningEarly to "Morning Early"
set morningLate to "Morning Late"
set afternoonEarly to "Afternoon Early"
set afternoonLate to "Afternoon Late"
set eveningEarly to "Evening Early"
set eveningLate to "Evening Late"

-- Fetch and convert the JSONData and extract the sunset and sunrise strings (ISO8601 format).
set downloadURL to current application's class "NSURL"'s URLWithString:("http://api.sunrise-sunset.org/json?lat=-37.814&lng=144.96332&formatted=0") -- change lat and lng to suit
set JSONData to current application's NSData's dataWithContentsOfURL:(downloadURL)
set dataAsDictionary to current application's NSJSONSerialization's JSONObjectWithData:(JSONData) options:(0) |error|:(missing value)
set sunsetTimeString to dataAsDictionary's valueForKeyPath:("results.sunset")
set sunriseTimeString to dataAsDictionary's valueForKeyPath:("results.sunrise")

set rightNow to current application's NSDate's |date|()
set theCal to current application's NSCalendar's currentCalendar()

-- make date formatter
set NSISO8601DateFormatter to current application's NSClassFromString("NSISO8601DateFormatter")
if NSISO8601DateFormatter is missing value then -- pre-10.12, so fall back to normal formatter
	set dateFormatter to current application's NSDateFormatter's new()
	dateFormatter's setLocale:(current application's NSLocale's localeWithLocaleIdentifier:"en_US_POSIX")
	dateFormatter's setDateFormat:("yyyy-MM-dd'T'HH:mm:ssZZZZZ")
	dateFormatter's setTimeZone:(current application's NSTimeZone's timeZoneForSecondsFromGMT:0)
else -- running 10.12
	set dateFormatter to NSISO8601DateFormatter's new()
end if

-- convert date strings to dates
set sunset to dateFormatter's dateFromString:(sunsetTimeString)
set sunrise to dateFormatter's dateFromString:(sunriseTimeString)

-- calculate approximate current-day values if necessary
if (theCal's isDateInToday:sunset) as boolean is false then set sunset to sunset's dateByAddingTimeInterval:days
if (theCal's isDateInToday:sunrise) as boolean is false then set sunrise to sunrise's dateByAddingTimeInterval:days
set middayToday to theCal's dateBySettingHour:12 minute:0 |second|:0 ofDate:rightNow options:0

set hoursAfterSunrise to (rightNow's timeIntervalSinceDate:sunrise) / hours
set hoursBeforeSunset to (sunset's timeIntervalSinceDate:rightNow) / hours
set hoursAfterMidday to (rightNow's timeIntervalSinceDate:middayToday) / hours

if (hoursBeforeSunset ≤ 0 or hoursAfterSunrise < -1) then
	set periodOfDay to eveningLate
else if (hoursAfterSunrise < 1) then
	set periodOfDay to morningEarly
else if (hoursAfterMidday < 0) then
	set periodOfDay to morningLate
else if (hoursAfterMidday < 1) then
	set periodOfDay to afternoonEarly
else if (hoursBeforeSunset > 2) then
	set periodOfDay to afternoonLate
else
	set periodOfDay to eveningEarly
end if

-- Rest of script here.

But if you’re a way off GMT and the equator, the fact that the sunrise and sunset values returned might be for a different day makes them potentially pretty inaccurate.

Thanks Shane

I made a little change - already described - to make adjustments to latitude and longitude easier.
Doing that I discovered an interesting difference. Nigel’ and the original poster use “formatted=1” at the end of the instruction defining downloadURL.
You use “formatted=0”.
When I simply pasted the instructions borrowed from the ‘old’ versions :

set lat to 43.58056 # was 47.6768927 # ADDED
set lng to 7.05389 # was -122.2059833 # ADDED

-- get current days details from API
set |⌘| to current application
-- Fetch and convert the JSONData and extract the sunset and sunrise strings (UTC times).
set downloadURL to |⌘|'s class "NSURL"'s URLWithString:("http://api.sunrise-sunset.org/json?lat=" & lat & "&lng=" & lng & "&formatted=1")

The strings grabbed from the web were:
(sunsetTimeString = 3:57:48 PM)
(sunriseTimeString = 6:55:25 AM)

With your setting (formatted=0) they are :
(sunsetTimeString = 2016-12-13T15:57:48+00:00)
(sunriseTimeString = 2016-12-13T06:55:25+00:00)
so the trickery used by Nigel to build date-time strings is no longer needed.

sunset and sunrise are correctly set as :
(sunset = mardi 13 décembre 2016 à 16:57:48)
(sunrise = mardi 13 décembre 2016 à 07:55:25)

If I understand well, it’s your use of NSISO8601DateFormatter which makes the difference.

I will have to make tests under 10.10 or 10.11 because I am wondering what would be the impact of localeWithLocaleIdentifier:“en_US_POSIX” on my system running in French.

Under 10.11 there is no visible change. The script works exactly well as it does under 10.12.1.

Yvan KOENIG running Sierra 10.12.1 in French (VALLAURIS, France) mardi 13 décembre 2016 11:18:09