# UMN Schedule to Google Calendar

### Updated Fall 2019

This script ports the class schedule from MyU to your google calendar. To use:

- Go to MyU (myu.umn.edu) and click the Academics tab. Make sure the "My Classes" tab is selected and use the arrows navigate to the first week of classes.
- Right click anywhere on the page and click "Inspect". On Firefox it's called "Inspect Element" and on Safari you can follow these instructions.
- Scroll to the Console tab on the window that pops up. Copy and paste the following code directly into the
console and press enter. Note: Never paste random code from the internet into
your console! Make sure you trust me before continuing. A malicious attacker in my place could perform a myriad of
actions including installing malware, unenrolling you from all of your classes and paying your tuition.
`// to run: go to myU and to the schedule page, then inspect element and paste this in the console function getWeekDateString(){ let nextWeekButton = document.querySelector('[title=\'Next Week\']'); let dateH = nextWeekButton.parentElement.children[1]; let fullString = dateH.innerText; return fullString.match(/\d\d\/\d\d\/\d\d\d\d/g)[0]; } function getClassDataForWeek(){ let days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday']; let weekData = {}; for(let d of days){ weekData[d] = []; } for(let day of days){ let dayGroup = document.querySelector('[data-day=\''+day+'\']'); if(!dayGroup){ return weekData; } // each class is an element let classElements = []; for(let el of dayGroup.childNodes){ if(el.nodeName === 'A'){ classElements.push(el); } } let dayClassInfo = []; for(let c of classElements){ let name = c.children[0].innerText; let details = c.children[1].innerText; let [type, time, place] = details.split('\n'); weekData[day].push({name:name,time:time,place:place,type:type}); } } return weekData; } function isWeekEmpty(data){ for(let key in data){ if(data[key].length > 0){ return false; } } return true; } function nextWeek(){ let nextWeekButton = document.querySelector('[title=\'Next Week\']'); nextWeekButton.click(); } Date.prototype.addDays = function(days) { var date = new Date(this.valueOf()); date.setDate(date.getDate() + days); return date; } function mod(n, m) { return ((n % m) + m) % m; } function toMilitary(hour, isAM){ if(isAM){ if(hour === 12){ return 0; } return hour; } if(hour === 12){ return 12; } return hour + 12; } let weeks = []; let weeksEmpty = 0; function proceed(){ let weekData = getClassDataForWeek(); if(weeksEmpty <= 1){ if(isWeekEmpty(weekData)){ weeksEmpty++; }else{ weeksEmpty = 0; weeks.push({weekDate:getWeekDateString(), data:weekData}); } nextWeek(); // wait until it finishes loading setTimeout(proceed, 3000); }else{ let csvFile = 'Subject,Start Date,Start Time,End Date,End Time,All Day Event,Description,Location,Private'; let days = ['Monday','Tuesday','Wednesday','Thursday','Friday']; let dayDateConversion = {'Monday':0,'Tuesday':1,'Wednesday':2,'Thursday':3,'Friday':4}; for(let week of weeks){ // map days to dates let weekDate = new Date(week.weekDate); for(let d of days){ let classes = week.data[d]; for(let c of classes){ let classDate = weekDate.addDays(dayDateConversion[d]).toLocaleDateString(); let className = c.name; let classLocation = c.place; let classTime = c.time; // isolate start/end time. myU uses the hh:mm - hh:mm AM/PM format let lastIsAM = !!classTime.match(/AM/g); let [startTime, endTime] = classTime.match(/\d{1,2}:\d{1,2}/g); let [startTimeHour, endTimeHour] = [startTime.split(':')[0], endTime.split(':')[0]].map(x=>parseInt(x)); // we want to know if the first hour occurs in the AM or the PM. // we make the assumption that no class lasts more than 12 hours (apparently the folks // who made myU thought so too), so we take the option (am vs pm) in which the class length is less let militaryAMOption = toMilitary(startTimeHour, true); let militaryPMOption = toMilitary(startTimeHour, false); let militaryEnd = toMilitary(endTimeHour, lastIsAM); let firstIsAM; if(mod(militaryEnd - militaryAMOption, 24) < mod(militaryEnd - militaryPMOption, 24)){ firstIsAM = true; } else { firstIsAM = false; } let finalStartTime = startTime + (firstIsAM ? ' AM' : ' PM'); let finalEndTime = endTime + (lastIsAM ? ' AM' : ' PM'); let classType = c.type; // csv columns are Subject, Start Date, Start Time, End Date, End Time, All Day Event, Description, Location, Private let row = '"'+className+'","'+classDate+'","'+finalStartTime+'","'+classDate+'","'+finalEndTime+'",'+'False,"'+c.type+'","'+classLocation+'",False'; csvFile += '\n'+row; } } } // download as csv file let blob = new Blob([csvFile], {type: 'text/csv;charset=utf-8;'}); let url = URL.createObjectURL(blob); let link = document.createElement("a"); link.setAttribute("href", url); link.setAttribute("download", "class_schedule.csv"); link.innerHTML= "Click Here to download"; document.body.appendChild(link); // Required for FF link.click(); } } proceed();`

- Wait patiently while the program scrolls through all the weeks. If you are really impatient you
can change the line
`setTimeout(proceed, 3000);`

to`setTimeout(proceed, 1000);`

or even`setTimeout(proceed, 500);`

if you have really fast internet and are feeling lucky. After it finishes it should download a csv file called`class_schedule.csv`

. - Follow step 2 from Import Events to Google Calendar.
- If it's spring semester, you'll have to repeat this process for the weeks after spring break (the script stops at the first full week without classes). Reload and start at step 1 except navigate to the week after spring break instead of the first week of the semester.

# Excel Formula Tool

Typing long expressions into Google sheets or MS excel can be a challenge. A friend thought a tool that used an math expression editor to generate an excel-style formula would be helpful. I already had a half-way expression parser sitting around from Koval's 3D Grapher, so I repurposed it here. A few notes

- Be sure to separate adjacent variables with "*". I.e. write "a*b*c" (\(a\cdot b\cdot c\)) instead of "abc" (\(abc\)).
- Both Google Sheets and MS Excel have
very strange behavior when evaluating exponents prefixed with a negative sign. Turns out
that
`-x^n = (-x)^n`

in excel language. Instead write`-1*x^n`

.