@bp.get("/<int:jamid>")
 def jam(jamid):
-    jam = get_jam_by_id(jamid)
+    jam = _get_jam_by_id(jamid)
     # Show the main jam page
     return render_template("jam.html", jam=jam)
 
 @bp.get("/<int:jamid>/events/<int:eventid>")
 def events_view(jamid, eventid):
     # Show the event page
-    jam = get_jam_by_id(jamid)
+    jam = _get_jam_by_id(jamid)
     try:
         event = next(e for e in jam.events if e.eventid == eventid)
     except StopIteration:
 @jam_owner_only
 def events_update(jamid, eventid):
     # Update an event with the new form data
-    ...
+    title = request.form["title"]
+    description = request.form["description"]
+    startdate = request.form["startdate"]
+    enddate = request.form["enddate"]
+    _validate_timestamp(startdate)
+    _validate_timestamp(enddate)
+    db.query(
+            """
+            UPDATE jam_events
+            SET title = ?, description = ?, startdate = ?, enddate = ?
+            WHERE eventid = ? AND jamid = ?
+            RETURNING *
+            """,
+            [title, description, startdate, enddate, eventid, jamid],
+            expect_one=True)
+    db.commit()
+    return redirect(url_for("jams.events_view", jamid=jamid, eventid=eventid))
 
 
 @bp.get("/<int:jamid>/events/<int:eventid>/delete")
 @jam_owner_only
 def events_delete(jamid, eventid):
     # Delete an event, redirect to list of all events
-    ...
+    db.query(
+            """
+            DELETE FROM jam_events
+            WHERE eventid = ? AND jamid = ?
+            RETURNING *
+            """, [eventid, jamid], expect_one=True)
+    return redirect(url_for("jams.jam", jamid=jamid))
+
 
-def get_jam_by_id(jamid):
+def _get_jam_by_id(jamid):
     row = db.query(
             """
             SELECT * FROM jams
             """, [jamid], expect_one=True)
     return Jam.from_row(row)
 
+
+def _validate_timestamp(timestamp):
+    try:
+        datetime.fromisoformat(timestamp)
+    except ValueError:
+        abort(400)
+
 @dataclass
 class Jam:
     jamid: int
                 threadid=row["threadid"],
                 created=datetime.fromisoformat(row["created"]),
                 title=row["title"],
-                startdate=datetime.fromisoformat(row["startdate"]) if "startdate" in row else None,
-                enddate=datetime.fromisoformat(row["enddate"]) if "enddate" in row else None,
+                startdate=datetime.fromisoformat(row["startdate"]) if row["startdate"] else None,
+                enddate=datetime.fromisoformat(row["enddate"]) if row["enddate"] else None,
                 description=sanitize_user_text(row["description"] or ""),
                 jam_title=row["jam_title"],
                 jam_ownername=row["jam_ownername"],
 
+from datetime import datetime, timezone
+
 import pytest
 
 from .utils import create_user
     yield "user"
 
 @pytest.fixture
-def jam(client):
+def jam(client, user):
     client.get("/jams/create")
     return 1
 
+@pytest.fixture
+def event(client, jam):
+    client.get(f"/jams/{jam}/events/create")
+    return 1
+
 # Jams #########################################################################
 
 def test_view_invalid_jam(client):
     response = client.get(f"/jams/{jam}/events/create", follow_redirects=True)
     assert response.status_code == 403
 
-# Update event
-# Update event invalid event
-# Update event not logged in
-# Update event other users jam
+def _to_utc(timestamp):
+    return (datetime
+            .fromisoformat(timestamp)
+            .astimezone(timezone.utc)
+            .isoformat())
+
+def _get_event_data(**kwargs):
+    event_data = {
+            "title": "Event Title",
+            "description": "description of the event",
+            "startdate": _to_utc("2040-01-01T00:00:00"),
+            "enddate": _to_utc("2040-01-02T00:00:00"),
+    }
+    for k, v in kwargs.items():
+        event_data[k] = v
+    return event_data
+
+def test_update_event(client, user, jam, event):
+    response = client.post(
+            f"/jams/{jam}/events/{event}/update",
+            data=_get_event_data(), follow_redirects=True)
+    assert response.request.path == f"/jams/{jam}/events/{event}"
+    assert b"Event Title" in response.data
+    assert b"description of the event" in response.data
+    assert b"2040-01-01" in response.data
+    assert b"2040-01-02" in response.data
+
+def test_update_event_invalid_eventid(client, user, jam):
+    response = client.post(f"/jams/{jam}/events/1/update", data=_get_event_data())
+    assert response.status_code == 404
+
+def test_update_event_invalid_jamid(client, user, event):
+    response = client.post(f"/jams/2/events/{event}/update", data=_get_event_data())
+    assert response.status_code == 404
+
+def test_update_event_not_logged_in(client, user, jam, event):
+    response = client.get("/logout")
+    response = client.post(
+            f"/jams/{jam}/events/{event}/update",
+            data=_get_event_data(),
+            follow_redirects=True)
+    assert response.request.path == "/login"
+
+def test_update_event_other_users_jam(client, user, jam, event):
+    create_user(client, "otheruser", login=True)
+    response = client.post(
+            f"/jams/{jam}/events/{event}/update",
+            data=_get_event_data(),
+            follow_redirects=True)
+    assert response.status_code == 403
+
+def test_update_event_invalid_startdate(client, user, jam, event):
+    response = client.post(
+            f"/jams/{jam}/events/{event}/update",
+            data=_get_event_data(startdate="notadate"),
+            follow_redirects=True)
+    assert response.status_code == 400
+
+def test_update_event_invalid_enddate(client, user, jam, event):
+    response = client.post(
+            f"/jams/{jam}/events/{event}/update",
+            data=_get_event_data(enddate="notadate"),
+            follow_redirects=True)
+    assert response.status_code == 400
+
+def test_delete_event(client, user, jam, event):
+    response = client.get(f"/jams/{jam}/events/{event}/delete", follow_redirects=True)
+    assert response.request.path == f"/jams/{jam}"
+    assert b"Event Title" not in response.data
 
-# Delete event
-# Delete event not logged in
-# Delete event invalid event
-# Delete event other users jam
+def test_delete_event_not_logged_in(client, user, jam, event):
+    client.get("/logout")
+    response = client.get(f"/jams/{jam}/events/{event}/delete", follow_redirects=True)
+    assert response.request.path == "/login"
+
+def test_delete_event_invalid_jamid(client, user, jam, event):
+    response = client.get(f"/jams/2/events/{event}/delete", follow_redirects=True)
+    assert response.status_code == 404
+
+def test_delete_event_invalid_eventid(client, user, jam, event):
+    response = client.get(f"/jams/{jam}/events/2/delete", follow_redirects=True)
+    assert response.status_code == 404
+
+def test_delete_event_other_users_jam(client, user, jam, event):
+    create_user(client, "otheruser", login=True)
+    response = client.get(f"/jams/{jam}/events/{event}/delete", follow_redirects=True)
+    assert response.status_code == 403