diff --git a/core/modules/views/src/Plugin/views/query/DateSqlBase.php b/core/modules/views/src/Plugin/views/query/DateSqlBase.php
new file mode 100644
index 0000000..73e7557
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/query/DateSqlBase.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Drupal\views\Plugin\views\query;
+
+use Drupal\Core\Database\Connection;
+
+/**
+ * Common date SQL base class.
+ */
+abstract class DateSqlBase implements DateSqlInterface {
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $database;
+
+  /**
+   * Construct the date sql class.
+   *
+   * @param \Drupal\Core\Database\Connection
+   *   The database connection.
+   */
+  public function __construct(Connection $database) {
+    $this->database = $database;
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/query/DateSqlInterface.php b/core/modules/views/src/Plugin/views/query/DateSqlInterface.php
new file mode 100644
index 0000000..c65230a
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/query/DateSqlInterface.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\views\Plugin\views\query;
+
+/**
+ * Defines an interface for handling date queries with SQL.
+ */
+interface DateSqlInterface {
+
+  /**
+   * Returns a native database expression for a given field.
+   *
+   * @param string $field
+   *   The query field that will be used in the expression.
+   * @param integer $offset
+   *   The timezone offset in seconds.
+   *
+   * @return string
+   *   An expression representing a date field with timezone.
+   */
+  public function getDateField($field, $offset);
+
+  /**
+   * Creates a native database date formatting.
+   *
+   * @param string $field
+   *   An appropriate query expression pointing to the date field.
+   * @param string $format
+   *   A format string for the result. For example: 'Y-m-d H:i:s'.
+   * @param bool $string_date
+   *   For certain databases, date format functions vary depending on string or
+   *   numeric storage.
+   *
+   * @return string
+   *   A string representing the field formatted as a date as specified by
+   *   $format.
+   */
+  public function getDateFormat($field, $format, $string_date = FALSE);
+
+  /**
+   * Set the database to the given timezone.
+   *
+   * @param string $offset
+   *   The timezone.
+   */
+  public function setTimezoneOffset($offset);
+
+}
diff --git a/core/modules/views/src/Plugin/views/query/MysqlDateSql.php b/core/modules/views/src/Plugin/views/query/MysqlDateSql.php
new file mode 100644
index 0000000..881c15e
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/query/MysqlDateSql.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace Drupal\views\Plugin\views\query;
+
+/**
+ * MySQL-specific date handling.
+ */
+class MysqlDateSql extends DateSqlBase {
+
+  /**
+   * An array of PHP-to-MySQL replacement patterns.
+   */
+  protected static $replace = [
+    'Y' => '%Y',
+    'y' => '%y',
+    'M' => '%b',
+    'm' => '%m',
+    'n' => '%c',
+    'F' => '%M',
+    'D' => '%a',
+    'd' => '%d',
+    'l' => '%W',
+    'j' => '%e',
+    'W' => '%v',
+    'H' => '%H',
+    'h' => '%h',
+    'i' => '%i',
+    's' => '%s',
+    'A' => '%p',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDateField($field, $offset) {
+    $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
+    if (!empty($offset)) {
+      $field = "($field + INTERVAL $offset SECOND)";
+    }
+    return $field;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDateFormat($field, $format, $string_date = FALSE) {
+    $format = strtr($format, static::$replace);
+    return "DATE_FORMAT($field, '$format')";
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTimezoneOffset($offset) {
+    $this->database->query("SET @@session.time_zone = '$offset'");
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php b/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php
new file mode 100644
index 0000000..56ed996
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/query/PostgresqlDateSql.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\views\Plugin\views\query;
+
+/**
+ * PostgreSQL-specific date handling.
+ */
+class PostgresqlDateSql extends DateSqlBase {
+
+  /**
+   * An array of PHP-to-PostgreSQL replacement patterns.
+   *
+   * @var array
+   */
+  protected static $replace = [
+    'Y' => 'YYYY',
+    'y' => 'YY',
+    'M' => 'Mon',
+    'm' => 'MM',
+    // No format for Numeric representation of a month, without leading
+    // zeros.
+    'n' => 'MM',
+    'F' => 'Month',
+    'D' => 'Dy',
+    'd' => 'DD',
+    'l' => 'Day',
+    // No format for Day of the month without leading zeros.
+    'j' => 'DD',
+    'W' => 'IW',
+    'H' => 'HH24',
+    'h' => 'HH12',
+    'i' => 'MI',
+    's' => 'SS',
+    'A' => 'AM',
+  ];
+
+  /**
+   * @inheritDoc
+   */
+  public function getDateField($field, $offset) {
+    $field = "TO_TIMESTAMP($field)";
+    if (!empty($offset)) {
+      $field = "($field + INTERVAL '$offset SECONDS')";
+    }
+    return $field;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDateFormat($field, $format, $string_date = FALSE) {
+    $format = strtr($format, static::$replace);
+    if (!$string_date) {
+      return "TO_CHAR($field, '$format')";
+    }
+    // In order to allow for partials (eg, only the year), transform to a
+    // date, back to a string again.
+    return "TO_CHAR(TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS'), '$format')";
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTimezoneOffset($offset) {
+    $this->database->query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
+  }
+
+}
diff --git a/core/modules/views/src/Plugin/views/query/Sql.php b/core/modules/views/src/Plugin/views/query/Sql.php
index cfe593c..a4833b7 100644
--- a/core/modules/views/src/Plugin/views/query/Sql.php
+++ b/core/modules/views/src/Plugin/views/query/Sql.php
@@ -116,6 +116,13 @@ class Sql extends QueryPluginBase {
   protected $entityTypeManager;
 
   /**
+   * The database-specific date handler.
+   *
+   * @var \Drupal\views\Plugin\views\query\DateSqlInterface
+   */
+  protected $dateSql;
+
+  /**
    * Constructs a Sql object.
    *
    * @param array $configuration
@@ -126,11 +133,14 @@ class Sql extends QueryPluginBase {
    *   The plugin implementation definition.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity type manager.
+   * @param \Drupal\views\Plugin\views\query\DateSqlInterface $date_sql
+   *   The database-specific date handler.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, DateSqlInterface $date_sql) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->entityTypeManager = $entity_type_manager;
+    $this->dateSql = $date_sql;
   }
 
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
@@ -138,7 +148,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('entity_type.manager')
+      $container->get('entity_type.manager'),
+      $container->get('views.date_sql')
     );
   }
 
@@ -1755,174 +1766,35 @@ public function aggregationMethodDistinct($group_type, $field) {
    * {@inheritdoc}
    */
   public function getDateField($field) {
-    $db_type = Database::getConnection()->databaseType();
     $offset = $this->setupTimezone();
-    if (isset($offset) && !is_numeric($offset)) {
+    $offset_seconds = FALSE;
+    if ($offset) {
       $dtz = new \DateTimeZone($offset);
       $dt = new \DateTime('now', $dtz);
       $offset_seconds = $dtz->getOffset($dt);
     }
-
-    switch ($db_type) {
-      case 'mysql':
-        $field = "DATE_ADD('19700101', INTERVAL $field SECOND)";
-        if (!empty($offset)) {
-          $field = "($field + INTERVAL $offset_seconds SECOND)";
-        }
-        break;
-      case 'pgsql':
-        $field = "TO_TIMESTAMP($field)";
-        if (!empty($offset)) {
-          $field = "($field + INTERVAL '$offset_seconds SECONDS')";
-        }
-        break;
-      case 'sqlite':
-        if (!empty($offset)) {
-          $field = "($field + $offset_seconds)";
-        }
-        break;
-    }
-
-    return $field;
+    return $this->dateSql->getDateField($field, $offset_seconds);
   }
 
   /**
    * {@inheritdoc}
    */
   public function setupTimezone() {
-    $timezone = drupal_get_user_timezone();
-
-    // set up the database timezone
-    $db_type = Database::getConnection()->databaseType();
-    if (in_array($db_type, array('mysql', 'pgsql'))) {
-      $offset = '+00:00';
-      static $already_set = FALSE;
-      if (!$already_set) {
-        if ($db_type == 'pgsql') {
-          Database::getConnection()->query("SET TIME ZONE INTERVAL '$offset' HOUR TO MINUTE");
-        }
-        elseif ($db_type == 'mysql') {
-          Database::getConnection()->query("SET @@session.time_zone = '$offset'");
-        }
-
-        $already_set = TRUE;
-      }
+    // Set the database timezone offset.
+    static $already_set = FALSE;
+    if (!$already_set) {
+      $this->dateSql->setTimezoneOffset('+00:00');
+      $already_set = TRUE;
     }
 
-    return $timezone;
+    return parent::setupTimezone();
   }
 
   /**
    * {@inheritdoc}
    */
   public function getDateFormat($field, $format, $string_date = FALSE) {
-    $db_type = Database::getConnection()->databaseType();
-    switch ($db_type) {
-      case 'mysql':
-        $replace = array(
-          'Y' => '%Y',
-          'y' => '%y',
-          'M' => '%b',
-          'm' => '%m',
-          'n' => '%c',
-          'F' => '%M',
-          'D' => '%a',
-          'd' => '%d',
-          'l' => '%W',
-          'j' => '%e',
-          'W' => '%v',
-          'H' => '%H',
-          'h' => '%h',
-          'i' => '%i',
-          's' => '%s',
-          'A' => '%p',
-        );
-        $format = strtr($format, $replace);
-        return "DATE_FORMAT($field, '$format')";
-      case 'pgsql':
-        $replace = array(
-          'Y' => 'YYYY',
-          'y' => 'YY',
-          'M' => 'Mon',
-          'm' => 'MM',
-          // No format for Numeric representation of a month, without leading
-          // zeros.
-          'n' => 'MM',
-          'F' => 'Month',
-          'D' => 'Dy',
-          'd' => 'DD',
-          'l' => 'Day',
-          // No format for Day of the month without leading zeros.
-          'j' => 'DD',
-          'W' => 'IW',
-          'H' => 'HH24',
-          'h' => 'HH12',
-          'i' => 'MI',
-          's' => 'SS',
-          'A' => 'AM',
-        );
-        $format = strtr($format, $replace);
-        if (!$string_date) {
-          return "TO_CHAR($field, '$format')";
-        }
-        // In order to allow for partials (eg, only the year), transform to a
-        // date, back to a string again.
-        return "TO_CHAR(TO_TIMESTAMP($field, 'YYYY-MM-DD HH24:MI:SS'), '$format')";
-      case 'sqlite':
-        $replace = array(
-          'Y' => '%Y',
-          // No format for 2 digit year number.
-          'y' => '%Y',
-          // No format for 3 letter month name.
-          'M' => '%m',
-          'm' => '%m',
-          // No format for month number without leading zeros.
-          'n' => '%m',
-          // No format for full month name.
-          'F' => '%m',
-          // No format for 3 letter day name.
-          'D' => '%d',
-          'd' => '%d',
-          // No format for full day name.
-          'l' => '%d',
-          // no format for day of month number without leading zeros.
-          'j' => '%d',
-          'W' => '%W',
-          'H' => '%H',
-          // No format for 12 hour hour with leading zeros.
-          'h' => '%H',
-          'i' => '%M',
-          's' => '%S',
-          // No format for AM/PM.
-          'A' => '',
-        );
-        $format = strtr($format, $replace);
-
-        // Don't use the 'unixepoch' flag for string date comparisons.
-        $unixepoch = $string_date ? '' : ", 'unixepoch'";
-
-        // SQLite does not have a ISO week substitution string, so it needs
-        // special handling.
-        // @see http://wikipedia.org/wiki/ISO_week_date#Calculation
-        // @see http://stackoverflow.com/a/15511864/1499564
-        if ($format === '%W') {
-          $expression = "((strftime('%j', date(strftime('%Y-%m-%d', $field" . $unixepoch . "), '-3 days', 'weekday 4')) - 1) / 7 + 1)";
-        }
-        else {
-          $expression = "strftime('$format', $field" . $unixepoch . ")";
-        }
-        // The expression yields a string, but the comparison value is an
-        // integer in case the comparison value is a float, integer, or numeric.
-        // All of the above SQLite format tokens only produce integers. However,
-        // the given $format may contain 'Y-m-d', which results in a string.
-        // @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
-        // @see http://www.sqlite.org/lang_datefunc.html
-        // @see http://www.sqlite.org/lang_expr.html#castexpr
-        if (preg_match('/^(?:%\w)+$/', $format)) {
-          $expression = "CAST($expression AS NUMERIC)";
-        }
-        return $expression;
-    }
+    return $this->dateSql->getDateFormat($field, $format, $string_date);
   }
 
 }
diff --git a/core/modules/views/src/Plugin/views/query/SqliteDateSql.php b/core/modules/views/src/Plugin/views/query/SqliteDateSql.php
new file mode 100644
index 0000000..ac4cf9c
--- /dev/null
+++ b/core/modules/views/src/Plugin/views/query/SqliteDateSql.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\views\Plugin\views\query;
+
+/**
+ * SQLite-specific date handling.
+ */
+class SqliteDateSql extends DateSqlBase {
+
+  /**
+   * An array of PHP-to-SQLite date replacement patterns.
+   *
+   * @var array
+   */
+  protected static $replace = [
+    'Y' => '%Y',
+    // No format for 2 digit year number.
+    'y' => '%Y',
+    // No format for 3 letter month name.
+    'M' => '%m',
+    'm' => '%m',
+    // No format for month number without leading zeros.
+    'n' => '%m',
+    // No format for full month name.
+    'F' => '%m',
+    // No format for 3 letter day name.
+    'D' => '%d',
+    'd' => '%d',
+    // No format for full day name.
+    'l' => '%d',
+    // no format for day of month number without leading zeros.
+    'j' => '%d',
+    'W' => '%W',
+    'H' => '%H',
+    // No format for 12 hour hour with leading zeros.
+    'h' => '%H',
+    'i' => '%M',
+    's' => '%S',
+    // No format for AM/PM.
+    'A' => '',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDateField($field, $offset) {
+    if (!empty($offset)) {
+      $field = "($field + $offset)";
+    }
+    return $field;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDateFormat($field, $format, $string_date = FALSE) {
+    $format = strtr($format, static::$replace);
+
+    // Don't use the 'unixepoch' flag for string date comparisons.
+    $unixepoch = $string_date ? '' : ", 'unixepoch'";
+
+    // SQLite does not have a ISO week substitution string, so it needs
+    // special handling.
+    // @see http://wikipedia.org/wiki/ISO_week_date#Calculation
+    // @see http://stackoverflow.com/a/15511864/1499564
+    if ($format === '%W') {
+      $expression = "((strftime('%j', date(strftime('%Y-%m-%d', $field" . $unixepoch . "), '-3 days', 'weekday 4')) - 1) / 7 + 1)";
+    }
+    else {
+      $expression = "strftime('$format', $field" . $unixepoch . ")";
+    }
+    // The expression yields a string, but the comparison value is an
+    // integer in case the comparison value is a float, integer, or numeric.
+    // All of the above SQLite format tokens only produce integers. However,
+    // the given $format may contain 'Y-m-d', which results in a string.
+    // @see \Drupal\Core\Database\Driver\sqlite\Connection::expandArguments()
+    // @see http://www.sqlite.org/lang_datefunc.html
+    // @see http://www.sqlite.org/lang_expr.html#castexpr
+    if (preg_match('/^(?:%\w)+$/', $format)) {
+      $expression = "CAST($expression AS NUMERIC)";
+    }
+    return $expression;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setTimezoneOffset($offset) {
+    // Nothing to do here.
+  }
+
+}
diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml
index 1a01543..ac8ce14 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -80,3 +80,16 @@ services:
     arguments: ['@entity.manager']
     tags:
       - { name: 'event_subscriber' }
+  views.date_sql:
+    class: Drupal\datetime\GenericDateField
+    tags:
+       - { name: backend_overridable }
+  mysql.views.date_sql:
+    class: Drupal\views\Plugin\views\query\MysqlDateSql
+    arguments: ['@database']
+  pgsql.views.date_sql:
+    class: Drupal\views\Plugin\views\query\PostgresqlDateSql
+    arguments: ['@database']
+  sqlite.views.date_sql:
+    class: Drupal\views\Plugin\views\query\SqliteDateSql
+    arguments: ['@database']
