diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
index e7e5a4117..f472f8b03 100644
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -502,6 +502,36 @@ def ics_esc(text):
     text = re.sub(r"([\n,;\\])", r"\\\1", text)
     return text
 
+
+@register.simple_tag
+def ics_date_time(dt, tzname):
+    """Render a datetime as an iCalendar date-time
+
+    dt a datetime, localized to the timezone to be displayed
+    tzname is the name for this timezone
+
+    Caller must arrange for a VTIMEZONE for the tzname to be included in the iCalendar file.
+    Output includes a ':'. Use like:
+      DTSTART{% ics_date_time timestamp 'America/Los_Angeles' %}
+    to get
+      DTSTART;TZID=America/Los_Angeles:20221021T111200
+
+    >>> ics_date_time(datetime.datetime(2022,1,2,3,4,5), 'utc')
+    ':20220102T030405Z'
+
+    >>> ics_date_time(datetime.datetime(2022,1,2,3,4,5), 'UTC')
+    ':20220102T030405Z'
+
+    >>> ics_date_time(datetime.datetime(2022,1,2,3,4,5), 'America/Los_Angeles')
+    ';TZID=America/Los_Angeles:20220102T030405'
+    """
+    timestamp = dt.strftime('%Y%m%dT%H%M%S')
+    if tzname.lower() == 'utc':
+        return f':{timestamp}Z'
+    else:
+        return f';TZID={ics_esc(tzname)}:{timestamp}'
+
+
 @register.filter
 def consensus(doc):
     """Returns document consensus Yes/No/Unknown."""
diff --git a/ietf/templates/meeting/agenda.ics b/ietf/templates/meeting/agenda.ics
index 778905147..b7a8d3031 100644
--- a/ietf/templates/meeting/agenda.ics
+++ b/ietf/templates/meeting/agenda.ics
@@ -8,9 +8,9 @@ SUMMARY:{% if item.session.name %}{{item.session.name|ics_esc}}{% else %}{% if n
 {% if item.timeslot.show_location %}LOCATION:{{item.timeslot.get_location}}
 {% endif %}STATUS:{{item.session.ical_status}}
 CLASS:PUBLIC
-DTSTART;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
-DTEND;TZID={{schedule.meeting.time_zone|ics_esc}}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
-DTSTAMP:{{ item.timeslot.modified|date:"Ymd" }}T{{ item.timeslot.modified|date:"His" }}Z{% if item.session.agenda %}
+DTSTART{% ics_date_time item.timeslot.local_start_time schedule.meeting.time_zone %}
+DTEND{% ics_date_time item.timeslot.local_end_time schedule.meeting.time_zone %}
+DTSTAMP:{% ics_date_time item.timeslot.modified|utc 'utc' %}{% if item.session.agenda %}
 URL:{{item.session.agenda.get_versionless_href}}{% endif %}
 DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
  Note: {{item.session.agenda_note|ics_esc}}\n{% endif %}{% if item.timeslot.location.webex_url %}
diff --git a/ietf/templates/meeting/important_dates_for_meeting.ics b/ietf/templates/meeting/important_dates_for_meeting.ics
index bc505e687..181bfe15e 100644
--- a/ietf/templates/meeting/important_dates_for_meeting.ics
+++ b/ietf/templates/meeting/important_dates_for_meeting.ics
@@ -1,9 +1,9 @@
-{% load tz %}{% for d in meeting.important_dates %}BEGIN:VEVENT
+{% load tz ietf_filters %}{% for d in meeting.important_dates %}BEGIN:VEVENT
 UID:ietf-{{ meeting.number }}-{{ d.name_id }}-{{ d.date.isoformat }}
 SUMMARY:IETF {{ meeting.number }}: {{ d.name.name }}
 CLASS:PUBLIC
 DTSTART{% if not d.midnight_cutoff %};VALUE=DATE{% endif %}:{{ d.date|date:"Ymd" }}{% if d.midnight_cutoff %}235900Z{% endif %}
-DTSTAMP:{{ meeting.cached_updated|utc|date:"Ymd" }}T{{ meeting.cached_updated|utc|date:"His" }}Z
+DTSTAMP{% ics_date_time meeting.cached_updated|utc 'utc' %}
 TRANSP:TRANSPARENT
 DESCRIPTION:{{ d.name.desc }}{% if first and d.name.slug == 'openreg' or first and d.name.slug == 'earlybird' %}\n
  Register here: https://www.ietf.org/how/meetings/register/{% endif %}{% if d.name.slug == 'opensched' %}\n
diff --git a/ietf/templates/meeting/upcoming.ics b/ietf/templates/meeting/upcoming.ics
index 0f2e68b27..a448aeb59 100644
--- a/ietf/templates/meeting/upcoming.ics
+++ b/ietf/templates/meeting/upcoming.ics
@@ -1,4 +1,4 @@
-{% load humanize %}{% autoescape off %}{% load ietf_filters %}BEGIN:VCALENDAR
+{% load humanize tz %}{% autoescape off %}{% load ietf_filters %}BEGIN:VCALENDAR
 VERSION:2.0
 METHOD:PUBLISH
 PRODID:-//IETF//datatracker.ietf.org ical upcoming//EN
@@ -8,9 +8,9 @@ SUMMARY:{% if item.session.name %}{{item.session.group.acronym|lower}} - {{item.
 {% if item.schedule.meeting.city %}LOCATION:{{item.schedule.meeting.city}},{{item.schedule.meeting.country}}
 {% endif %}STATUS:{{item.session.ical_status}}
 CLASS:PUBLIC
-DTSTART{% if item.schedule.meeting.time_zone %};TZID={{ item.schedule.meeting.time_zone|ics_esc }}{%endif%}:{{ item.timeslot.time|date:"Ymd" }}T{{item.timeslot.time|date:"Hi"}}00
-DTEND{% if item.schedule.meeting.time_zone %};TZID={{ item.schedule.meeting.time_zone|ics_esc }}{%endif%}:{{ item.timeslot.end_time|date:"Ymd" }}T{{item.timeslot.end_time|date:"Hi"}}00
-DTSTAMP:{{ item.timeslot.modified|date:"Ymd" }}T{{ item.timeslot.modified|date:"His" }}Z
+DTSTART{% ics_date_time item.timeslot.local_start_time item.schedule.meeting.time_zone %}
+DTEND{% ics_date_time item.timeslot.local_end_time item.schedule.meeting.time_zone %}
+DTSTAMP:{% ics_date_time item.timeslot.modified|utc 'utc' %}
 {% if item.session.agenda %}URL:{{item.session.agenda.get_href}}
 DESCRIPTION:{{item.timeslot.name|ics_esc}}\n{% if item.session.agenda_note %}
  Note: {{item.session.agenda_note|ics_esc}}\n{% endif %}{% for material in item.session.materials.all %}
@@ -25,7 +25,7 @@ LOCATION:{{ meeting.city }},{{ meeting.country }}{% endif %}
 CLASS:PUBLIC
 DTSTART;VALUE=DATE{% if meeting.time_zone %};TZID={{ meeting.time_zone|ics_esc }}{% endif %}:{{ meeting.date|date:"Ymd" }}
 DTEND;VALUE=DATE{% if meeting.time_zone %};TZID={{ meeting.time_zone|ics_esc }}{% endif %}:{{ meeting.end_date|date:"Ymd" }}
-DTSTAMP:{{ meeting.cached_updated|date:"Ymd" }}T{{ meeting.cached_updated|date:"His" }}Z
+DTSTAMP{% ics_date_time meeting.cached_updated|utc 'utc' %}
 URL:{{ request.scheme }}://{{ request.get_host }}{% url 'agenda' num=meeting.number %}
 END:VEVENT
 {% endfor %}END:VCALENDAR{% endautoescape %}