In response to the latter part of this thread, instead of relying on hard-coded values and a limited number of timezones, I’ve created a script that relies on OS X’s built-in timezones record to retrieve the latest daylight savings times.
Edit: parseDumpedDate handler changed courtesy of Nigel Garvey.
property monthAbbrs : "JanFebMarAprMayJunJulAugSepOctNovDec"
on parseDumpedDate(dateString)
-- One-line version:
-- tell dateString to return (my date (word 3 & space & ((my (offset of (word 2 of dateString) in monthAbbrs)) + 2) div 3 & space & word -2 & space & text (word 4) thru (word -3)))
set a to date (text (word 4) thru (word -3) of dateString)
set day of a to word 3 of dateString
set month of a to ((offset of (word 2 of dateString) in monthAbbrs) + 2) div 3
set year of a to word -2 of dateString
return a
end parseDumpedDate
(*** on run ***)
set {text returned:chosenYear, button returned:buttonReturned} to display dialog "Daylight Saving Times for Year:" default answer "2007" buttons {"Choose Timezone", "Cancel", "Use System Timezone"} default button "Use System Timezone"
if (buttonReturned is "Choose Timezone") then
set firstLevel to paragraphs of (do shell script "ls -p /usr/share/zoneinfo | sed -e '/\\//!d' -e 's|/||'")
set firstChoice to choose from list firstLevel with prompt "Continents:"
if (firstChoice is false) then error number -128
set secondLevel to paragraphs of (do shell script "ls /usr/share/zoneinfo/" & firstChoice)
set secondChoice to choose from list secondLevel with prompt "Regions:"
if (secondChoice is false) then error number -128
set zonePath to "/usr/share/zoneinfo/" & firstChoice & "/" & secondChoice
else
set zonePath to "/etc/localtime"
set {firstChoice, secondChoice} to (do shell script "/usr/bin/readlink " & zonePath & " | sed -E -e 's|.*zoneinfo/||' -e 's|/|\\" & return & "|'")'s paragraphs
end if
try
set DSTs to run script ("{" & text 1 thru -3 of ¬
(do shell script "/usr/sbin/zdump -v " & zonePath & " | sed -E -e '/" & chosenYear & "/!d' -e 's|^[^ ]+ *|{utcDate:\"|g' -e 's/ = /\", localDate:\"/' -e 's/ isdst=1/\", isDST:true},¬/' -e 's/ isdst=0/\", isDST:false},¬/'") ¬
& "}")
on error
display dialog "No Daylight Savings Times found for " & chosenYear & " at " & secondChoice & ", " & firstChoice
return
end try
set t to chosenYear & " Daylight Savings Times for " & secondChoice & ", " & firstChoice & ":" & return & return
set oldItem to null
repeat with anItem in DSTs
if (oldItem is not null and oldItem's isdst is not anItem's isdst) then
if (anItem's isdst) then
set t to t & "Begins "
else
set t to t & "Ends "
end if
set date1 to parseDumpedDate(oldItem's localdate)
tell date1's date string to if it ends with chosenYear then
set t to t & text 1 thru -6
else
set t to t & it
end if
set t to t & "," & return & tab & "changing from " & time string of date1 & " to " & time string of parseDumpedDate(anItem's localdate) & return
end if
set oldItem to anItem
end repeat
display dialog t buttons {"OK"} default button 1
Old parseDumpedDate handler:
on parseDumpedDate(dateString)
set a to date (text (word 4) thru (word -2) of dateString)
set day of a to word 3 of dateString
set month of a to ((offset of (word 2 of dateString) in monthAbbrs) + 2) div 3
return a
end parseDumpedDate
I like the slight rearrangement: asking for the year first and in the buttons offering a choice of time zone. Much nicer presentation. Thanks for posting it
Seconded. Unfortunately, on my machine, of the years I’ve tried so far, only 2007 gives the right weekdays. The day numbers and months appear to be about right, though.
UK system time zone
Mac OS 10.4.9
Later:
The problem’s in the parseDumpedDate() handler. The initial setting of a to ‘date (text (word 4) thru (word -2) of dateString)’ doesn’t necessarily produce a date in the right year. (On my UK system, when a string containing only the time followed by the year is used in a date specifier, the year figure’s ignored and the system fills in the details from the current date.) This works for me:
on parseDumpedDate(dateString)
set a to date (text (word 4) thru (word -3) of dateString)
set day of a to word 3 of dateString
set month of a to ((offset of (word 2 of dateString) in monthAbbrs) + 2) div 3
set year of a to word -2 of dateString
return a
end parseDumpedDate
You’re totally correct. The problem was my assumption that the year wasn’t ignored, and not doing the proper testing. I’ve edited it.
This board’s great for having scripts tested, especially for that one bug that I usually leave in as a ‘secret’ surprise!
As usual, thanks to Nigel Garvey for his rigourous testing! And thanks to the others for the nice comments.
Yes, sorry, your primary language. :rolleyes: My fault.
From what I could find, the problem seemed to be in the spelling of the months. I’ve kept Nigel’s version.
I’ve also changed the display text, so it at least uses localised weekday and month names.
The spelling of the month doesn’t matter, because you get always the same information with zoneinfo
regardless of the language. In your parseDumpedDate handler you assumed the date order m / d / y.
Setting a date just with the date keyword and a string is never independent from international date format settings.
The most robust way is to take a present date and change its values or like Nigel does, set only the time with a string
and change month, day and year “manually”
As discussed here, I’ve written an adaptation of this example in the form of a handler that converts a given date from local time to GMT, accounting for the daylight savings offset, if any, at the time of the given date. Here is the script:
on parseDumpedDate(dateString)
set a to date (text (word 4) thru (word -3) of dateString)
set day of a to word 3 of dateString
set month of a to ((offset of (word 2 of dateString) in "JanFebMarAprMayJunJulAugSepOctNovDec") + 2) div 3
set year of a to word -2 of dateString
return a
end parseDumpedDate
on ConvertToGMT(_inputDate)
try
-- Get a list of significant DST dates in _localDate's year.
set _dstData to run script ("{" & text 1 thru -3 of ¬
(do shell script "/usr/sbin/zdump -v /etc/localtime | sed -E -e '/" & year of _inputDate & "/!d' -e 's|^[^ ]+ *|{_utcDate:\"|g' -e 's/ = /\", _localDate:\"/' -e 's/ isdst=1/\", _isDST:true},¬/' -e 's/ isdst=0/\", _isDST:false},¬/'") ¬
& "}")
on error
-- Use plain GMT offset if no local DST data is available.
return _inputDate - (time to GMT)
end try
-- Initialize defaults.
set _dstOffset to time to GMT
set _stdOffset to time to GMT
set _dstLocal to (current date)
set _stdLocal to _dstLocal
-- Inspect each DST data point.
set _prevData to null
repeat with _data in _dstData
-- Any transition between DST and standard time is of interest.
if (_prevData is not null and _prevData's _isDST is not _data's _isDST) then
-- Convert the last data point's dates into AppleScript date objects.
set _dataLocal to parseDumpedDate(_prevData's _localDate)
set _dataUTC to parseDumpedDate(_prevData's _utcDate)
-- Record the seasonal offset to GMT and the season endpoints.
if (_data's _isDST) then
-- We have entered DST; the dates are the last standard times.
set _stdOffset to (_dataLocal - _dataUTC)
set _dstLocal to _dataLocal
else
-- We have entered standard time; the dates are the last DST times.
set _dstOffset to (_dataLocal - _dataUTC)
set _stdLocal to _dataLocal
end if
end if
set _prevData to _data
end repeat
-- Determine which GMT offset to use based on the sequence of _inputDate and the DST dates.
if _dstLocal is less than _stdLocal then
-- DST in middle of year (northern hemisphere)
if (_inputDate is less than or equal to _dstLocal) or (_inputDate is greater than _stdLocal) then
return _inputDate - _stdOffset
else
return _inputDate - _dstOffset
end if
else if _stdLocal is less than _dstLocal then
-- Standard time in middle of year (southern hemisphere)
if (_inputDate is less than or equal to _stdLocal) or (_inputDate is greater than _dstLocal) then
return _inputDate - _dstOffset
else
return _inputDate - _stdOffset
end if
end if
-- default return only if _dstLocal equals _stdLocal (only if no or incomplete _dstData)
return _inputDate - (time to GMT)
end ConvertToGMT
-- test case for change from DST to standard time in my time zone (US Eastern)
--ConvertToGMT(date "Sunday, November 4, 2007 1:59:59 AM") -- 5:59:59 AM
--ConvertToGMT(date "Sunday, November 4, 2007 2:00:00 AM") -- 7:00:00 AM
I use this function in a pair of scripts I wrote to modify the timestamps of Yojimbo items.
Your script adjusts for the switches between GMT and British Summer Time on the correct dates and at the correct times too.
But there’s an interpretation problem (for everyone) around the time of the switch. When the clocks go back for Standard Time, the local time repeats the hour between 1:00:00 and 1:59:59. There’s no way for the script to know which occurrence of that hour is meant. Going the other way, from Standard to Daylight Saving Time, the hour’s skipped completely, so perhaps there should be a little error message when a local time’s specified in the non-existent period.