Outlook and Google Calendar sync: four methods and their delays
Calendar links update every 3 to 6 hours or slower, one way only; Google's Outlook plugin syncs every 10 minutes. A script tested on sample files merges both.
The short read
Microsoft says a subscribed calendar in Outlook updates about every 3 to 6 hours and can take more than 24.
- Calendar links between Outlook and Google run one way and read-only; only the Google account in new Outlook and Google's plugin sync both ways.
- Our merge script matched all 137 occurrences in made-up sample calendars; skipping one time zone step moved 34 of them by an hour.
What you can do with this
If a meeting booked today must show up on the other calendar today, skip calendar links: add the Google account to new Outlook, or ask your Workspace admin about Google's Outlook plugin.
In this article
- The four methods side by side
- Calendar links are one-way snapshots on a timer
- Method 1: put an Outlook calendar into Google Calendar
- Method 2: put a Google calendar into Outlook
- Method 3: add the Google account to new Outlook
- Method 4: Google’s Outlook plugin for Workspace companies
- What none of the four does
- Our test: made-up calendars with an answer key
- The script
- Step 1: get the two calendar links
- Step 2: run it once with the check on
- Step 3: put the file where a calendar app can fetch it
- Two things the script does not handle
- Which method to pick
- How we made this
Outlook and Google Calendar do not sync with each other by default, and the four built-in ways to connect them differ more than their setup screens suggest. Two run one way, read-only, with delays of hours by Microsoft’s own estimate. Two sync both ways but tie you to a particular Outlook app or a Google Workspace account.
If you keep a work calendar in Microsoft 365 and a personal or client calendar in Google, the method you pick decides whether a meeting booked at 9:00 shows up on the other side by 9:10 or the next day. This guide sets out each method’s direction, its documented delay and what it needs. Then it gives a tested Python script for the case none of them covers: one combined feed that shows only busy blocks.
We read Microsoft’s and Google’s help pages on October 29, 2026, and ran every test on that date.
The four methods side by side
| Method | Direction | Edit events from the other side | Documented delay | What it needs |
|---|---|---|---|---|
| Outlook publishes an ICS link, Google adds it From URL | Outlook to Google | No, read-only | None stated by Google | Publishing allowed on the account; a computer browser |
| Google’s secret iCal address, Outlook Subscribe from web | Google to Outlook | No, read-only | About 3 hours (Outlook.com) or 6 hours (work Outlook on the web), can exceed 24 | The secret address not hidden by a Workspace admin |
| Google account added to new Outlook or Outlook mobile | Both ways | Yes, within the features Microsoft lists | None stated by Microsoft | A copy of the account held in Microsoft’s cloud |
| Google Workspace Sync for Microsoft Outlook (GWSMO) | Both ways | Yes | Every 10 minutes, or at once for a new invitation | Google Workspace, classic Outlook for Windows, admin setup |
The rows come from the pages linked in each section below. “None stated” means we found no interval on the vendor’s page, not that the sync is instant.
Calendar links are one-way snapshots on a timer
Both directions of the link method use the same file format, iCalendar, defined in RFC 5545. One side publishes a web address that returns an .ics file. The other side downloads it again from time to time and redraws a separate, read-only calendar.
Microsoft states the read-only part plainly in its calendar sharing help: “HTML and ICS calendars are read-only, so recipients won’t be able to edit your calendar.” It also says “How often your ICS calendar syncs depends on the recipient’s email provider.”
So the delay is set by whoever downloads the file. The standard has a field for a hint: RFC 7986 defines REFRESH-INTERVAL as “a suggested minimum interval for polling for changes”. Microsoft’s Outlook 2007 documentation says the desktop app of that era updated each subscription “at the publisher’s recommended interval”, which users could override.
We downloaded Google’s public US holidays calendar twice, 66 seconds apart, to see what a Google feed carries. The file had 317 events and no REFRESH-INTERVAL or X-PUBLISHED-TTL line at all. So that feed, at least, gives a downloading app no hint about how often to check.
Method 1: put an Outlook calendar into Google Calendar
This is the route when your work calendar lives in Microsoft 365 or Outlook.com and you want to see it inside Google Calendar.
In Outlook on the web, per Microsoft’s steps: open the Calendar view, select Settings, then Shared calendars. Under Publish a calendar, pick the calendar and “how much detail people can see”, then select Publish. You get an HTML link for browsers and an ICS link for calendar apps. Copy the ICS link.
In Google Calendar, per Google’s steps: on a computer, next to Other calendars, click Add other calendars, choose From URL, paste the link and click Add calendar. Google says “you must use a computer web browser” to subscribe and that you “can’t subscribe to a calendar in the Google Calendar app for Android, iPhone, or iPad”.
Google’s subscription page gives no refresh interval. We checked it on October 29, 2026, and found none, so do not count on minutes.
The catch on a work account: publishing to people outside the company is an admin decision. Microsoft’s sharing policy reference says sharing policies allow “people-to-people sharing of their calendar information with anonymous users through the use of Internet Calendar Publishing”. If your admin’s policy leaves anonymous sharing out, you have no link to publish.
When not to use it: when the link would carry meeting titles you may not show outside the company. Anyone who has the ICS link can read whatever detail level you chose. Pick the free/busy level, or use the busy-only script below.
Method 2: put a Google calendar into Outlook
This is the reverse, and the one Microsoft documents with numbers.
In Google Calendar, per Google’s help: open Settings, pick the calendar under Settings for my calendars, click Integrate calendar and copy the link under Secret address in iCal format. Google warns: “Only you should know the Secret Address for your calendar.” If it leaks, click Reset to “invalidate the old address and create a new one”. On a work or school account, “your admin might’ve changed the sharing settings”, and the address may be missing.
In Outlook, per Microsoft’s steps: select Add calendar, then Subscribe from web, paste the address and select Import. Microsoft’s Google-specific page gives the same steps for new Outlook for Windows.
Microsoft’s subscribe page gives two different timings on one page:
- Outlook.com (personal accounts): “This update can take more than 24 hours, although updates should happen approximately every 3 hours.”
- Outlook on the web (work or school accounts): “This update can take more than 24 hours, although updates should happen approximately every 6 hours.”
Do not pick Upload from file instead. Microsoft says that when you import an .ics file, “your calendar doesn’t refresh the imported events, even if the calendar’s owner makes an update.” The same holds for the classic Outlook import route on the Google page: “The imported calendar is a static snapshot; it does not self-update.”
When not to use it: for anything booked at short notice. A meeting added at 10:00 may not appear in Outlook until the afternoon, by Microsoft’s own estimate.
Method 3: add the Google account to new Outlook
This route does not use a link. New Outlook for Windows signs in to the Google account itself, so Google’s calendar appears in Outlook as a calendar you can work in.
Steps, from Microsoft’s Gmail page: on the View tab select View settings (or File, then Account info). Select Accounts, then Your accounts, then Add account. Enter the Gmail address, select Continue, and on Google’s permissions window select Allow.
What happens to the data: Microsoft’s cloud sync page says “a copy of your email, calendar, and contacts will be synchronized between your email provider and Microsoft data centers.” In new Outlook for Windows this covers Gmail and Yahoo accounts. Outlook for iOS and Android add iCloud. The calendar features listed include recurring meetings, “Free, tentative, busy, OOF”, “Mark as private” and replying to invitations.
Two terms come with that copy. The page says the Microsoft Services Agreement and Privacy Statement apply “to your account data”, and the provider’s own terms still apply too. Microsoft’s account page also warns that Remove from all devices deletes that cloud copy, and items that exist only there “might be permanently lost”.
Microsoft states no sync interval for this route. We could not measure one, because we had no accounts to test with.
When not to use it: when company policy says Google data may not be copied to another provider. Note too that this puts both calendars in one app, side by side. It does not copy events from one calendar into the other.
Method 4: Google’s Outlook plugin for Workspace companies
Google Workspace Sync for Microsoft Outlook (GWSMO) is for a company on Google Workspace whose staff want to keep classic Outlook. Google’s overview calls it “a plugin that allows users to synchronize mail, calendar events, contacts, notes, and tasks between their new Google Account and Outlook.”
It is the only method of the four with a published schedule. Google’s list of what syncs says GWSMO syncs each time Outlook starts. Calendar then syncs “Every 10 minutes or immediately after you create or receive a meeting invitation”. Email syncs every minute and contacts every 20 minutes.
The system requirements narrow who can use it:
- Windows 10 or 11 (older versions are listed but no longer supported by Microsoft), with Outlook 2016, 2019, 2021 or the Microsoft 365 desktop app.
- Not supported: macOS, and “Outlook from Microsoft 365 (web, Android, macOS, and iOS versions) and Outlook from Microsoft Store”.
- “To install GWSMO, users must have local administrator permissions for their computers,” and port 443 must be open for Outlook.exe and ProfileEditor.exe.
- An admin turns it on first in the Google Admin console.
Google’s FAQ adds a limit that matters for mixed setups: “GWSMO doesn’t support having an Exchange account in a GWSMO profile.” It suits a company that moved to Google. It will not merge a Microsoft 365 work mailbox with a Google one in the same profile.
For a company that runs both systems, Google’s admin-level option is Calendar Interop. It lets users of each system see the other’s availability. Google says “only busy blocks are visible” for availability, and only “titles and locations” for event lookups. It works with Exchange 2016 or newer and Exchange Online. It shares free/busy, not copies of events.
What none of the four does
None of the methods turns two calendars into one. The link methods add a separate overlay. New Outlook shows two accounts next to each other. GWSMO serves a single Google account.
That leaves a common need open: one feed of “when am I busy” across a Google and a Microsoft calendar. You might give it to a colleague, an assistant or a booking tool without showing anyone your meeting titles.
Merging two .ics files sounds like pasting one file into the other. Three things break when you do that:
- The same meeting appears twice. If one invitation reaches both accounts, both files can hold it. RFC 5545 defines
UIDas “the persistent, globally unique identifier for the calendar component” andSEQUENCEas “the revision sequence number”, so when two entries share aUID, a merge should keep one copy, the newest revision. - Time zones go missing. The standard says “An individual “VTIMEZONE” calendar component MUST be specified for each unique “TZID” parameter value”. A merged file needs every zone definition from every input.
- Some times move by an hour. Google-style files can carry
X-WR-TIMEZONE, a line outside the standard. The x-wr-timezone project explains that “Strict interpretations according to RFC 5545 ignore” it. In a naive merge that header is lost, and events stored in UTC then sit an hour away from what Google shows while daylight saving time is in effect.
Our test: made-up calendars with an answer key
We built two calendar files for 2026 by script, with made-up events, and an answer key computed separately with Python’s zoneinfo and dateutil, without the calendar libraries the merge uses.
The first file has an X-WR-TIMEZONE header and a weekly standup with one skipped week and one moved instance. It also holds a weekly client call stored in UTC, a day the office is closed, a weekly lunch marked free, a cancelled offsite and one shared meeting. The second file names its zone “Eastern Standard Time”, a Windows-style name, with its own definition as the standard allows. It holds a monthly meeting with one cancelled month, a later revision of the shared meeting, and a meeting with a location, notes and an attendee.
The answer key lists 137 occurrences, 117 of them busy. Here is what the script produced on October 29, 2026, with Python 3.11.17, icalendar 7.3.0, recurring-ical-events 3.8.2 and x-wr-timezone 2.0.1:
| Test | Result |
|---|---|
| Full merge against the answer key | 137 of 137 occurrences at the right time; 1 duplicate meeting merged, newer revision kept |
| Busy-only merge | 117 of 117 busy blocks; every title reads “Busy”; no title, location, note or attendee text left in the file |
Same merge without the X-WR-TIMEZONE step |
34 of 52 client calls an hour late, March 9 to October 26 |
| Same inputs run twice | Identical output files, byte for byte |
| Output merged again with one input | 137 of 137, no doubles |
| An empty file, an HTML error page or a missing file as input | Stops with a message, exit code 1, previous output untouched |
| Google’s real US holidays feed plus both samples | 165 occurrences checked, 0 missing (28 holidays in 2026); in busy-only mode all 317 holiday entries left out |
The last row needs a word. Every one of Google’s 317 holiday entries is marked TRANSP:TRANSPARENT, which RFC 5545 defines as transparent “to busy time searches”. A holiday is free time, so a busy-only feed should drop it, and the script does.
The script
Save it as merge_ics.py. It needs Python 3.10 or later (icalendar 7 requires it) and three libraries: python3 -m pip install icalendar recurring-ical-events x-wr-timezone.
#!/usr/bin/env python3
"""Merge .ics calendars into one file, optionally as busy-only blocks.
usage: python3 merge_ics.py out.ics in1.ics [in2.ics ...] [--busy-only]
[--check-from 2026-01-01 --check-to 2027-01-01]
Inputs can be file paths or https:// links. Needs:
python3 -m pip install icalendar recurring-ical-events x-wr-timezone
"""
import argparse, hashlib, os, sys, urllib.request
from collections import Counter
from datetime import date, datetime, timezone
import icalendar, recurring_ical_events, x_wr_timezone
KEEP_BUSY = {"UID", "DTSTART", "DTEND", "DURATION", "RRULE", "RDATE", "EXDATE",
"RECURRENCE-ID", "SEQUENCE", "STATUS", "TRANSP", "DTSTAMP"}
def load(src):
raw = (urllib.request.urlopen(src, timeout=30).read()
if src.startswith("https://") else open(src, "rb").read())
# Google-style files can carry X-WR-TIMEZONE; turn it into real TZIDs
# first, or the merged file loses it and some times move by an hour.
return x_wr_timezone.to_standard(icalendar.Calendar.from_ical(raw))
def key(ev):
rid = ev.get("RECURRENCE-ID")
rid = rid.dt.astimezone(timezone.utc).isoformat() if rid and isinstance(
rid.dt, datetime) and rid.dt.tzinfo else (rid.dt.isoformat() if rid else "")
return (str(ev.get("UID")), rid)
def rank(ev):
seq = int(ev.get("SEQUENCE", 0))
mod = ev.get("LAST-MODIFIED") or ev.get("DTSTAMP")
return (seq, mod.dt.astimezone(timezone.utc) if mod else datetime.min.replace(tzinfo=timezone.utc))
def busy_copy(ev):
out = icalendar.Event()
for name, value in ev.items():
if name in KEEP_BUSY:
out[name] = value
out["UID"] = hashlib.sha256(str(ev["UID"]).encode()).hexdigest()[:32] + "@merged"
out.add("SUMMARY", "Busy")
return out
def instances(cal, a, b):
return {(str(e.get("UID")), e["DTSTART"].dt.isoformat() if isinstance(e["DTSTART"].dt, date)
and not isinstance(e["DTSTART"].dt, datetime) else
e["DTSTART"].dt.astimezone(timezone.utc).isoformat())
for e in recurring_ical_events.of(cal).between(a, b)
if str(e.get("STATUS", "")).upper() != "CANCELLED"}
def main():
p = argparse.ArgumentParser(description="Merge .ics calendars into one file.")
p.add_argument("out")
p.add_argument("inputs", nargs="+", help="files or https:// links")
p.add_argument("--busy-only", action="store_true")
p.add_argument("--check-from", type=date.fromisoformat)
p.add_argument("--check-to", type=date.fromisoformat)
args = p.parse_args()
busy, a, b = args.busy_only, args.check_from, args.check_to
out_path, sources = args.out, args.inputs
cals = []
for s in sources: # one bad input stops the run; the old output stays as it was
try:
cals.append(load(s))
except Exception as e:
sys.exit(f"could not read {s}: {e}")
merged = icalendar.Calendar()
merged.add("PRODID", "-//merge_ics.py//EN")
merged.add("VERSION", "2.0")
merged.add("X-WR-CALNAME", "Merged (busy only)" if busy else "Merged")
zones, events, dupes, skipped = {}, {}, 0, 0
# busy-only: leave out whole series that are cancelled or marked free.
# A cancelled or free single instance stays, or the series would get it back.
drop = {str(ev.get("UID")) for cal in cals for ev in cal.walk("VEVENT")
if busy and "RECURRENCE-ID" not in ev
and (str(ev.get("STATUS", "")).upper() == "CANCELLED"
or str(ev.get("TRANSP", "")).upper() == "TRANSPARENT")}
for cal in cals:
for tz in cal.walk("VTIMEZONE"):
tzid = str(tz["TZID"])
if tzid in zones and zones[tzid].to_ical() != tz.to_ical():
print(f"warning: two different definitions of time zone {tzid}; kept the first")
zones.setdefault(tzid, tz)
for ev in cal.walk("VEVENT"):
if str(ev.get("UID")) in drop:
skipped += 1
continue
k = key(ev)
if k in events:
dupes += 1
if rank(ev) <= rank(events[k]):
continue
events[k] = ev
for tz in zones.values():
merged.add_component(tz)
for ev in events.values():
merged.add_component(busy_copy(ev) if busy else ev)
with open(out_path + ".tmp", "wb") as f:
f.write(merged.to_ical())
os.replace(out_path + ".tmp", out_path) # readers never see a half-written file
print(f"{len(sources)} inputs, {len(events)} events written, "
f"{dupes} duplicate entries merged, {skipped} free or cancelled left out")
if a and b: # check: the file on disk holds every kept occurrence at the same instant
kept = icalendar.Calendar()
for tz in zones.values():
kept.add_component(tz)
for ev in events.values():
kept.add_component(ev)
got = instances(icalendar.Calendar.from_ical(open(out_path, "rb").read()), a, b)
want = instances(kept, a, b)
if busy: # new UIDs in the output, so compare start times only
got, want = Counter(t for _, t in got), Counter(t for _, t in want)
else:
got, want = Counter(got), Counter(want)
missing, extra = want - got, got - want
print(f"check {a} to {b}: {sum(want.values())} expected, "
f"{sum(missing.values())} missing, {sum(extra.values())} extra")
for m in sorted(missing)[:10]:
print(" missing:", m)
if missing or extra:
sys.exit(1)
if __name__ == "__main__":
main()
What it does, in order:
- Loads each input from a file or an
https://link, and convertsX-WR-TIMEZONEinto proper zone references first. - Keeps one copy per meeting, keyed on
UIDplusRECURRENCE-ID; on a clash it keeps the higherSEQUENCE, then the laterLAST-MODIFIED. - Copies every time zone definition and warns if two files define the same zone name differently.
- With
--busy-only, keeps only the timing lines, titles every event “Busy”, replaces eachUIDwith a hash and leaves out whole series that are cancelled or marked free. - With
--check-fromand--check-to, expands every occurrence in that window and checks that the file on disk has each one at the same instant.
Step 1: get the two calendar links
Use the steps in methods 1 and 2: the ICS link from Publish a calendar in Outlook on the web, and the Secret address in iCal format from Google. For the Outlook link, choose the detail level that shows times, since the script removes the rest anyway.
Treat both links like passwords. Store them in an environment variable or a file outside any shared folder, not inside the script.
Step 2: run it once with the check on
python3 merge_ics.py busy.ics "$OUTLOOK_ICS" "$GOOGLE_ICS" --busy-only --check-from 2026-11-01 --check-to 2027-11-01
Read the two summary lines. On our sample files, with a 2026 window, the run printed:
2 inputs, 8 events written, 1 duplicate entries merged, 2 free or cancelled left out
check 2026-01-01 to 2027-01-01: 117 expected, 0 missing, 0 extra
Then open busy.ics in a text editor and search for a word from a real meeting title. It should not be there. A non-zero “missing” count makes the script exit with code 1; do not publish that file.
Step 3: put the file where a calendar app can fetch it
The output has to sit at a web address for Google’s From URL or Outlook’s Subscribe from web to read it. Use any web host you already have, at a long, random path, because anyone with the address can read the busy times.
Run the script on a schedule, for example hourly. It writes to a temporary file and swaps it in at the end, so a reader never fetches half a file, and a failed download leaves the last good file in place. Our guide to scheduled scripts that fail silently covers how to get an alert when a run stops.
The delays from the table still apply at the other end. Outlook fetches your merged feed on its own schedule of about 3 or 6 hours, and Google states none. The script removes the merge problems, not the polling delay.
Two things the script does not handle
Detail beyond busy. The script either keeps everything or keeps timing only. Recognizing “tentative” as busy, or keeping titles for one calendar but not the other, means editing KEEP_BUSY or running it twice.
Change detection by file bytes. If you extend it to act only when a feed changes, do not compare raw downloads. Our two downloads of Google’s holiday feed, 66 seconds apart, had different bytes. The only difference was DTSTAMP on all 317 entries, each set to the moment of download. That matches the standard: for a file with a METHOD line, DTSTAMP is “the date and time that the instance of the iCalendar object was created”. Compare the files with DTSTAMP lines removed.
Which method to pick
- You live in Outlook and need your Gmail calendar there, editable: add the Google account to new Outlook (method 3), if a copy in Microsoft’s cloud is acceptable.
- Your company is on Google Workspace and some staff insist on classic Outlook: GWSMO (method 4). It is the only route with a published 10-minute calendar schedule.
- You only need to see the other calendar, and a lag of hours is fine: a calendar link (method 1 or 2). It needs no admin and no extra app.
- Your company runs both Exchange and Google Workspace: ask the Google admin about Calendar Interop, so scheduling tools on each side see busy time on the other.
- Someone needs your busy times from both calendars, and nothing else: the script, with
--busy-only, behind a private link.
If a missed meeting costs real money, rule out the link methods first. Microsoft’s own pages say their updates “can take more than 24 hours”.
How we made this
On October 29, 2026, we read Microsoft’s support pages on subscribing, publishing, Gmail in new Outlook and Microsoft cloud sync, Google’s help pages on subscribing, the secret address, GWSMO and Calendar Interop, and RFC 5545 and RFC 7986. We wrote the merge script and ran it on two made-up calendar files with an answer key computed without calendar libraries, and on Google’s public US holidays feed, downloaded twice. We had no Microsoft or Google accounts to test with, so we did not measure any sync delay ourselves: every delay in this guide is the vendor’s own statement. We did not test paid third-party sync services.
Sources
- Microsoft Support, Import or subscribe to a calendar in Outlook.com or Outlook on the web, read October 29, 2026
- Microsoft Support, Share your calendar in Outlook on the web, read October 29, 2026
- Microsoft Support, See your Google Calendar in Outlook, read October 29, 2026
- Microsoft Support, Add a Gmail account to Outlook for Windows, Sync your account in Outlook to the Microsoft Cloud and Add an email account to Outlook, read October 29, 2026
- Microsoft Learn, New-SharingPolicy and Configure Internet Calendars in Outlook 2007, read October 29, 2026
- Google Calendar Help, Subscribe to someone else’s calendar and Sync your calendar with computer programs, read October 29, 2026
- Google Workspace Admin Help, About GWSMO, What’s synchronized?, System requirements, GWSMO FAQ and About Calendar Interop, read October 29, 2026
- IETF, RFC 5545: iCalendar, sections 3.6.5, 3.8.2.7, 3.8.4.4, 3.8.4.7, 3.8.7.2 and 3.8.7.4, and RFC 7986: New Properties for iCalendar, section 5.7, read October 29, 2026
- x-wr-timezone package description, version 2.0.1, read October 29, 2026
- 28creation test, October 29, 2026: two generated calendar files for 2026 (137 occurrences) scored against an answer key, and Google’s public US holidays feed downloaded twice