🗓️ REI UKG Schedule Extractor

Setup Guide & Documentation

📋 Overview

This tool automatically extracts your work schedule from the UKG system and imports it into Google Calendar, with automatic duplicate detection.

What you'll need:

Part 1: Customize Your Bookmarklet Code

1Fill in Your Information

Before creating the bookmarklet, you need to customize these variables:

YOUR_NAME - Your first name (e.g., "Jordan", "Sarah", "Mike")

YOUR_LOCATION - Your REI store nickname (e.g., "REI Conshy", "REI Seattle", "REI Denver")
Note: This is the short name used in event titles, not the full address

REMINDER_MINUTES - How many minutes before shifts you want a reminder (e.g., "45", "30", "60")

2Generate Your Bookmarklet Code

Copy the code below and replace the THREE variables at the top with your information:

javascript:(function(){const YOUR_NAME="Jordan";const YOUR_LOCATION="REI Conshy";const REMINDER_MINUTES="45";function waitForElements(selector,timeout){return new Promise((resolve,reject)=>{const interval=500;let elapsed=0;const id=setInterval(()=>{const elems=document.querySelectorAll(selector);if(elems.length>0){clearInterval(id);resolve(elems)}else if((elapsed+=interval)>=timeout){clearInterval(id);reject(new Error('Timeout waiting for '+selector))}},interval)})}function convertTo24Hour(time12h){const[time,modifier]=time12h.split(' ');let[hours,minutes]=time.split(':');if(hours==='12'){hours='00'}if(modifier==='PM'){hours=parseInt(hours,10)+12}return hours.toString().padStart(2,'0')+':'+minutes+':00'}function createDateTime(date,time){const time24=convertTo24Hour(time);return date+'T'+time24}(async function(){try{await waitForElements('li[id^="myschedule-day_"]',5000);const results=(function(){const dayItems=[...document.querySelectorAll('li[id^="myschedule-day_"]')];const res=[];dayItems.forEach(li=>{const dateMatch=li.id.match(/myschedule-day_(\d{4}-\d{2}-\d{2})/);const workDate=dateMatch?dateMatch[1]:null;if(!workDate)return;const timeLabel=li.querySelector('time.label');if(!timeLabel)return;const timeText=timeLabel.textContent.trim();const timeSpan=timeText.split('[')[0].trim();const[startTime,endTime]=timeSpan.split('-').map(t=>t.trim());if(startTime&&endTime){res.push({title:YOUR_NAME+' at '+YOUR_LOCATION+' '+startTime+' - '+endTime,startDateTime:createDateTime(workDate,startTime),endDateTime:createDateTime(workDate,endTime),description:'',location:YOUR_LOCATION,reminderMinutes:REMINDER_MINUTES,inviteEmails:''})}});return res})();if(results.length===0){alert('No shifts found!');return}let csv='title,startDateTime,endDateTime,description,location,reminderMinutes,inviteEmails\n';results.forEach(item=>{csv+='"'+item.title+'","'+item.startDateTime+'","'+item.endDateTime+'","'+item.description+'","'+item.location+'","'+item.reminderMinutes+'","'+item.inviteEmails+'"\n'});navigator.clipboard.writeText(csv).then(()=>{alert('Copied '+results.length+' shifts to clipboard!')}).catch(err=>{const ta=document.createElement('textarea');ta.value=csv;ta.style.position='fixed';ta.style.opacity='0';document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);alert('Copied '+results.length+' shifts to clipboard!')})}catch(error){alert('Error: '+error.message)}})()})();
💡 Example: If your name is Sarah and you work at REI Seattle with 30-minute reminders, change the first three lines to:
const YOUR_NAME="Sarah";
const YOUR_LOCATION="REI Seattle";
const REMINDER_MINUTES="30";

Part 2: Create the Bookmarklet

Desktop Setup (Required First)

  1. Show your bookmarks bar:
    • Chrome/Edge: Press Ctrl+Shift+B (Windows) or Cmd+Shift+B (Mac)
    • Safari: View menu → Show Favorites Bar
    • Firefox: View menu → Toolbars → Bookmarks Toolbar
  2. Create the bookmark:
    • Right-click on the bookmarks bar
    • Select "Add Page" or "Add Bookmark"
    • Name: Extract Schedule (or whatever you want)
    • URL: Paste your customized bookmarklet code from Part 1
    • Click Save
  3. Sync to mobile (optional):
    • iPhone/iPad: The bookmark will sync via iCloud if you use Safari
    • Android: The bookmark will sync via your Google account if you use Chrome
    • Note: Firefox mobile doesn't support bookmarklets reliably

Part 3: Set Up Google Sheets & Apps Script

1Create Your Google Sheet

  1. Go to Google Sheets
  2. Create a new blank spreadsheet
  3. Rename the sheet tab (bottom left) to exactly: rei_cal
  4. In row 1, add these headers:
    • Click on cell A1
    • Copy the line below and paste it into A1
    • It will automatically fill across the row (A1 through G1)
title startDateTime endDateTime description location reminderMinutes inviteEmails
💡 Tip: When you paste this into A1, Google Sheets will automatically detect the tabs and split it across cells A1 through G1. No need to paste into each cell individually!

2Get Your Google Calendar ID

  1. Open Google Calendar
  2. Create a new calendar (recommended) or use an existing one:
    • Left sidebar → Click + next to "Other calendars"
    • Select "Create new calendar"
    • Name it "Work Schedule" or similar
    • Click "Create calendar"
  3. Get the Calendar ID:
    • Left sidebar → Find your calendar → Click the three dots → Settings
    • Scroll down to "Integrate calendar"
    • Copy the Calendar ID (looks like: [email protected])

3Install the Apps Script

  1. In your Google Sheet, go to: Extensions → Apps Script
  2. Delete any default code in the editor
  3. Copy and paste this complete script:
/** * Google Apps Script to import REI schedule data into Google Calendar * CUSTOMIZE THE VARIABLES BELOW */ function importScheduleToCalendar() { try { // ======================================== // CUSTOMIZE THESE VARIABLES // ======================================== const CALENDAR_ID = 'YOUR_CALENDAR_ID_HERE'; // Paste your Calendar ID here const SHEET_NAME = 'rei_cal'; // Don't change unless you named your sheet differently const DEFAULT_LOCATION = 'YOUR_STORE_ADDRESS_HERE'; // Optional: full store address const EVENT_COLOR = CalendarApp.EventColor.GREEN; // Options: RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE, GRAY // ======================================== // DON'T EDIT BELOW THIS LINE // ======================================== const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const sheet = spreadsheet.getSheetByName(SHEET_NAME); if (!sheet) { throw new Error(`Sheet "${SHEET_NAME}" not found`); } const calendar = CalendarApp.getCalendarById(CALENDAR_ID); if (!calendar) { throw new Error('Calendar not found or no access'); } const lastRow = sheet.getLastRow(); console.log(`Sheet has ${lastRow} rows total`); const data = sheet.getRange(1, 1, lastRow, sheet.getLastColumn()).getValues(); const headers = data[0]; const rows = data.slice(1).filter(row => row[1] && row[2]); console.log(`Found ${rows.length} valid rows with data`); if (rows.length === 0) { SpreadsheetApp.getUi().alert('No Data', 'No valid rows found to import', SpreadsheetApp.getUi().ButtonSet.OK); return; } const colIndices = { title: headers.indexOf('title'), startDateTime: headers.indexOf('startDateTime'), endDateTime: headers.indexOf('endDateTime'), description: headers.indexOf('description'), location: headers.indexOf('location'), reminderMinutes: headers.indexOf('reminderMinutes'), inviteEmails: headers.indexOf('inviteEmails') }; const requiredCols = ['title', 'startDateTime', 'endDateTime']; for (const col of requiredCols) { if (colIndices[col] === -1) { throw new Error(`Required column "${col}" not found`); } } let minDate = null; let maxDate = null; rows.forEach(row => { const start = new Date(row[colIndices.startDateTime]); const end = new Date(row[colIndices.endDateTime]); if (!isNaN(start.getTime())) { if (!minDate || start < minDate) minDate = start; } if (!isNaN(end.getTime())) { if (!maxDate || end > maxDate) maxDate = end; } }); console.log(`Fetching existing events from ${minDate} to ${maxDate}`); const existingEvents = calendar.getEvents(minDate, maxDate); const existingEventsMap = new Map(); existingEvents.forEach(event => { const key = `${event.getTitle()}_${event.getStartTime().getTime()}`; existingEventsMap.set(key, event); }); console.log(`Found ${existingEvents.length} existing events in date range`); let processedCount = 0; let skippedCount = 0; let updatedCount = 0; for (let i = 0; i < rows.length; i++) { const row = rows[i]; console.log(`Processing row ${i + 1} of ${rows.length}`); try { const title = row[colIndices.title] || 'Work Shift'; const startDateTime = new Date(row[colIndices.startDateTime]); const endDateTime = new Date(row[colIndices.endDateTime]); const description = row[colIndices.description] || ''; const location = row[colIndices.location] || DEFAULT_LOCATION; const reminderMinutes = row[colIndices.reminderMinutes] || ''; const inviteEmails = row[colIndices.inviteEmails] || ''; if (isNaN(startDateTime.getTime()) || isNaN(endDateTime.getTime())) { console.error(`Invalid date format in row ${i + 2}`); skippedCount++; continue; } const eventKey = `${title}_${startDateTime.getTime()}`; const existingEvent = existingEventsMap.get(eventKey); const options = { description: description, location: location }; let event; if (existingEvent) { event = existingEvent; event.setTime(startDateTime, endDateTime); event.setDescription(description); event.setLocation(location); updatedCount++; } else { event = calendar.createEvent(title, startDateTime, endDateTime, options); processedCount++; } event.setColor(EVENT_COLOR); if (inviteEmails) { const guestList = inviteEmails.toString().split(',') .map(email => email.trim()) .filter(email => email.length > 0 && email.includes('@')); if (guestList.length > 0) { guestList.forEach(email => { try { event.addGuest(email); } catch (e) { console.log(`Could not add guest ${email}: ${e.message}`); } }); } } if (reminderMinutes) { event.removeAllReminders(); const remindersList = reminderMinutes.toString().split(','); remindersList.forEach(reminder => { const minutes = parseInt(reminder.trim()); if (!isNaN(minutes) && minutes >= 0) { event.addPopupReminder(minutes); } }); } Utilities.sleep(1000); } catch (error) { console.error(`Error processing row ${i + 2}:`, error); skippedCount++; } } const message = `Import Complete!\n\nNew events created: ${processedCount}\nExisting events updated: ${updatedCount}\nRows skipped: ${skippedCount}\nTotal rows processed: ${rows.length}`; SpreadsheetApp.getUi().alert('Schedule Import Results', message, SpreadsheetApp.getUi().ButtonSet.OK); console.log(message); } catch (error) { console.error('Error in importScheduleToCalendar:', error); SpreadsheetApp.getUi().alert('Error', `Failed to import schedule: ${error.message}`, SpreadsheetApp.getUi().ButtonSet.OK); } } function onOpen() { const ui = SpreadsheetApp.getUi(); ui.createMenu('REI Schedule') .addItem('Import to Calendar', 'importScheduleToCalendar') .addToUi(); } function testCalendarAccess() { const CALENDAR_ID = 'YOUR_CALENDAR_ID_HERE'; // Same Calendar ID as above try { const calendar = CalendarApp.getCalendarById(CALENDAR_ID); if (calendar) { console.log(`Calendar found: ${calendar.getName()}`); SpreadsheetApp.getUi().alert('Success', `Calendar access confirmed: ${calendar.getName()}`, SpreadsheetApp.getUi().ButtonSet.OK); } else { throw new Error('Calendar not found'); } } catch (error) { console.error('Calendar access error:', error); SpreadsheetApp.getUi().alert('Error', `Cannot access calendar: ${error.message}`, SpreadsheetApp.getUi().ButtonSet.OK); } }
⚠️ Important: You must customize these variables in the script:
  1. Save the script:
    • Click the disk icon or File → Save
    • Name it something like "Schedule Importer"
  2. Close the Apps Script tab and return to your Google Sheet
  3. Refresh the Google Sheet page - you should now see a new menu: "REI Schedule"

Part 4: How to Use the Complete System

Every Time You Get a New Schedule:

  1. Go to your UKG schedule page:
    https://recreationalequip-ss3.prd.mykronos.com/wfd/ess/myschedule#/
  2. Load all your shifts:
    Click "Load More" until you see all the shifts you want to import
  3. Run the bookmarklet:
    Click the "Extract Schedule" bookmark in your bookmarks bar
    You'll see an alert: "Copied X shifts to clipboard!"
  4. Paste into Google Sheet:
    • Open your Google Sheet
    • Click on cell A2 (the row below the headers)
    • Paste: Ctrl+V (Windows) or Cmd+V (Mac)
    • Your schedule data should fill in columns A through G
  5. Import to Calendar:
    • In Google Sheet, click: REI Schedule → Import to Calendar
    • Wait for the import to complete (you'll see a progress indicator)
    • A popup will show: "Import Complete! New events created: X"
  6. Check your calendar:
    Open Google Calendar - your shifts should now appear!
  7. Clean up the sheet (optional):
    • Select all the data rows you just pasted (not the header row)
    • Right-click → Delete rows
    • This keeps your sheet clean for next time

Troubleshooting

"No shifts found!" alert

Bookmarklet doesn't work on mobile

"Calendar not found" error

Duplicate events being created

Events have wrong timezone

Tips & Best Practices

Privacy & Security Notes


Version 1.0 - Created for REI UKG Schedule Extraction
Questions or issues? Share this guide with your team!