Repository: pallets/werkzeug

Issue:
Response.age mishandled as datetime instead of int (or timedelta)
Environment:

```
Flask==0.10.1
Werkzeug==0.9.1
```

Test case:

``` py
#/usr/bin/env python2.7
from flask import Flask, Response

app = Flask(__name__)

@app.route('/')
def root():
    resp = Response()
    resp.age = 42
    return resp

if __name__ == "__main__":
    client = app.test_client()
    resp = client.get('/')
    print "age property:", resp.age
    print "Age header:", resp.headers['Age']
```

Result:

```
age property: 1970-01-01 00:00:42
Age header: Thu, 01 Jan 1970 00:00:42 GMT
```

Expected `42` instead in both cases (though personally I'd optimally prefer `timedelta(seconds=42)` in the former case).
[The HTTP spec says (in part; emphasis added):](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html)

> The Age response-header field conveys the sender's estimate of the **amount of time** since the response (or its revalidation) was generated at the origin server.
> `age-value = delta-seconds`
> Age values are **non-negative decimal integers**, representing time in seconds.



Discussion:

Resolving patch:
diff --git a/werkzeug/http.py b/werkzeug/http.py
--- a/werkzeug/http.py
+++ b/werkzeug/http.py
@@ -781,6 +781,49 @@ def http_date(timestamp=None):
     return _dump_date(timestamp, ' ')
 
 
+def parse_age(value=None):
+    """Parses a base-10 integer count of seconds into a timedelta.
+
+    If parsing fails, the return value is `None`.
+
+    :param value: a string consisting of an integer represented in base-10
+    :return: a :class:`datetime.timedelta` object or `None`.
+    """
+    if not value:
+        return None
+    try:
+        seconds = int(value)
+    except ValueError:
+        return None
+    if seconds < 0:
+        return None
+    try:
+        return timedelta(seconds=seconds)
+    except OverflowError:
+        return None
+
+
+def dump_age(age=None):
+    """Formats the duration as a base-10 integer.
+
+    :param age: should be an integer number of seconds,
+                a :class:`datetime.timedelta` object, or,
+                if the age is unknown, `None` (default).
+    """
+    if age is None:
+        return
+    if isinstance(age, timedelta):
+        # do the equivalent of Python 2.7's timedelta.total_seconds(),
+        # but disregarding fractional seconds
+        age = age.seconds + (age.days * 24 * 3600)
+
+    age = int(age)
+    if age < 0:
+        raise ValueError('age cannot be negative')
+
+    return str(age)
+
+
 def is_resource_modified(environ, etag=None, data=None, last_modified=None,
                          ignore_if_range=True):
     """Convenience method for conditional requests.
diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py
--- a/werkzeug/wrappers.py
+++ b/werkzeug/wrappers.py
@@ -31,7 +31,8 @@
     parse_www_authenticate_header, remove_entity_headers, \
     parse_options_header, dump_options_header, http_date, \
     parse_if_range_header, parse_cookie, dump_cookie, \
-    parse_range_header, parse_content_range_header, dump_header
+    parse_range_header, parse_content_range_header, dump_header, \
+    parse_age, dump_age
 from werkzeug.urls import url_decode, iri_to_uri, url_join
 from werkzeug.formparser import FormDataParser, default_stream_factory
 from werkzeug.utils import cached_property, environ_property, \
@@ -1824,7 +1825,7 @@ def on_update(d):
         The Location response-header field is used to redirect the recipient
         to a location other than the Request-URI for completion of the request
         or identification of a new resource.''')
-    age = header_property('Age', None, parse_date, http_date, doc='''
+    age = header_property('Age', None, parse_age, dump_age, doc='''
         The Age response-header field conveys the sender's estimate of the
         amount of time since the response (or its revalidation) was
         generated at the origin server.


Test patch:
diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py
--- a/tests/test_wrappers.py
+++ b/tests/test_wrappers.py
@@ -14,7 +14,7 @@
 
 import pickle
 from io import BytesIO
-from datetime import datetime
+from datetime import datetime, timedelta
 from werkzeug._compat import iteritems
 
 from tests import strict_eq
@@ -696,11 +696,18 @@ def test_common_response_descriptors_mixin():
     response.content_length = '42'
     assert response.content_length == 42
 
-    for attr in 'date', 'age', 'expires':
+    for attr in 'date', 'expires':
         assert getattr(response, attr) is None
         setattr(response, attr, now)
         assert getattr(response, attr) == now
 
+    assert response.age is None
+    age_td = timedelta(days=1, minutes=3, seconds=5)
+    response.age = age_td
+    assert response.age == age_td
+    response.age = 42
+    assert response.age == timedelta(seconds=42)
+
     assert response.retry_after is None
     response.retry_after = now
     assert response.retry_after == now