UMN Schedule to Google Calendar

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

  1. 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.
  2. Right click anywhere on the page and click "Inspect". On Firefox it's called "Inspect Element" and on Safari you can follow these instructions.
  3. 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;
    }
    
    
    let weeks = [];
    
    function proceed(){
        let weekData = getClassDataForWeek();
        if(!isWeekEmpty(weekData)){
            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 dd:dd - dd:dd AM/PM format
                        let lastIsAM = !!classTime.match(/AM/g);
                        let firstIsAM = lastIsAM;
                        let [startTime, endTime] = classTime.match(/\d{1,2}:\d{1,2}/g);
                        // I strongly dislike date and times
                        // I don't care, I'm not supporting classes that happen around midnight
                        let [startTimeHour, endTimeHour] = [startTime.split(':')[0], endTime.split(':')[0]].map(x=>parseInt(x));
                        if(!lastIsAM && ((endTimeHour === 12 && startTimeHour !== 12) || startTimeHour > endTimeHour)){
                            firstIsAM = true;
                        }
    
                        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();
                    
  4. 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.
  5. Follow step 2 from Import Events to Google Calendar.
  6. 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.
Note that this method produces many individual events. Unfortunately Google does not appear to support importing recurring events.

Excel Formula Tool

Harry's 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.