Skip to content

Instantly share code, notes, and snippets.

@milnak
Created November 9, 2022 07:06
Show Gist options
  • Save milnak/16a38e2b5a1c10f4bcc6faa72af4e949 to your computer and use it in GitHub Desktop.
Save milnak/16a38e2b5a1c10f4bcc6faa72af4e949 to your computer and use it in GitHub Desktop.
Convert an online ICAL to a text representation
# Powershell: No Rules calendar (ICAL import)
[cmdletbinding()] # Support -Verbose
Param(
# Google Calendar URL.
# Calendar Settings > Calendar Details > Calendar Address > ICAL
# Settings for my calendars > [Calendar Name] > Integrate calendar > Public address in iCal format
[string]$ICalUrl = 'https://calendar.google.com/calendar/ical/FILL_THIS_IN/public/basic.ics',
# If False, then only events after the current day are displayed.
[switch]$AllEvents = $False,
# If True, then use test data
[switch]$UseTestData = $False
)
function Convert-Date {
Param([string]$date)
# e.g. '20170913T210838Z'
if ($date -match 'Z') {
[DateTime]::ParseExact($date, 'yyyyMMddTHHmmssZ', [Globalization.CultureInfo]::InvariantCulture)
}
# e.g. '20181025T000000'
elseif ($date -match 'T') {
[DateTime]::ParseExact($date, 'yyyyMMddTHHmmss', [Globalization.CultureInfo]::InvariantCulture)
}
# e.g. '20160729'
else {
[DateTime]::ParseExact($date, 'yyyyMMdd', [Globalization.CultureInfo]::InvariantCulture)
}
}
if (!$UseTestData) {
$prevProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
$ical = (Invoke-WebRequest $icalUrl | Select-Object -ExpandProperty Content) # -split "`r`n"
$ProgressPreference = $prevProgressPreference
} else {
$ical = @'
BEGIN:VCALENDAR
PRODID:-//Google Inc//Google Calendar 70.9054//EN
VERSION:2.0
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:No Rules
X-WR-TIMEZONE:America/Vancouver
X-WR-CALDESC:No Rules rehearsal and gig calendar
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
X-LIC-LOCATION:America/Los_Angeles
BEGIN:DAYLIGHT
TZOFFSETFROM:-0800
TZOFFSETTO:-0700
TZNAME:PDT
DTSTART:19700308T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:-0700
TZOFFSETTO:-0800
TZNAME:PST
DTSTART:19701101T020000
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
END:STANDARD
END:VTIMEZONE
BEGIN:VEVENT
DTSTART;TZID=America/Vancouver:20181024T000000
DTEND;TZID=America/Vancouver:20181025T000000
DTSTAMP:20221109T054416Z
UID:5uoh4l4m15ppe22h4jn3l63ouj@google.com
RECURRENCE-ID;VALUE=DATE:20181024
CLASS:PUBLIC
CREATED:20180122T220041Z
DESCRIPTION:
LAST-MODIFIED:20200119T230337Z
LOCATION:
SEQUENCE:2
STATUS:CONFIRMED
SUMMARY:Rehearsal - 6PM\, Jim Back\, no horns
TRANSP:OPAQUE
X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
X-MICROSOFT-DISALLOW-COUNTER:FALSE
END:VEVENT
BEGIN:VEVENT
DTSTART:20220424T210000Z
DTEND:20220425T010000Z
DTSTAMP:20221109T064633Z
UID:cko6ad1j74rj8b9i60q38b9k6cp6abb261ijib9g6so36cr365gjior270@google.com
CREATED:20211204T191955Z
DESCRIPTION:<html-blob><u></u><p><b>Sun</b><br></p><p>load-in&nbsp\; &nbsp\ ; 11:00</p></html-blob>
LAST-MODIFIED:20220421T185035Z
LOCATION:1383 Court E\, Tacoma\, WA 98402\, USA
SEQUENCE:1
STATUS:CONFIRMED
SUMMARY:Alma Mater Rooftop Gig
TRANSP:TRANSPARENT
END:VEVENT
END:VCALENDAR
'@
}
$events = @()
$curevent = $null
foreach ($line in $ical -split "`r`n") {
# Write-Verbose "line = $line"
switch -Wildcard ($line) {
'BEGIN:*' {
# BEGIN:VEVENT
$value = $line.SubString(6)
if ($value -eq 'VEVENT' ) {
$curevent = New-Object PSObject
}
# NOTE: Ignoring BEGIN:VTIMEZONE
break
}
'END:*' {
# END:VEVENT
$value = $line.SubString(4)
if ($value -eq 'VEVENT') {
$events += $curevent
$curevent = $null
}
# NOTE: Ignoring END:VTIMEZONE
break
}
'UID:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'UID' -Value $line.SubString(4)
}
break
}
# e.g. SUMMARY:Rehearsal - 6PM\, Jim Back\, no horns
'SUMMARY:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'SUMMARY' -Value ($line.SubString(8) -replace '\\')
}
break
}
# e.g. STATUS:CONFIRMED
'STATUS:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'STATUS' -Value $line.SubString(7)
}
break
}
# e.g. DESCRIPTION:<html-blob>...
'DESCRIPTION:*' {
if ($curevent) {
$value = $line.SubString(12) -replace '\\'
if (![string]::IsNullOrEmpty($value)) {
$curevent | Add-Member -Type NoteProperty -Name 'DESCRIPTION' -Value $value
}
}
break
}
# e.g. LOCATION:1383 Court E\, Tacoma\, WA 98402\, USA'
'LOCATION:*' {
if ($curevent) {
$value = $line.SubString(9) -replace '\\'
if (![string]::IsNullOrEmpty($value)) {
$curevent | Add-Member -Type NoteProperty -Name 'LOCATION' -Value $value
}
}
break
}
# e.g. RECURRENCE-ID;VALUE=DATE:20181024
'RECURRENCE-ID;VALUE=DATE:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'RECURRENCE-ID' -Value (Convert-Date $line.SubString(25))
}
break
}
'CREATED:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'CREATED' -Value (Convert-Date $line.SubString(8))
}
break
}
'DTSTAMP:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'DTSTAMP' -Value (Convert-Date $line.SubString(8))
}
break
}
# e.g. DTSTART;TZID=America/Vancouver:20181024T000000
'DTSTART;*' {
if ($curevent) {
$line = $line.SubString(8)
$curevent | Add-Member -Type NoteProperty -Name 'DTSTART' -Value (Convert-Date $line.SubString($line.IndexOf(':') + 1))
}
break
}
# e.g. DTSTART:20220424T210000Z
'DTSTART:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'DTSTART' -Value (Convert-Date $line.SubString(8))
}
break
}
# e.g. DTEND;TZID=America/Vancouver:20181025T000000
'DTEND;*' {
if ($curevent) {
$line = $line.SubString(6)
$curevent | Add-Member -Type NoteProperty -Name 'DTEND' -Value (Convert-Date $line.SubString($line.IndexOf(':') + 1))
}
break
}
# e.g. DTEND:20220425T010000Z
'DTEND:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'DTEND' -Value (Convert-Date $line.SubString(6))
}
break
}
'LAST-MODIFIED:*' {
if ($curevent) {
$curevent | Add-Member -Type NoteProperty -Name 'LAST-MODIFIED' -Value (Convert-Date $line.SubString(14))
}
break
}
# e.g. SEQUENCE:0
'SEQUENCE:*' { }
# e.g. 'TRANSP:OPAQUE'
'TRANSP:*' { }
# e.g.CLASS:PUBLIC
'CLASS:*' { }
# e.g. X-MICROSOFT-CDO-ALLDAYEVENT:FALSE
'X-MICROSOFT-CDO-ALLDAYEVENT:*' { }
# e.g. X-MICROSOFT-DISALLOW-COUNTER:FALSE
'X-MICROSOFT-DISALLOW-COUNTER:*' { }
# e.g. EXDATE;VALUE=DATE:20190724
'EXDATE;*' { }
# e.g. RRULE:FREQ=WEEKLY;UNTIL=20200121;INTERVAL=1;BYDAY=WE
'RRULE:*' { }
default {
# Skip anything outside of a VEVENT block.
if ($curevent) {
Write-Verbose "Ignoring $line"
# $curevent | Add-Member -Type NoteProperty -Name $kv[0] -Value $kv[1]
}
}
}
}
$events = $events | Sort-Object DTSTART
# $events; return
if (!$AllEvents) {
$events = $events | Where-Object DTSTART -ge (Get-Date)
}
$events | ForEach-Object {
$fmt = 'MMM dd yy (ddd)'
if (!($_.DTSTART.Hour -eq 0 -and $_.DTSTART.Minute -eq 0 -and $_.DTSTART.Second -eq 0)) {
$fmt += ' h:mmtt'
}
Write-Verbose ("DTSTART = '{0}', SUMMARY = '{1}" -f $_.DTSTART, $_.SUMMARY)
"$($_.DTSTART.ToString($fmt)) - $($_.SUMMARY)"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment