diff --git a/core/lib/Drupal/Component/Utility/Unicode.php b/core/lib/Drupal/Component/Utility/Unicode.php
index c5e0d82..58f9464 100644
--- a/core/lib/Drupal/Component/Utility/Unicode.php
+++ b/core/lib/Drupal/Component/Utility/Unicode.php
@@ -258,7 +258,7 @@ public static function strlen($text) {
   }
 
   /**
-   * Uppercase a UTF-8 string.
+   * Uppercases a UTF-8 string.
    *
    * @param string $text
    *   The string to run the operation on.
@@ -280,7 +280,7 @@ public static function strtoupper($text) {
   }
 
   /**
-   * Lowercase a UTF-8 string.
+   * Lowercases a UTF-8 string.
    *
    * @param string $text
    *   The string to run the operation on.
@@ -315,6 +315,40 @@ public static function ucfirst($text) {
   }
 
   /**
+   * Lowercases the first letter of a UTF-8 string.
+   *
+   * @param string $text
+   *   The string that will be converted.
+   *
+   * @return string
+   *   The string with the first letter as lowercase.
+   *
+   * @ingroup php_wrappers
+   */
+  public static function lcfirst($text) {
+    // Note: no mbstring equivalent!
+    return static::strtolower(static::substr($text, 0, 1)) . static::substr($text, 1);
+  }
+
+  /**
+   * Capitalizes the first letter of each word in a UTF-8 string.
+   *
+   * @param string $text
+   *   The text that will be converted.
+   *
+   * @return string
+   *   The input $text with each word capitalized.
+   *
+   * @ingroup php_wrappers
+   */
+  public static function ucwords($text) {
+    $regex = '/(^|[' . static::PREG_CLASS_WORD_BOUNDARY . '])([^' . static::PREG_CLASS_WORD_BOUNDARY . '])/u';
+    return preg_replace_callback($regex, function(array $matches) {
+      return $matches[1] . Unicode::strtoupper($matches[2]);
+    }, $text);
+  }
+
+  /**
    * Cuts off a piece of a string based on character indices and counts.
    *
    * Follows the same behavior as PHP's own substr() function. Note that for
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
index e319ab1..a2cfac6 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/HandlerBase.php
@@ -263,14 +263,9 @@ protected function caseTransform($string, $option) {
       case 'lower':
         return drupal_strtolower($string);
       case 'ucfirst':
-        return drupal_strtoupper(drupal_substr($string, 0, 1)) . drupal_substr($string, 1);
+        return Unicode::ucfirst($string);
       case 'ucwords':
-        if (Unicode::getStatus() == Unicode::STATUS_MULTIBYTE) {
-          return mb_convert_case($string, MB_CASE_TITLE);
-        }
-        else {
-          return ucwords($string);
-        }
+        return Unicode::ucwords($string);
     }
   }
 
diff --git a/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php b/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php
index ee45829..350fa25 100644
--- a/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/UnicodeTest.php
@@ -194,6 +194,70 @@ public function providerUcfirst() {
   }
 
   /**
+   * Tests Unicode::lcfirst().
+   *
+   * @dataProvider providerLcfirst
+   */
+  public function testLcfirst($text, $expected, $multibyte = FALSE) {
+    $status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
+    Unicode::setStatus($status);
+    $this->assertEquals($expected, Unicode::lcfirst($text));
+  }
+
+  /**
+   * Data provider for testLcfirst().
+   *
+   * @see testLcfirst()
+   *
+   * @return array
+   *   An array containing a string, its lowercase version and whether it should
+   *   be processed as multibyte.
+   */
+  public function providerLcfirst() {
+    return array(
+      array('tHe QUIcK bRoWn', 'tHe QUIcK bRoWn'),
+      array('FrançAIS is ÜBER-åwesome', 'françAIS is ÜBER-åwesome'),
+      array('Über', 'über'),
+      array('Åwesome', 'åwesome'),
+      // Add a multibyte string.
+      array('ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', 'αΒΓΔΕΖΗΘΙΚΛΜΝΞΟΣὨ', TRUE),
+    );
+  }
+
+  /**
+   * Tests Unicode::ucwords().
+   *
+   * @dataProvider providerUcwords
+   */
+  public function testUcwords($text, $expected, $multibyte = FALSE) {
+    $status = $multibyte ? Unicode::STATUS_MULTIBYTE : Unicode::STATUS_SINGLEBYTE;
+    Unicode::setStatus($status);
+    $this->assertEquals($expected, Unicode::ucwords($text));
+  }
+
+  /**
+   * Data provider for testUcwords().
+   *
+   * @see testUcwords()
+   *
+   * @return array
+   *   An array containing a string, its capitalized version and whether it should
+   *   be processed as multibyte.
+   */
+  public function providerUcwords() {
+    return array(
+      array('tHe QUIcK bRoWn', 'THe QUIcK BRoWn'),
+      array('françAIS', 'FrançAIS'),
+      array('über', 'Über'),
+      array('åwesome', 'Åwesome'),
+      // Make sure we don't mangle extra spaces.
+      array('frànçAIS is  über-åwesome', 'FrànçAIS Is  Über-Åwesome'),
+      // Add a multibyte string.
+      array('σion', 'Σion', TRUE),
+    );
+  }
+
+  /**
    * Tests Unicode::strlen().
    *
    * @dataProvider providerStrlen
