Index: src/wp-includes/functions.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/wp-includes/functions.php	(date 1558926153000)
+++ src/wp-includes/functions.php	(date 1559717516914)
@@ -81,6 +81,10 @@
  * take over the format for the date. If it isn't, then the date format string
  * will be used instead.
  *
+ * Note that due to the way WP typically generates a sum of timestamp and offset with `strtotime()`
+ * it implies offset added at a _current_ time, not at the time which timestamp represents.
+ * Storing such timestamps or calculating them differently will lead to invalid output.
+ *
  * @since 0.71
  *
  * @global WP_Locale $wp_locale
@@ -106,78 +110,74 @@
 	 * See https://core.trac.wordpress.org/ticket/9396
 	 */
 	$req_format = $dateformatstring;
+	$new_format = '';
 
-	$dateformatstring = preg_replace( '/(?<!\\\\)c/', DATE_W3C, $dateformatstring );
+	// We need to unpack shorthand `r` format because it has parts that might be localized.
 	$dateformatstring = preg_replace( '/(?<!\\\\)r/', DATE_RFC2822, $dateformatstring );
 
-	if ( ( ! empty( $wp_locale->month ) ) && ( ! empty( $wp_locale->weekday ) ) ) {
-		$datemonth            = $wp_locale->get_month( gmdate( 'm', $i ) );
-		$datemonth_abbrev     = $wp_locale->get_month_abbrev( $datemonth );
-		$dateweekday          = $wp_locale->get_weekday( gmdate( 'w', $i ) );
-		$dateweekday_abbrev   = $wp_locale->get_weekday_abbrev( $dateweekday );
-		$datemeridiem         = $wp_locale->get_meridiem( gmdate( 'a', $i ) );
-		$datemeridiem_capital = $wp_locale->get_meridiem( gmdate( '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 );
+	/**
+	 * Timestamp with offset is typically produced by a UTC `strtotime()` call on an input without time zone.
+     *
+     * This is the best attempt to reverse that operation into a local time to use.
+	 */
+	$local_time = gmdate( 'Y-m-d H:i:s', $i );
+	$gmt_mode   = $gmt && ( false === $timestamp_with_offset );
+	$timezone   = $gmt_mode ? new DateTimeZone( 'UTC' ) : wp_timezone();
+	$datetime   = date_create( $local_time, $timezone );
 
-		$dateformatstring = substr( $dateformatstring, 1, strlen( $dateformatstring ) - 1 );
+	/**
+	 * This is a legacy implementation quirk that returned timestamp is also with offset.
+	 *
+	 * Ideally this function should never be used to produce a timestamp.
+	 */
+	$timestamp_mode = 'U' === $dateformatstring;
+
+	if ( $timestamp_mode ) {
+		$new_format = $i;
 	}
-	$timezone_formats    = array( 'P', 'I', 'O', 'T', 'Z', 'e' );
-	$timezone_formats_re = implode( '|', $timezone_formats );
-	if ( preg_match( "/$timezone_formats_re/", $dateformatstring ) ) {
-		$timezone_string = get_option( 'timezone_string' );
-		if ( false === $timestamp_with_offset && $gmt ) {
-			$timezone_string = 'UTC';
-		}
-		if ( $timezone_string ) {
-			$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 );
-				}
-			}
-		} else {
-			$offset = get_option( 'gmt_offset' );
-			foreach ( $timezone_formats as $timezone_format ) {
-				if ( 'I' === $timezone_format ) {
-					continue;
-				}
 
-				if ( false !== strpos( $dateformatstring, $timezone_format ) ) {
-					if ( 'Z' === $timezone_format ) {
-						$formatted = (string) ( $offset * HOUR_IN_SECONDS );
-					} else {
-						$prefix    = '';
-						$hours     = (int) $offset;
-						$separator = '';
-						$minutes   = abs( ( $offset - $hours ) * 60 );
+	if ( ! $timestamp_mode && ( ! empty( $wp_locale->month ) ) && ( ! empty( $wp_locale->weekday ) ) ) {
+		$month   = $wp_locale->get_month( $datetime->format( 'm' ) );
+		$weekday = $wp_locale->get_weekday( $datetime->format( 'w' ) );
 
-						if ( 'T' === $timezone_format ) {
-							$prefix = 'GMT';
-						} elseif ( 'e' === $timezone_format || 'P' === $timezone_format ) {
-							$separator = ':';
-						}
+		$format_length = strlen( $dateformatstring );
 
-						$formatted = sprintf( '%s%+03d%s%02d', $prefix, $hours, $separator, $minutes );
-					}
+		for ( $i = 0; $i < $format_length; $i ++ ) {
+			switch ( $dateformatstring[ $i ] ) {
+				case 'D':
+					$new_format .= backslashit( $wp_locale->get_weekday_abbrev( $weekday ) );
+					break;
+				case 'F':
+					$new_format .= backslashit( $month );
+					break;
+				case 'l':
+					$new_format .= backslashit( $weekday );
+					break;
+				case 'M':
+					$new_format .= backslashit( $wp_locale->get_month_abbrev( $month ) );
+					break;
+				case 'a':
+					$new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'a' ) ) );
+					break;
+				case 'A':
+					$new_format .= backslashit( $wp_locale->get_meridiem( $datetime->format( 'A' ) ) );
+					break;
+				case '\\':
+					$new_format .= $dateformatstring[ $i ];
 
-					$dateformatstring = ' ' . $dateformatstring;
-					$dateformatstring = preg_replace( "/([^\\\])$timezone_format/", "\\1" . backslashit( $formatted ), $dateformatstring );
-					$dateformatstring = substr( $dateformatstring, 1 );
-				}
+					// If character follows slash we add it without translating.
+					if ( $i < $format_length ) {
+						$new_format .= $dateformatstring[ ++ $i ];
+					}
+					break;
+				default:
+					$new_format .= $dateformatstring[ $i ];
+					break;
 			}
 		}
 	}
-	$j = @gmdate( $dateformatstring, $i );
+
+	$j = $datetime->format( $new_format );
 
 	/**
 	 * Filters the date formatted based on the locale.
Index: tests/phpunit/tests/date/dateI18n.php
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- tests/phpunit/tests/date/dateI18n.php	(date 1558926153000)
+++ tests/phpunit/tests/date/dateI18n.php	(date 1559716648341)
@@ -6,6 +6,8 @@
  */
 class Tests_Date_I18n extends WP_UnitTestCase {
 	public function test_should_format_date() {
+		update_option( 'timezone_string', 'UTC' );
+
 		$this->assertEquals( strtotime( gmdate( 'Y-m-d H:i:s' ) ), strtotime( date_i18n( 'Y-m-d H:i:s' ) ), 'The dates should be equal', 2 );
 	}
 
@@ -17,10 +19,6 @@
 		$this->assertEquals( strtotime( gmdate( DATE_RFC3339 ) ), strtotime( date_i18n( DATE_RFC3339, false, true ) ), 'The dates should be equal', 2 );
 	}
 
-	public function test_custom_timestamp_ignores_gmt_setting() {
-		$this->assertEquals( '2012-12-01 00:00:00', date_i18n( 'Y-m-d H:i:s', strtotime( '2012-12-01 00:00:00' ) ) );
-	}
-
 	public function test_custom_timezone_setting() {
 		update_option( 'timezone_string', 'America/Regina' );
 
@@ -107,4 +105,61 @@
 			),
 		);
 	}
+
+	public function test_should_return_wp_timestamp() {
+
+		update_option( 'timezone_string', 'Europe/Kiev' );
+
+		$datetime     = new DateTimeImmutable( 'now', wp_timezone() );
+		$timestamp    = $datetime->getTimestamp();
+		$wp_timestamp = $timestamp + $datetime->getOffset();
+
+		$this->assertEquals( $wp_timestamp, date_i18n( 'U' ), 2 );
+		$this->assertEquals( $timestamp, date_i18n( 'U', false, true ), 2 );
+		$this->assertEquals( $wp_timestamp, date_i18n( 'U', $wp_timestamp ) );
+	}
+
+	/**
+	 * @link   https://core.trac.wordpress.org/ticket/43530
+	 * @ticket 43530
+	 */
+	public function test_swatch_internet_time_with_wp_timestamp() {
+		update_option( 'timezone_string', 'America/Regina' );
+
+		$this->assertEquals( gmdate( 'B' ), date_i18n( 'B' ) );
+	}
+
+	public function test_should_handle_escaped_formats() {
+
+		$format = 'D | \D | \\D | \\\D | \\\\D | \\\\\D | \\\\\\D';
+
+		$this->assertEquals( gmdate( $format ), date_i18n( $format ) );
+	}
+
+	/**
+	 * @dataProvider dst_times
+	 *
+	 * @param string $time    Time to test in Y-m-d H:i:s format.
+	 * @param string $tmezone PHP time zone string to use.
+	 */
+	public function test_should_handle_dst( $time, $timezone ) {
+
+		update_option( 'timezone_string', $timezone );
+
+		$timezone     = new DateTimeZone( $timezone );
+		$datetime     = new DateTime( $time, $timezone );
+		$wp_timestamp = strtotime( $time );
+		$format       = 'I ' . DATE_RFC3339;
+
+		$this->assertEquals( $datetime->format( $format ), date_i18n( $format, $wp_timestamp ) );
+	}
+
+	public function dst_times() {
+		return [
+			'Before DST start' => [ '2019-03-31 02:59:00', 'Europe/Kiev' ],
+			'After DST start'  => [ '2019-03-31 04:01:00', 'Europe/Kiev' ],
+			'Before DST end'   => [ '2019-10-27 02:59:00', 'Europe/Kiev' ],
+			'After DST end'    => [ '2019-10-27 04:01:00', 'Europe/Kiev' ],
+		];
+	}
 }
