Create list of available times (availability calendar) using Applescript - applescript

I am trying to create an availability calendar using applescript for my mac. This would query application "Calendar", get all events for the day (ideally would like it to be for multiple days), and then give me the times between the events so that I can send that via email, chat, etc.
I've gotten as far as being able to find events, but haven't been able to find the "inverse" times of the events.
set hours of theStartDate to 0
set minutes of theStartDate to 0
set seconds of theStartDate to 0
set theEndDate to theStartDate + (1 * days) - 1
tell application "Calendar"
tell calendar "GENERIC CALENDAR"
every event where its start date is greater than or equal to theStartDate and end date is less than or equal to theEndDate
end tell
end tell

This is an approach which creates the range (startDate/endDate) and then populates a list with available start/end date pairs
-- set startDate and endDate to todays's midnight and tomorrow's midnight
tell (current date) to set startDate to it - (its time)
set endDate to startDate + 86400
-- availableTimes is the result which contains a list of startDate/endDate pairs
set availableTimes to {}
-- temporary variable which is set to the current start date
set currentStartDate to startDate
tell application "Calendar"
tell calendar "GENERIC CALENDAR"
set todaysEvents to every event where its start date ≥ startDate and end date ≤ endDate
repeat with anEvent in todaysEvents
set end of availableTimes to {currentStartDate, anEvent's start date}
set currentStartDate to anEvent's end date
end repeat
set end of availableTimes to {currentStartDate, endDate}
end tell
end tell

The start and end times of each event can be expressed as an interval comprised of two integer values, each representing the number of minutes since midnight between which you are unavailable. These intervals are then simply numbers on a number line between 0 and 1440 (the number of minutes in a day), which are easy to invert and convert back to dates to obtain your availability.
use application "Calendar"
use scripting additions
property calendar : a reference to calendar "GENERIC CALENDAR"
--------------------------------------------------------------------------------
tell (current date) to set midnight to (it - (its time))
set _E to a reference to (events of my calendar ¬
whose start date ≥ midnight and ¬
end date ≤ (midnight + 1 * days))
set {|d₁|, |d₂|} to {start date, end date} of _E
repeat with i from 1 to length of |d₁|
set |t₁| to (a reference to item i of |d₁|)
set |t₂| to (a reference to item i of |d₂|)
set |t₁|'s contents to (|t₁| - midnight) / minutes
set |t₂|'s contents to (|t₂| - midnight) / minutes
end repeat
set ranges to flatten({0, transpose(|d₁|, |d₂|), days / minutes})
set availability to {}
repeat with i from 1 to length of ranges by 2
set {|t₁|, |t₂|} to {item i, item (i + 1)} of ranges
set end of availability to {¬
midnight + |t₁| * minutes, ¬
midnight + |t₂| * minutes}
end repeat
return availability
--------------------------------------------------------------------------------
# HANDLERS:
to transpose(A, B)
local A, B
tell {}
repeat with i from 1 to A's length
set its end to {item i of A, item i of B}
end repeat
it
end tell
end transpose
to flatten(L)
local L
if L = {} then return {}
if L's class ≠ list then return {L}
flatten(L's first item) & flatten(rest of L)
end flatten
---------------------------------------------------------------------------❮END❯

Related

How to find the number of items in a variable? (AppleScript)

I am trying to find the number of items in a variable, I tried to create somewhat of a while loop to test if the item of the variable exists, if it does then go to the next item and test if it exists, this repeats until the item does not exist, then it displays the current item number, which should be the final item of the variable.
Here is my code:
set stuff to "123456789"
set x to "1"
set num to item x of stuff
if exists item x of stuff then
repeat while exists item (x + 1) of stuff
if exists (item x of stuff) then
set x to (x + 1)
else
set num to x
end if
end repeat
end if
display dialog num
Currently when I run this code I get the error:
"Can’t get item 10 of "123456789"."
Which I understand tells me that 10 is the last item of this variable but the information does me no good in the form of an error message. Thanks in advance
How to find the number of items in a variable? (AppleScript)
The clue's in the question:
set variable to "123456789"
return the number of items in the variable --> 9
As already stated by red_menace, you can also quantify data objects using the length property (for list, record, or text objects), or by using the count command, which is superfluous in my view as it ends up accessing the length property anyway.
The AppleScript command count counts the number of elements in an object, for example count of stuff. In addition, classes such as list, record, and text also have a length property, for example length of stuff.
set stuff to "this is a test"
set x to 0
repeat with anItem in (get items of stuff)
set x to x + 1 -- just using x to count
log anItem
end repeat
display dialog "Number of items is " & x & return & "Count is " & (count stuff) & return & "Length is " & (length of stuff)
See the AppleScript Language Guide for more information.

Setting a variable in AppleScript based on a list in a Numbers sheet

AppleScript beginner here. Searching high and low hasn't led me to the answer yet.
I'm using AppleScript to help run youth wrestling tournaments. Each division (based on age) is broken down into weight classes. For example: Novice 80 or Cadet 105.
Once a certain group of kids is put into a certain division/weight class, those kids get added to a new sheet that contains their bracket (think March Madness bracket but a small number of kids wrestling instead of playing basketball).
I've figured out how to get a group into a new sheet where they populate the bracket, but when this new sheet is created, I don't know how to make AppleScript change the name of the sheet to the correct division/weight class. I'm sure it has something to do with creating variables based on a list of the divisions/weight classes (that I have), but I can't figure out how to do it. Here's the relevant portion of the code:
tell document 1
set active sheet to the last sheet
set thisSheet to make new sheet
set the name of thisSheet to "[Division variable – Weight class variable]"
tell thisSheet
delete every table
end tell
Any ideas on how to make AppleScript name the sheet like I want?
To give you a small example that you're able to visualize what you're going after, heres a small snippet. I think its self explanatory, iterate through a list of titles, then apply the names to the sheets.
set divisionNames to {"Novice", "Cadet"} -- How You Grab These Values Matters
set weightClasses to {"80", "105"} -- These Values Too
tell application "Numbers"
activate
set thisDocument to make new document
tell thisDocument
repeat with i from 1 to count of divisionNames
make new sheet with properties {name:item i of divisionNames & space & item i of weightClasses}
end repeat
end tell
end tell
Alternately, if you're pulling the values from a list as a whole then you could
set sheetTitles to {"Novice 80", "Cadet 105"}
tell application "Numbers"
activate
set thisDocument to make new document
tell thisDocument
repeat with division in sheetTitles
make new sheet with properties {name:division}
end repeat
end tell
end tell
EDIT: In the spirit of helping a low to no budget school/organization.. here's another example answering the second question issued in comments. Again without knowing the structure of your data its hard to give you an exact answer on your specific case. Additionally, here's a link to a site they may help further the advancement on your project. https://iworkautomation.com/numbers/index.html
(*
set sampleKids to {"John Doe", "Jane Doe", "Johnny Foe", "Janie Foe", "Tommy Joe", "Tammy Joe"}
set sampleDivisions to {"Novice-80", "Novice-85", "Cadet-105", "Cadet-110", "Novice-80", "Cadet-105"}
tell application "Numbers"
activate
set thisDoc to make new document with properties {name:"Wrestling Sample"}
tell thisDoc
set the name of sheet 1 to "Sign In Sheet"
tell active sheet
delete every table
set newTable to make new table with properties {row count:(count of sampleKids) + 1, column count:2, name:"Sign In Sheet"}
tell newTable
set value of cell 1 of column "A" to "Name"
set value of cell 1 of column "B" to "Division"
set x to 2
repeat with eachName in sampleKids
set value of cell x of column "A" to eachName
set x to (x + 1)
end repeat
set x to 2
repeat with eachDivision in sampleDivisions
set value of cell x of column "B" to eachDivision
set x to (x + 1)
end repeat
end tell
end tell
end tell
end tell
*)
--********** IGNORE ABOVE THIS LINE IT'S ONLY BUILDING A SAMPLE TABLE **********--
--********** SAVE ABOVE TO ANOTHER SCRIPT FOR TESTING WITH NEW TABLE **********--
(*
ERROR HANDLING ISN'T PRESENT - AN EMPTY CELL IN FIRST COLUMN OR CHOSEN COLUMN WILL THROW AN
ERROR SINCE THEY ARE THE IMPORTANT PIECES OF DATA FOR GRABBING LISTS - SAVE SCRIPT
TO NUMBERS SCRIPT FOLDER OF YOUR CHOICE - ENTER ALL INFO ON FIRST TABLE OF FIRST SHEET OF
DOCUMENT THEN RUN SCRIPT - SCRIPT ACCEPTS ANY AMOUNT OF ROWS OR COLUMNS
*)
tell application "Numbers"
activate
-- Display A Simple Reminder That You're About To Lose Some Existing Data
display dialog "This Script Will Delete All Sheets Except The First Sheet Of This Document Before It Proceeds To Make New Sheets & Tables Based On The First Table Of The First Sheet." buttons {"Cancel", "Start"} default button 2 with icon 1
tell document 1
-- Get A List of the Sheet Names
set sheetNames to name of sheets
-- Start With A Fresh Slate, No Old Sheets
delete (every sheet whose name is not item 1 of sheetNames)
tell sheet 1
-- Grab and Set Future Header Values
set columnHeaders to value of cell of row 1 in table 1
-- Display A List Of Possible Choices To Create New Sheets With From The List We Make Above
set chosenColumn to choose from list columnHeaders with prompt "Which Column Do You Want To Use For New Sheets?" default items item 1 of columnHeaders
set chosenColumn to chosenColumn as text
tell table 1
-- Remove All Empty Rows to Help Prevent Error In Script
set {row_count, col_count} to {count rows, count columns}
set blank_row to {}
repeat with x from 1 to col_count
set blank_row to blank_row & missing value
end repeat
set x to 1
-- Delete Empty Rows In Reverse, It's Logical
repeat with y from row_count to 1 by -1
set row_values to value of cells of row y
if row_values = blank_row then delete row y
end repeat
-- Grab A List of All Divisions for Future Use Depending on Choice From Prompt, excluding the First Row Which Is A Header. If You Selected The First Column, We Have to Handle That Differently
if chosenColumn is item 1 of columnHeaders then
set theDivisions to the value of every cell of column "A" whose value is not chosenColumn
else
set theDivisions to the value of every cell of column named chosenColumn whose value is not chosenColumn
end if
end tell
end tell
-- Start the New "Sheet Making" Loop
repeat with division in theDivisions
-- Make An Empty Blank List At the Start of Every Loop
set matchingDivisions to {}
tell table 1 of sheet 1
-- Get All Rows Matching the Current Division of the Loop We Are On, to Make New Tables With Later
repeat with x from 1 to count of cells
if the value of cell x is division then
-- Put All Data About the Rows We Gathered Above Into the Empty List We Made
set the end of matchingDivisions to value of cells of row of cell x
end if
end repeat
-- Reset x, Because I'm Not Creative and Want to Use It Later
set x to 1
end tell
-- If The Sheet Of the Division We Are On, of the Loop, Doesn't Exist, Make It
if not (exists sheet division) then
make new sheet with properties {name:division}
tell sheet division
-- Start With A Fresh Slate On This New Sheet
delete every table
-- Make the Table With All Relevant Parameters
set currentDivisionTable to make new table with properties ¬
{row count:((count of matchingDivisions) + 1), column count:count of item 1 of matchingDivisions, name:division}
tell currentDivisionTable
set x to 1
-- Set The Header Values from A List We Created Earlier
repeat with theHeader in columnHeaders
set the value of cell x to theHeader
set x to (x + 1)
end repeat
-- Reset x Again, I'm Lazy
set x to 1
-- Set Starting Point to Start Filling The Table, Compensate For Our Headers
set rowIndex to 1
set columnIndex to 0
-- Start Filling The Table With Data, Which Comes From The List Earlier
repeat with x from 1 to count of the matchingDivisions
set rowData to item x of the matchingDivisions
tell row (rowIndex + x)
repeat with i from 1 to the count of rowData
tell cell (columnIndex + i)
set value to item i of rowData
end tell
end repeat
end tell
end repeat
end tell
end tell
end if
end repeat
-- Return To the First Sheet
set the active sheet to the first sheet
-- Display Notification That The Tables Are Done Being Made -- OPTIONAL
display notification "Processing is complete." with title "Numbers Table Converter" subtitle "All Tables Have Been Made." sound name "Hero"
end tell
end tell
Try
set thisSheet to make new sheet with properties {name:"yourname"}
Edit
Some explanation: if your struggling on how to adress third party apps try to load it's library and look up the methods you need. In applescript editor you will find it in the (I guess) menu. Then select the desired app to get the library
The full code modify at below:
set {begCol, endCol} to {2, 17}
set tgtCol to 1
tell application "Numbers"
tell front sheet of front document
tell active sheet
set getVal to rows's cells's value
set myOriginalTable to front table
set itemCode to {"CardNo", "SAL-DAY", "OT15-Money",
"OT2-Money", "OT3-Money", "OTHINC", "P0007", "DILIG", "P0004", "P0003",
"SEV_SPE", "P0011", "SI-SSF", "TI-TAXITEM", "P0022", "P0021", "P0025"} --
change to variable
set Amount to {"CardNo", "SAL-DAY", "OT15-Money",
"OT2-Money", "OT3-Money", "OTHINC", "P0007", "DILIG", "P0004", "P0003",
"SEV_SPE", "P0011", "SI-SSF", "TI-TAXITEM", "P0022", "P0021", "P0025"} --
change to variable
set setCols to 8
end tell
set myNewTable to make new table with properties ¬
{column count:setCols, row count:(count of itemCode) + 1,
header column count:0}
tell myNewTable
set value of cell 1 of column "A" to "cardNo"
set value of cell 1 of column "B" to "emCode"
set value of cell 1 of column "C" to "emName"
set value of cell 1 of column "D" to "itemCode"
set value of cell 1 of column "E" to "itemName"
set value of cell 1 of column "F" to "effDate"
set value of cell 1 of column "G" to "amt"
set value of cell 1 of column "H" to "remark"
set x to 2
repeat with eachAmount in Amount
set value of cell x of column "G" to eachAmount
set x to (x + 1)
end repeat
set x to 2
repeat with eachItemCode in itemCode
set value of cell x of column "D" to eachItemCode
set x to (x + 1)
end repeat
end tell
end tell
end tell
Thank you
According source code above. I found other way for use range select for determine data. But it still incorrect because the code pickup all data to one cell like this.
result after ran script
Could some one able to suggest me?
try
tell application "Numbers" to tell front document to tell active sheet
set delimiter to ","
set selected_table to first table whose class of selection range is range
tell selected_table
set my_selection to the selection range
set begCol to address of first column of my_selection
set endCol to address of last column of my_selection
set begRow to address of first row of my_selection
set endRow to address of last row of my_selection
set getVal to ""
repeat with j from begRow to endRow
repeat with i from begCol to endCol
set getVal to (getVal & (value of cell j of column i of selected_table) as text) & delimiter
set getVal to getVal & return
end repeat
end repeat
end tell
end tell
set AmountVal to {getVal}
tell application "Numbers"
activate
tell front sheet of front document
set myOriginalTable to front table
set setCols to 8
set myNewTable to make new table with properties ¬
{row count:(count of AmountVal) + 1, column count:setCols, header column count:0}
tell myNewTable
set value of cell 1 of column "A" to "cardNo"
set value of cell 1 of column "B" to "emCode"
set value of cell 1 of column "C" to "emName"
set value of cell 1 of column "D" to "itemCode"
set value of cell 1 of column "E" to "itemName"
set value of cell 1 of column "F" to "effDate"
set value of cell 1 of column "G" to "amt"
set value of cell 1 of column "H" to "remark"
set x to 2
repeat with eachAmount in AmountVal
set value of cell x of column "G" to eachAmount
set x to (x + 1)
end repeat
end tell
end tell
end tell
display notification "Already Done!" with title "Numbers"
on error
display dialog "Select a range first and then try again"
end try

Applescript Error "can't get end of"

I am trying my hand at Applescript and can't see anything wrong with this.
The Error I get is
error "Can’t get end of {button returned:\"OK\", text returned:\"3\"}." number -1728 from last insertion point of {button returned:"OK", text returned:"3"}
This is my code:
beep
set counter to 0
set tempX to 0
set temp to 0
set counting to 0
set stored to {0}
set input to "How many grades do you wish to enter?" as string
set str to display dialog input buttons {"NEXT"} default button "NEXT" default answer ""
repeat text returned of str times
counting = counting + 1
set grades to display dialog "GRADES: " default answer ""
set stored to grades
end repeat
set rep to the length of stored
repeat rep times
counter = counter + 1
set tempX to the ((end of stored) - counter) as number
set temp to temp + tempX
end repeat
set ln to the length of grades
set average to temp / ln
if text returned of str is 1 then
say "The Average of your grade is " & average using "Zarvox"
else
say "The Average of your grades is " & average using "Zarvox"
end if
get "AVERAGE: " & average
So, before I begin: I'd strongly recommend that you teach yourself how to use the Javascript interface to Apple Events, rather than the Applescript language itself. Applescript is a really weird language, and its quirks are largely unique; learning it is going to be frustrating, and won't help you learn other languages.
That being said, let's dive into your code:
set stored to {0}
This would start you out with one grade that's always present and set to zero. You probably want to just initialize this to an empty list:
set stored to {}
Next:
set grades to display dialog "GRADES: " default answer ""
This sets grades to a result object, not just the answer. What you probably want here is actually the text returned of the result:
set grades to text returned of (display dialog "GRADES: " default answer "")
(This is what's creating the really weird-looking object in your error message.)
Next, you overwrite stored with this result object:
set stored to grades
What you probably want here is to insert this element into the list. Because Applescript is a strange and obnoxious language, this is somewhat more cumbersome than you're thinking:
set stored to stored & {grades}
Finally, there's some logical issues with your averaging; you're adding the end of stored (that is, the last grade input) to the temp variable each time. A much simpler approach would be:
set temp to 0
repeat with n in stored
set temp to temp + n
end repeat
set average to sum / (count of stored)
With these changes all made, your script should work correctly.

How many Mondays in a Month Visual Basic

I'm trying to determine how many mondays there is between two dates.
These dates will be inserted by a datetimepicker
I know there are similar threads to this but they're all in PHP or VBScript.
Any ideas?
This post tells you how to do it in C#. Count number of Mondays in a given date range
I have run the code through a converter below. Have not tested it.
Private Shared Function CountDays(day As DayOfWeek, start As DateTime, [end] As DateTime) As Integer
Dim ts As TimeSpan = [end] - start
' Total duration
Dim count As Integer = CInt(Math.Floor(ts.TotalDays / 7))
' Number of whole weeks
Dim remainder As Integer = CInt(ts.TotalDays Mod 7)
' Number of remaining days
Dim sinceLastDay As Integer = CInt([end].DayOfWeek - day)
' Number of days since last [day]
If sinceLastDay < 0 Then
sinceLastDay += 7
End If
' Adjust for negative days since last [day]
' If the days in excess of an even week are greater than or equal to the number days since the last [day], then count this one, too.
If remainder >= sinceLastDay Then
count += 1
End If
Return count
End Function

How do you efficiently build a list within a handler in AppleScript?

AppleScript documentation suggests the following code to efficiently build a list:
set bigList to {}
set bigListRef to a reference to bigList
set numItems to 100000
set t to (time of (current date)) --Start timing operations
repeat with n from 1 to numItems
copy n to the end of bigListRef
end
set total to (time of (current date)) - t --End timing
Note the use of an explicit reference. This works fine at the top level of a script or within an explicit run handler, but if you run the same exact code verbatim within another handler like so:
on buildList()
set bigList to {}
set bigListRef to a reference to bigList
set numItems to 100000
set t to (time of (current date)) --Start timing operations
repeat with n from 1 to numItems
copy n to the end of bigListRef
end
set total to (time of (current date)) - t --End timing
end buildList
buildList()
it breaks, yielding an error message saying, "Can't make bigList into type reference." Why does this break, and what is the correct way to efficiently build a list within a handler other than run()?
set end of l to i seems to be faster than copy i to end of l:
on f()
set l to {}
repeat with i from 1 to 100000
set end of l to i
end repeat
l
end f
set t to time of (current date)
set l to f()
(time of (current date)) - t
You could also use a script object:
on f()
script s
property l : {}
end script
repeat with i from 1 to 100000
copy i to end of l of s
end repeat
l of s
end f
set t to time of (current date)
set l to f()
(time of (current date)) - t
100000 is also over the limit of items that can be saved in compiled scripts, so you'll get an error like this if you run the script and try to save it as scpt:
The document “Untitled” could not be saved as “Untitled.scpt”.
You can either put set l to f() inside a handler so l is local, add set l to {} to the end, or save the script as .applescript.
Here are the results and methods of a speed test I did a while ago. Note that the first result in each trial is slower because the script had not compiled previously.
list_speed.xlsx
Adding "global bigList" to the first line of buildList() fixes the compiler error. It seems that within the run handler, variables are naked by default, and the "a reference to" operator is useful. However, in other contexts, variables are already essentially indirect references, and creating another reference layer breaks stuff. Declaring a global variable in these contexts strips the indirect reference and allows the "a reference to" operator to work, but this is unnecessary. Just use the default indirect reference.
If this is unclear, it's because I don't completely understand the mechanism. If you have a better grasp of what's going on here, please comment below.

Resources