diff --git a/core/includes/errors.inc b/core/includes/errors.inc
index 215b6cb..1757acd 100644
--- a/core/includes/errors.inc
+++ b/core/includes/errors.inc
@@ -65,6 +65,11 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
     $caller = Error::getLastCaller($backtrace);
 
     // We treat recoverable errors as fatal.
+    $recoverable = $error_level == E_RECOVERABLE_ERROR;
+    // As __toString() methods must not throw exceptions (recoverable errors)
+    // in PHP, we allow them to trigger a fatal error by emitting a user error
+    // using trigger_error().
+    $to_string = $error_level == E_USER_ERROR && substr($caller['function'], -strlen('__toString()')) == '__toString()';
     _drupal_log_error(array(
       '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error',
       // The standard PHP error handler considers that the error messages
@@ -75,7 +80,7 @@ function _drupal_error_handler_real($error_level, $message, $filename, $line, $c
       '%line' => $caller['line'],
       'severity_level' => $severity_level,
       'backtrace' => $backtrace,
-    ), $error_level == E_RECOVERABLE_ERROR);
+    ), $recoverable || $to_string);
   }
 }
 
diff --git a/core/lib/Drupal/Component/Datetime/DateTimePlus.php b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
index c3edf99..f6c16ca 100644
--- a/core/lib/Drupal/Component/Datetime/DateTimePlus.php
+++ b/core/lib/Drupal/Component/Datetime/DateTimePlus.php
@@ -280,7 +280,17 @@ public function __construct($time = 'now', $timezone = NULL, $settings = array()
    */
   public function __toString() {
     $format = static::FORMAT;
-    return $this->format($format) . ' ' . $this->getTimeZone()->getName();
+    try {
+      return $this->format($format) . ' ' . $this->getTimeZone()->getName();
+    }
+    catch (\Exception $e) {
+      // User errors in __toString() methods are considered fatal in the Drupal
+      // error handler.
+      trigger_error(get_class($e) . ' thrown while calling __toString on a ' . get_class($this) . ' object in ' . $e->getFile() . ' on line ' . $e->getLine() . ': ' . $e->getMessage(), E_USER_ERROR);
+      // In case we are using another error handler that did not fatal on the
+      // E_USER_ERROR, we terminate execution.
+      die();
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
index dffd2cf..19a7c2c 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -98,7 +98,18 @@ public function getOptions() {
    * Implements the magic __toString() method.
    */
   public function __toString() {
-    return $this->render();
+    try {
+      throw new \Exception('hi');
+      return (string) $this->render();
+    }
+    catch (\Exception $e) {
+      // User errors in __toString() methods are considered fatal in the Drupal
+      // error handler.
+      trigger_error(get_class($e) . ' thrown while calling __toString on a ' . get_class($this) . ' object in ' . $e->getFile() . ' on line ' . $e->getLine() . ': ' . $e->getMessage(), E_USER_ERROR);
+      // In case we are using another error handler that did not fatal on the
+      // E_USER_ERROR, we terminate execution.
+      die();
+    }
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
new file mode 100644
index 0000000..27b8497
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\StringTranslation\TranslationWrapperTest.
+ */
+
+namespace Drupal\Tests\Core\StringTranslation;
+
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Drupal\Tests\UnitTestCase;
+use Drupal\Core\StringTranslation\TranslationWrapper;
+
+/**
+ * Tests the TranslationWrapper class.
+ *
+ * @coversDefaultClass \Drupal\Core\StringTranslation\TranslationWrapper
+ * @group StringTranslation
+ */
+class TranslationWrapperTest extends UnitTestCase {
+
+  /**
+   * The error message of the last error in the error handler.
+   *
+   * @var string
+   */
+  protected $lastErrorMessage;
+
+  /**
+   * The error number of the last error in the error handler.
+   *
+   * @var int
+   */
+  protected $lastErrorNumber;
+
+  /**
+   * Custom error handler that saves the last error.
+   *
+   * We need this custom error handler because we cannot rely on the error to
+   * exception conversion as __toString is never allowed to leak any kind of
+   * exception.
+   *
+   * @param int $error_number
+   *   The error number.
+   * @param string $error_message
+   *   The error message.
+   */
+  public function errorHandler($error_number, $error_message) {
+    $this->lastErrorNumber = $error_number;
+    $this->lastErrorMessage = $error_message;
+  }
+
+  /**
+   * Tests that errors are correctly handled when a __toString() fails.
+   *
+   * @covers ::__toString
+   */
+  public function testToString() {
+    $string = 'May I have an exception please?';
+    $text = new TranslationWrapper($string);
+
+    $translation = $this->prophesize(TranslationInterface::class);
+    $translation->translate($string, [], [])->will(function () {
+      throw new \Exception('Yes you may.');
+    });
+    $text->setStringTranslation($translation->reveal());
+
+    // We set a custom error handler because of https://github.com/sebastianbergmann/phpunit/issues/487
+    set_error_handler([$this, 'errorHandler']);
+    // We want this to trigger an error.
+    (string) $text;
+    restore_error_handler();
+
+    $this->assertEquals(E_USER_ERROR, $this->lastErrorNumber);
+    $this->assertRegExp('/An exception was thrown while calling __toString on a .*\TranslationWrapper object in .* on line [0-9]+: Yes you may./', $this->lastErrorMessage);
+  }
+
+}
