Index: src/wp-includes/functions.php
===================================================================
--- src/wp-includes/functions.php	(revision 25770)
+++ src/wp-includes/functions.php	(working copy)
@@ -82,17 +82,16 @@
  * @return string The date, translated if locale specifies it.
  */
 function date_i18n( $dateformatstring, $unixtimestamp = false, $gmt = false ) {
+	/* @var $wp_locale WP_Locale */
 	global $wp_locale;
-	$i = $unixtimestamp;
+	$timestamp = $unixtimestamp;
 
-	if ( false === $i ) {
-		if ( ! $gmt )
-			$i = current_time( 'timestamp' );
+	// get current timestamp if $unixtimestamp is false
+	if ( false === $timestamp ) {
+		if ( $gmt )
+			$timestamp = time();
 		else
-			$i = time();
-		// we should not let date() interfere with our
-		// specially computed timestamp
-		$gmt = true;
+			$timestamp = current_time( 'timestamp' );
 	}
 
 	// store original value for language with untypical grammars
@@ -99,46 +98,107 @@
 	// see http://core.trac.wordpress.org/ticket/9396
 	$req_format = $dateformatstring;
 
-	$datefunc = $gmt? 'gmdate' : 'date';
+	// get components of the date (timestamp) as array
+	$datecomponents = getdate( $timestamp );
 
-	if ( ( !empty( $wp_locale->month ) ) && ( !empty( $wp_locale->weekday ) ) ) {
-		$datemonth = $wp_locale->get_month( $datefunc( 'm', $i ) );
-		$datemonth_abbrev = $wp_locale->get_month_abbrev( $datemonth );
-		$dateweekday = $wp_locale->get_weekday( $datefunc( 'w', $i ) );
-		$dateweekday_abbrev = $wp_locale->get_weekday_abbrev( $dateweekday );
-		$datemeridiem = $wp_locale->get_meridiem( $datefunc( 'a', $i ) );
-		$datemeridiem_capital = $wp_locale->get_meridiem( $datefunc( 'A', $i ) );
-		$dateformatstring = ' '.$dateformatstring;
-		$dateformatstring = preg_replace( "/([^\\\])D/", "\\1" . backslashit( $dateweekday_abbrev ), $dateformatstring );
-		$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( "/([^\\\])a/", "\\1" . backslashit( $datemeridiem ), $dateformatstring );
-		$dateformatstring = preg_replace( "/([^\\\])A/", "\\1" . backslashit( $datemeridiem_capital ), $dateformatstring );
+	// numeric representation of a month, with leading zeros
+	$datemonth = $wp_locale->get_month( $datecomponents['mon'] );
+	$datemonth_abbrev = $wp_locale->get_month_abbrev( $datemonth );
+	// numeric representation of the day of the week
+	$dateweekday = $wp_locale->get_weekday( $datecomponents['wday'] );
+	$dateweekday_abbrev = $wp_locale->get_weekday_abbrev( $dateweekday );
+	// get if hour is Ante meridiem or Post meridiem
+	$meridiem = $datecomponents['hours'] >= 12 ? 'pm' : 'am';
+	// lowercase Ante meridiem and Post meridiem hours
+	$datemeridiem = $wp_locale->get_meridiem( $meridiem );
+	// uppercase Ante meridiem and Post meridiem
+	$datemeridiem_capital = $wp_locale->get_meridiem( strtoupper( $meridiem ) );
 
-		$dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 );
+	// escape literals
+	$dateweekday_abbrev = backslashit( $dateweekday_abbrev );
+	$datemonth = backslashit( $datemonth );
+	$dateweekday = backslashit( $dateweekday );
+	$datemonth_abbrev = backslashit( $datemonth_abbrev );
+	$datemeridiem = backslashit( $datemeridiem );
+	$datemeridiem_capital = backslashit( $datemeridiem_capital );
+
+	// the translated format string
+	$translateddateformatstring = '';
+	// the 2 arrays map a format literal to its translation (e. g. 'F' to the escaped month translation)
+	$tranlateformates = array( 'D', 'F', 'l', 'M', 'a', 'A', 'c', 'r' );
+	$translations = array(
+		$dateweekday_abbrev, // D
+		$datemonth, // F
+		$dateweekday, // l
+		$datemonth_abbrev, // M
+		$datemeridiem, // a
+		$datemeridiem_capital, // a
+		'Y-m-d\TH:i:sP', // c
+		sprintf( '%s, d %s Y H:i:s O', $dateweekday_abbrev, $datemonth_abbrev ), // r
+	);
+
+	// find each format literal that needs translation and replace it by its translation
+	// respects the escaping
+	// iterate $dateformatstring from ending to beginning
+	for ( $i = strlen( $dateformatstring ) - 1; $i > -1; $i-- ) {
+		// test if current char is format literal that needs translation
+		$tranlateformateindex = array_search( $dateformatstring[$i], $tranlateformates );
+
+		if ( $tranlateformateindex !== false ) {
+			// counts the slashes (the escape char) in front of the current char
+			$slashescounter = 0;
+
+			// count all slashes left-hand side of the current char
+			for ( $j = $i - 1; $j > -1; $j-- ) {
+				if ( $dateformatstring[$j] == '\\' )
+					$slashescounter++;
+				else
+					break;
+			}
+
+			// number of slashes is even
+			if ( $slashescounter % 2 == 0 )
+				// current char is not escaped, therefore it is a format literal
+				$translateddateformatstring = $translations[$tranlateformateindex] . $translateddateformatstring;
+			else
+				// current char is escaped, therefore it is not a format literal, just add it unchanged
+				$translateddateformatstring = $dateformatstring[$i] . $translateddateformatstring;
+		}
+		else
+			// current char is no a format literal, just add it unchanged
+			$translateddateformatstring = $dateformatstring[$i] . $translateddateformatstring;
 	}
-	$timezone_formats = array( 'P', 'I', 'O', 'T', 'Z', 'e' );
-	$timezone_formats_re = implode( '|', $timezone_formats );
-	if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) {
+
+	$dateformatstring = $translateddateformatstring;
+
+	if ( $gmt )
+		// get GMT date string
+		$dateformated = gmdate( $dateformatstring, $timestamp );
+	else {
+		// get Wordpress time zone
 		$timezone_string = get_option( 'timezone_string' );
 		if ( $timezone_string ) {
+			// create time zone object
 			$timezone_object = timezone_open( $timezone_string );
-			$date_object = date_create( null, $timezone_object );
-			foreach( $timezone_formats as $timezone_format ) {
-				if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
-					$formatted = date_format( $date_object, $timezone_format );
-					$dateformatstring = ' '.$dateformatstring;
-					$dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
-					$dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) -1 );
-				}
-			}
+			// create date object from time zone object
+			$localdateobject = date_create( null, $timezone_object );
+			// set time and date of $localdateobject to current $timestamp
+			$datecomponents = isset( $datecomponents ) ? $datecomponents : getdate( $timestamp );
+			date_date_set( $localdateobject, $datecomponents['year'], $datecomponents['mon'], $datecomponents['mday'] );
+			date_time_set( $localdateobject, $datecomponents['hours'], $datecomponents['minutes'], $datecomponents['seconds'] );
+			// format date  according to the Wordpress time zone
+			$dateformated = date_format( $localdateobject, $dateformatstring );
 		}
+		else {
+			// fall back if no Wordpress time zone set
+			$dateformated = date( $dateformatstring, $i );
+		}
 	}
-	$j = @$datefunc( $dateformatstring, $i );
+
 	// allow plugins to redo this entirely for languages with untypical grammars
-	$j = apply_filters('date_i18n', $j, $req_format, $i, $gmt);
-	return $j;
+	$dateformated = apply_filters( 'date_i18n', $dateformated, $req_format, $timestamp, $gmt );
+
+	return $dateformated;
 }
 
 /**
