Correctly JSON-encode datetimes aware of non-UTC timezones
diff --git a/CHANGES b/CHANGES
index 7f9e968bdf..f0cb97232d 100644
--- a/CHANGES
+++ b/CHANGES
@@ -79,6 +79,7 @@ Major release, unreleased
 - Removed error handler caching because it caused unexpected results for some
   exception inheritance hierarchies. Register handlers explicitly for each
   exception if you don't want to traverse the MRO. (`#2362`_)
+- Fix incorrect JSON encoding of aware, non-UTC datetimes. (`#2374`_)
 - Template auto reloading will honor the ``run`` command's ``debug`` flag even
   if ``app.jinja_env`` was already accessed. (`#2373`_)
 
@@ -104,6 +105,7 @@ Major release, unreleased
 .. _#2354: https://github.com/pallets/flask/pull/2354
 .. _#2358: https://github.com/pallets/flask/pull/2358
 .. _#2362: https://github.com/pallets/flask/pull/2362
+.. _#2374: https://github.com/pallets/flask/pull/2374
 .. _#2373: https://github.com/pallets/flask/pull/2373
 
 Version 0.12.2
diff --git a/flask/json/__init__.py b/flask/json/__init__.py
index 93e6fdc46c..6559c1aa74 100644
--- a/flask/json/__init__.py
+++ b/flask/json/__init__.py
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 import io
 import uuid
-from datetime import date
+from datetime import date, datetime
 from flask.globals import current_app, request
 from flask._compat import text_type, PY2
 
@@ -62,6 +62,8 @@ def default(self, o):
                     return list(iterable)
                 return JSONEncoder.default(self, o)
         """
+        if isinstance(o, datetime):
+            return http_date(o.utctimetuple())
         if isinstance(o, date):
             return http_date(o.timetuple())
         if isinstance(o, uuid.UUID):
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index c66e650b68..73c6bee728 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -34,6 +34,27 @@ def has_encoding(name):
         return False
 
 
+class FixedOffset(datetime.tzinfo):
+    """Fixed offset in hours east from UTC.
+
+    This is a slight adaptation of the ``FixedOffset`` example found in
+    https://docs.python.org/2.7/library/datetime.html.
+    """
+
+    def __init__(self, hours, name):
+        self.__offset = datetime.timedelta(hours=hours)
+        self.__name = name
+
+    def utcoffset(self, dt):
+        return self.__offset
+
+    def tzname(self, dt):
+        return self.__name
+
+    def dst(self, dt):
+        return datetime.timedelta()
+
+
 class TestJSON(object):
     def test_ignore_cached_json(self, app):
         with app.test_request_context('/', method='POST', data='malformed',
@@ -177,6 +198,15 @@ def test_jsonify_date_types(self, app, client):
             assert rv.mimetype == 'application/json'
             assert flask.json.loads(rv.data)['x'] == http_date(d.timetuple())
 
+    @pytest.mark.parametrize('tz', (('UTC', 0), ('PST', -8), ('KST', 9)))
+    def test_jsonify_aware_datetimes(self, tz):
+        """Test if aware datetime.datetime objects are converted into GMT."""
+        tzinfo = FixedOffset(hours=tz[1], name=tz[0])
+        dt = datetime.datetime(2017, 1, 1, 12, 34, 56, tzinfo=tzinfo)
+        gmt = FixedOffset(hours=0, name='GMT')
+        expected = dt.astimezone(gmt).strftime('"%a, %d %b %Y %H:%M:%S %Z"')
+        assert flask.json.JSONEncoder().encode(dt) == expected
+
     def test_jsonify_uuid_types(self, app, client):
         """Test jsonify with uuid.UUID types"""
 

http_date() requires timetuple in UTC, but JSONEncoder.default() was
passing a local timetuple instead.

This fixes #2372.

<!--
Commit checklist:

* add tests that fail without the patch
* ensure all tests pass with ``pytest``
* add documentation to the relevant docstrings or pages
* add ``versionadded`` or ``versionchanged`` directives to relevant docstrings
* add a changelog entry if this patch changes code

Tests, coverage, and docs will be run automatically when you submit the pull
request, but running them yourself can save time.
-->
