Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 36118)
+++ src/wp-includes/functions.php	(working copy)
@@ -113,6 +113,7 @@
 		$datemonth_abbrev = $wp_locale->get_month_abbrev( $datemonth );
 		$dateweekday = $wp_locale->get_weekday( $datefunc( 'w', $i ) );
 		$dateweekday_abbrev = $wp_locale->get_weekday_abbrev( $dateweekday );
+		$dateordinal_suffix = get_ordinal_suffix( $datefunc( 'j', $i ) );
 		$datemeridiem = $wp_locale->get_meridiem( $datefunc( 'a', $i ) );
 		$datemeridiem_capital = $wp_locale->get_meridiem( $datefunc( 'A', $i ) );
 		$dateformatstring = ' '.$dateformatstring;
@@ -120,6 +121,7 @@
 		$dateformatstring = preg_replace( "/([^\\\])F/", "\\1" . backslashit( $datemonth ), $dateformatstring );
 		$dateformatstring = preg_replace( "/([^\\\])l/", "\\1" . backslashit( $dateweekday ), $dateformatstring );
 		$dateformatstring = preg_replace( "/([^\\\])M/", "\\1" . backslashit( $datemonth_abbrev ), $dateformatstring );
+		$dateformatstring = preg_replace( "/([^\\\])S/", "\\1" . backslashit( $dateordinal_suffix ), $dateformatstring );
 		$dateformatstring = preg_replace( "/([^\\\])a/", "\\1" . backslashit( $datemeridiem ), $dateformatstring );
 		$dateformatstring = preg_replace( "/([^\\\])A/", "\\1" . backslashit( $datemeridiem_capital ), $dateformatstring );
 
Index: src/wp-includes/l10n.php
===================================================================
--- src/wp-includes/l10n.php	(revision 36118)
+++ src/wp-includes/l10n.php	(working copy)
@@ -815,6 +815,33 @@
 }
 
 /**
+ * Get the ordinal suffix for a given number
+ *
+ * @param int|string $number
+ * @return string
+ */
+function get_ordinal_suffix( $number ) {
+	$suffix = _x( 'th', 'ordinal suffix' );
+
+	$value  = intval( $number );
+	if ( ! in_array( $value, array( 11, 12, 13 ) ) ) {
+		switch ( $value % 10 ) {
+			case 1:
+				$suffix = _x( 'st', 'ordinal suffix' );
+				break;
+			case 2:
+				$suffix = _x( 'nd', 'ordinal suffix' );
+				break;
+			case 3:
+				$suffix = _x( 'rd', 'ordinal suffix' );
+				break;
+		}
+	}
+
+	return apply_filters( 'ordinal_suffix', $suffix, $number );
+}
+
+/**
  * Translates role name.
  *
  * Since the role names are in the database and not in the source there
Index: tests/phpunit/tests/functions.php
===================================================================
--- tests/phpunit/tests/functions.php	(revision 36118)
+++ tests/phpunit/tests/functions.php	(working copy)
@@ -717,4 +717,35 @@
 		the_date( 'Y', 'before ', ' after', false );
 		$this->assertEquals( '', ob_get_clean() );
 	}
+
+	/**
+	 * Data provider for test_date_i18n_ordinal_suffix
+	 * @return array
+	 */
+	public function date_i18n_ordinal_suffix_provider() {
+		return array(
+			array( _x( 'st', 'ordinal suffix' ), 1448928000 ), // 2015-12-01
+			array( _x( 'nd', 'ordinal suffix' ), 1449014400 ), // 2015-12-02
+			array( _x( 'rd', 'ordinal suffix' ), 1449100800 ), // 2015-12-03
+			array( _x( 'th', 'ordinal suffix' ), 1449705600 ), // 2015-12-10
+			array( _x( 'th', 'ordinal suffix' ), 1449792000 ), // 2015-12-11
+			array( _x( 'th', 'ordinal suffix' ), 1449878400 ), // 2015-12-12
+			array( _x( 'th', 'ordinal suffix' ), 1449964800 ), // 2015-12-13
+			array( _x( 'th', 'ordinal suffix' ), 1450483200 ), // 2015-12-19
+			array( _x( 'st', 'ordinal suffix' ), 1450656000 ), // 2015-12-21
+			array( _x( 'nd', 'ordinal suffix' ), 1450742400 ), // 2015-12-22
+			array( _x( 'rd', 'ordinal suffix' ), 1450828800 ), // 2015-12-23
+			array( _x( 'th', 'ordinal suffix' ), 1450915200 ), // 2015-12-24
+			array( _x( 'st', 'ordinal suffix' ), 1451520000 ), // 2015-12-31
+		);
+	}
+
+	/**
+	 * @ticket 22225
+	 * @dataProvider date_i18n_ordinal_suffix_provider
+	 */
+	function test_date_i18n_ordinal_suffix( $a, $b ) {
+		$this->assertEquals( $a, date_i18n( 'S', $b ) );
+	}
+
 }
Index: tests/phpunit/tests/l10n.php
===================================================================
--- tests/phpunit/tests/l10n.php	(revision 36118)
+++ tests/phpunit/tests/l10n.php	(working copy)
@@ -26,4 +26,37 @@
 		$this->assertEquals( 'first-before-bar|second-before-bar', before_last_bar( 'first-before-bar|second-before-bar|after-last-bar' ) );
 	}
 
+	/**
+	 * Data provider for test_get_ordinal_suffix
+	 * @return array
+	 */
+	public function ordinal_suffix_provider() {
+		return array(
+			array( _x( 'th', 'ordinal suffix' ), 0 ),
+			array( _x( 'st', 'ordinal suffix' ), 1 ),
+			array( _x( 'nd', 'ordinal suffix' ), 2 ),
+			array( _x( 'rd', 'ordinal suffix' ), 3 ),
+			array( _x( 'th', 'ordinal suffix' ), '4' ),
+			array( _x( 'th', 'ordinal suffix' ), '05' ),
+			array( _x( 'th', 'ordinal suffix' ), 10 ),
+			array( _x( 'th', 'ordinal suffix' ), 11 ),
+			array( _x( 'th', 'ordinal suffix' ), 12 ),
+			array( _x( 'th', 'ordinal suffix' ), 13 ),
+			array( _x( 'th', 'ordinal suffix' ), 19 ),
+			array( _x( 'st', 'ordinal suffix' ), 21 ),
+			array( _x( 'nd', 'ordinal suffix' ), 22 ),
+			array( _x( 'rd', 'ordinal suffix' ), 23 ),
+			array( _x( 'th', 'ordinal suffix' ), 24 ),
+			array( _x( 'st', 'ordinal suffix' ), 31 ),
+		);
+	}
+
+	/**
+	 * @ticket 22225
+	 * @dataProvider ordinal_suffix_provider
+	 */
+	function test_get_ordinal_suffix( $a, $b ) {
+		$this->assertEquals( $a, get_ordinal_suffix( $b ) );
+	}
+
 }
