phacility/phabricator · error · Exception

Unable to write ICS document: event has no modified time, bu

Error message

Unable to write ICS document: event has no modified time, but each event MUST have a modified time.

What it means

While writing an ICS document, PhutilICSWriter requires every event to have a modified datetime because it must emit a DTSTAMP property (RFC5545 marks DTSTAMP as REQUIRED on VEVENT). If getModifiedDateTime() returns null the writer aborts rather than produce a document no client will accept.

Source

Thrown at src/applications/calendar/parser/ics/PhutilICSWriter.php:163

      throw new Exception(
        pht(
          'Unable to write ICS document: event has no UID, but each event '.
          'MUST have a UID.'));
    }
    $properties[] = $this->newTextProperty(
      'UID',
      $uid);

    $created = $event->getCreatedDateTime();
    if ($created) {
      $properties[] = $this->newDateTimeProperty(
        'CREATED',
        $event->getCreatedDateTime());
    }

    $dtstamp = $event->getModifiedDateTime();
    if (!$dtstamp) {
      throw new Exception(
        pht(
          'Unable to write ICS document: event has no modified time, but '.
          'each event MUST have a modified time.'));
    }
    $properties[] = $this->newDateTimeProperty(
      'DTSTAMP',
      $dtstamp);

    $dtstart = $event->getStartDateTime();
    if ($dtstart) {
      $properties[] = $this->newDateTimeProperty(
        'DTSTART',
        $dtstart);
    }

    $dtend = $event->getEndDateTime();
    if ($dtend) {
      $properties[] = $this->newDateTimeProperty(

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Set a modified datetime before export: $event->setModifiedDateTime(PhutilCalendarAbsoluteDateTime::newFromEpoch(time()))
  2. If the source has a real modification date, map it into the node instead of defaulting silently
  3. Centralize the UID+DTSTAMP checks in one pre-export validator

Example fix

// before
$ics = PhutilICSWriter::writeDocuments(array($event));

// after
if (!$event->getModifiedDateTime()) {
  $event->setModifiedDateTime(
    PhutilCalendarAbsoluteDateTime::newFromEpoch(time()));
}
$ics = PhutilICSWriter::writeDocuments(array($event));
Defensive patterns

Strategy: validation

Validate before calling

foreach ($events as $event) {
  if (!$event->getModifiedDateTime()) {
    $event->setModifiedDateTime(
      PhutilCalendarAbsoluteDateTime::newFromEpoch(time()));
  }
}
$ics = PhutilICSWriter::writeDocuments($events);

Try / catch

try {
  $ics = PhutilICSWriter::writeDocuments(array($event));
} catch (Exception $ex) {
  if (strpos($ex->getMessage(), 'modified time') !== false) {
    $event->setModifiedDateTime(
      PhutilCalendarAbsoluteDateTime::newFromEpoch(time()));
    $ics = PhutilICSWriter::writeDocuments(array($event));
  } else {
    throw $ex;
  }
}

Prevention

When it happens

Trigger: Writing an event node whose setModifiedDateTime() was never called, or whose imported source dictionary lacks the modified/stamp field — commonly right after error 228 was fixed and UID got set but the timestamp was overlooked.

Common situations: Import pipelines that fill created/start/end but not modified; exporting test fixture events; migrating events from legacy storage where the modified column is NULL.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/b66ecb5a52164bea. Report an issue: GitHub.