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:
- A desktop/laptop computer (for initial setup)
- Access to UKG schedule system
- A Google account with Google Sheets and Google Calendar
Part 1: Customize Your Bookmarklet Code
Step 1
Generate Your Bookmarklet Code
Copy the code below to get started — you'll customize three variables in it next:
Bookmarklet code — full snippet (1 line)
javascript:(function(){const YOUR_NAME="Jordan";const YOUR_LOCATION="My Store";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)}})()})();
Step 2
Customize Your Variables
Before using the bookmarklet, replace these three variables in the code you just copied:
YOUR_NAME — Your first name (e.g., "Jordan", "Sarah", "Mike")
YOUR_LOCATION — Your store nickname (e.g., "Downtown", "Seattle", "North 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")
If your name is Sarah and you work at the Seattle store with 30-minute reminders, change the first three lines to:
const YOUR_NAME="Sarah"; const YOUR_LOCATION="Seattle"; const REMINDER_MINUTES="30";
Part 2: Create the Bookmarklet
Desktop Setup (Required First)
-
Show your bookmarks bar:
- Chrome/Edge: Press
Ctrl+Shift+B(Windows) orCmd+Shift+B(Mac) - Safari: View menu → Show Favorites Bar
- Firefox: View menu → Toolbars → Bookmarks Toolbar
- Chrome/Edge: Press
-
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
-
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
Step 1
Create Your Google Sheet
- Go to Google Sheets
- Create a new blank spreadsheet
- Rename the sheet tab (bottom left) to exactly:
work_cal -
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
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!
Step 2
Get Your Google Calendar ID
- Open Google Calendar
-
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"
-
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:
abc123@group.calendar.google.com)
Step 3
Install the Apps Script
- In your Google Sheet, go to: Extensions → Apps Script
- Delete any default code in the editor
- Copy and paste this complete script:
Apps Script — full code (~200 lines)
/**
* Google Apps Script to import your work 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 = 'work_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('Work 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);
}
}
You must customize these variables in the script:
- Line 11: Replace
YOUR_CALENDAR_ID_HEREwith your Calendar ID - Line 13: Replace
YOUR_STORE_ADDRESS_HEREwith your store's full address (optional) - Line 140: Replace
YOUR_CALENDAR_ID_HEREagain (same ID as line 11)
-
Save the script:
- Click the disk icon or File → Save
- Name it something like "Schedule Importer"
- Close the Apps Script tab and return to your Google Sheet
- Refresh the Google Sheet page — you should now see a new menu: "Work Schedule"
Part 4: How to Use the Complete System
Every Time You Get a New Schedule
- Go to your UKG schedule page:
https://recreationalequip-ss3.prd.mykronos.com/wfd/ess/myschedule#/ - Load all your shifts:
Click "Load More" until you see all the shifts you want to import - Run the bookmarklet:
Click the "Extract Schedule" bookmark in your bookmarks bar. You'll see an alert: "Copied X shifts to clipboard!" -
Paste into Google Sheet:
- Open your Google Sheet
- Click on cell A2 (the row below the headers)
- Paste:
Ctrl+V(Windows) orCmd+V(Mac) - Your schedule data should fill in columns A through G
-
Import to Calendar:
- In Google Sheet, click: Work 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"
- Check your calendar:
Open Google Calendar — your shifts should now appear! -
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
- Make sure you clicked "Load More" to show all shifts before running the bookmarklet
- Verify you're on the correct UKG schedule page
Bookmarklet doesn't work on mobile
- Firefox mobile doesn't support bookmarklets reliably
- Use Safari (iOS) or Chrome (Android) instead
- Or just use your desktop/laptop
"Calendar not found" error
- Double-check your Calendar ID in the Apps Script (lines 11 and 140)
- Make sure you have access to that calendar
- Try running: Work Schedule → Test Calendar Access
Duplicate events being created
- The script automatically dedupes based on title + start time
- If you see duplicates, check that your event titles are consistent
- Make sure you're using the same calendar each time
Events have wrong timezone
- Google Calendar uses your computer's timezone settings
- Check your Google Calendar timezone: Settings → General → Time zone
Tips & Best Practices
- Run this weekly when your schedule is posted
- Don't delete the header row in your Google Sheet (row 1)
- Clear old data from the sheet periodically to keep it clean
- Use a dedicated calendar for work shifts (easier to hide/show)
- Customize the event color in Apps Script to make shifts stand out
- Share with coworkers — they just need to customize their own variables
Privacy & Security Notes
- This tool runs entirely in your browser and Google account
- No data is sent to any third-party servers
- Only you have access to your Google Sheet and Calendar
- The bookmarklet only reads data from the UKG page — it doesn't modify anything