]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Centralize formatting of HTTP-style dates.
authorBen Darnell <ben@bendarnell.com>
Sun, 27 Jan 2013 19:01:59 +0000 (14:01 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 27 Jan 2013 19:01:59 +0000 (14:01 -0500)
Use time.strftime, which turns out to be a bit faster than either
datetime.strftime or email.utils.formatdate.

tornado/httpclient.py
tornado/httputil.py
tornado/test/httputil_test.py
tornado/web.py

index 6fecdebbec9d82c42c0146c12d75b5cf8cd3988f..73a88af16271487f4af50400c05557196362664a 100644 (file)
@@ -31,8 +31,6 @@ supported version is 7.18.2, and the recommended version is 7.21.1 or newer.
 
 from __future__ import absolute_import, division, print_function, with_statement
 
-import calendar
-import email.utils
 import time
 import weakref
 
@@ -279,9 +277,8 @@ class HTTPRequest(object):
         if headers is None:
             headers = httputil.HTTPHeaders()
         if if_modified_since:
-            timestamp = calendar.timegm(if_modified_since.utctimetuple())
-            headers["If-Modified-Since"] = email.utils.formatdate(
-                timestamp, localtime=False, usegmt=True)
+            headers["If-Modified-Since"] = httputil.format_timestamp(
+                if_modified_since)
         self.proxy_host = proxy_host
         self.proxy_port = proxy_port
         self.proxy_username = proxy_username
index cd0aec7f7677328956b79c0a099a859b13f6db8a..163e303559ad13bbc1e0e871ae46b0e00dfddb9e 100644 (file)
 
 from __future__ import absolute_import, division, print_function, with_statement
 
+import datetime
+import numbers
 import re
+import time
 
 from tornado.escape import native_str, parse_qs_bytes, utf8
 from tornado.log import gen_log
@@ -288,6 +291,26 @@ def parse_multipart_form_data(boundary, data, arguments, files):
             arguments.setdefault(name, []).append(value)
 
 
+def format_timestamp(ts):
+    """Formats a timestamp in the format used by HTTP.
+
+    The argument may be a numeric timestamp as returned by `time.time()`,
+    a time tuple as returned by `time.gmtime()`, or a `datetime.datetime`
+    object.
+
+    >>> format_timestamp(1359312200)
+    'Sun, 27 Jan 2013 18:43:20 GMT'
+    """
+    if isinstance(ts, (tuple, time.struct_time)):
+        pass
+    elif isinstance(ts, datetime.datetime):
+        ts = ts.utctimetuple()
+    elif isinstance(ts, numbers.Real):
+        ts = time.gmtime(ts)
+    else:
+        raise TypeError("unknown timestamp type: %r" % ts)
+    return time.strftime("%a, %d %b %Y %H:%M:%S GMT", ts)
+
 # _parseparam and _parse_header are copied and modified from python2.7's cgi.py
 # The original 2.7 version of this code did not correctly support some
 # combinations of semicolons and double quotes.
index 026da5ee35171c0e01318bc6e43cd50e8ab20a0c..1e84da76f0b8c74c61d7fee6bebfbd376894c468 100644 (file)
@@ -2,12 +2,15 @@
 
 
 from __future__ import absolute_import, division, print_function, with_statement
-from tornado.httputil import url_concat, parse_multipart_form_data, HTTPHeaders
+from tornado.httputil import url_concat, parse_multipart_form_data, HTTPHeaders, format_timestamp
 from tornado.escape import utf8
 from tornado.log import gen_log
 from tornado.testing import ExpectLog
 from tornado.test.util import unittest
+
+import datetime
 import logging
+import time
 
 
 class TestUrlConcat(unittest.TestCase):
@@ -224,3 +227,29 @@ Foo: even
                          [("Asdf", "qwer zxcv"),
                           ("Foo", "bar baz"),
                           ("Foo", "even more lines")])
+
+
+class FormatTimestampTest(unittest.TestCase):
+    # Make sure that all the input types are supported.
+    TIMESTAMP = 1359312200.503611
+    EXPECTED = 'Sun, 27 Jan 2013 18:43:20 GMT'
+
+    def check(self, value):
+        self.assertEqual(format_timestamp(value), self.EXPECTED)
+
+    def test_unix_time_float(self):
+        self.check(self.TIMESTAMP)
+
+    def test_unix_time_int(self):
+        self.check(int(self.TIMESTAMP))
+
+    def test_struct_time(self):
+        self.check(time.gmtime(self.TIMESTAMP))
+
+    def test_time_tuple(self):
+        tup = tuple(time.gmtime(self.TIMESTAMP))
+        self.assertEqual(9, len(tup))
+        self.check(tup)
+
+    def test_datetime(self):
+        self.check(datetime.datetime.utcfromtimestamp(self.TIMESTAMP))
index 306c85eaefb2a37ab54c9e05b7aecf47088d6054..35f4e09824a679c6e24efd43e0e7364874b7f996 100644 (file)
@@ -53,14 +53,12 @@ from __future__ import absolute_import, division, print_function, with_statement
 
 import base64
 import binascii
-import calendar
 import datetime
 import email.utils
 import functools
 import gzip
 import hashlib
 import hmac
-import itertools
 import mimetypes
 import numbers
 import os.path
@@ -231,8 +229,7 @@ class RequestHandler(object):
         self._headers = httputil.HTTPHeaders({
                 "Server": "TornadoServer/%s" % tornado.version,
                 "Content-Type": "text/html; charset=UTF-8",
-                "Date": datetime.datetime.utcnow().strftime(
-                    "%a, %d %b %Y %H:%M:%S GMT"),
+                "Date": httputil.format_timestamp(time.gmtime()),
                 })
         self.set_default_headers()
         if not self.request.supports_http_1_1():
@@ -308,8 +305,7 @@ class RequestHandler(object):
             # return immediately since we know the converted value will be safe
             return str(value)
         elif isinstance(value, datetime.datetime):
-            t = calendar.timegm(value.utctimetuple())
-            return email.utils.formatdate(t, localtime=False, usegmt=True)
+            return httputil.format_timestamp(value)
         else:
             raise TypeError("Unsupported header value %r" % value)
         # If \n is allowed into the header, it is possible to inject
@@ -410,9 +406,7 @@ class RequestHandler(object):
             expires = datetime.datetime.utcnow() + datetime.timedelta(
                 days=expires_days)
         if expires:
-            timestamp = calendar.timegm(expires.utctimetuple())
-            morsel["expires"] = email.utils.formatdate(
-                timestamp, localtime=False, usegmt=True)
+            morsel["expires"] = httputil.format_timestamp(expires)
         if path:
             morsel["path"] = path
         for k, v in kwargs.items():