Repository: marshmallow-code/apispec

Issue:
TimeDelta fields not generating useful documentation
Documentation for `marshmallow.fields.TimeDelta` is not capturing anything about the field type except the description. Under the hood, Marshmallow treats it as an `int` that gets converted to a `datetime.timedelta` on deserialization.

Here is the line from the schema declaration:
```py
    uptime = fields.TimeDelta(precision="minutes", required=False, metadata={"description":"running time"})
```

Example output of "uptime" field (type TimeDelta) from openapi.json generated by flask-smorest:
```json
{
  "uptime": {
    "description": "running time"
  }
}
```

And here is a screen grab of the Swagger UI:
![Capture](https://user-images.githubusercontent.com/6279092/105771793-21cb3800-5f2f-11eb-8b77-e5ad9a2f03ae.PNG)

I think it should be treated as an number, with the precision string spelled out in the documentation. Is there a way to add this?

Comments:

Patch:
diff --git a/src/apispec/ext/marshmallow/field_converter.py b/src/apispec/ext/marshmallow/field_converter.py
index b1e4de8..470ed07 100644
--- a/src/apispec/ext/marshmallow/field_converter.py
+++ b/src/apispec/ext/marshmallow/field_converter.py
@@ -29,6 +29,7 @@ DEFAULT_FIELD_MAPPING = {
     marshmallow.fields.DateTime: ("string", "date-time"),
     marshmallow.fields.Date: ("string", "date"),
     marshmallow.fields.Time: ("string", None),
+    marshmallow.fields.TimeDelta: ("integer", None),
     marshmallow.fields.Email: ("string", "email"),
     marshmallow.fields.URL: ("string", "url"),
     marshmallow.fields.Dict: ("object", None),
@@ -99,6 +100,7 @@ class FieldConverterMixin:
             self.pluck2properties,
             self.list2properties,
             self.dict2properties,
+            self.timedelta2properties,
         ]
 
     def map_to_openapi_type(self, *args):
@@ -459,6 +461,19 @@ class FieldConverterMixin:
                 ret["additionalProperties"] = self.field2property(value_field)
         return ret
 
+    def timedelta2properties(self, field, **kwargs):
+        """Return a dictionary of properties from :class:`TimeDelta <marshmallow.fields.TimeDelta>` fields.
+
+        Adds a `x-unit` vendor property based on the field's `precision` attribute
+
+        :param Field field: A marshmallow field.
+        :rtype: dict
+        """
+        ret = {}
+        if isinstance(field, marshmallow.fields.TimeDelta):
+            ret["x-unit"] = field.precision
+        return ret
+
 
 def make_type_list(types):
     """Return a list of types from a type attribute


Test patch:
diff --git a/tests/test_ext_marshmallow.py b/tests/test_ext_marshmallow.py
index 162a4b6..7833484 100644
--- a/tests/test_ext_marshmallow.py
+++ b/tests/test_ext_marshmallow.py
@@ -2,7 +2,7 @@ import json
 
 import pytest
 
-from marshmallow.fields import Field, DateTime, Dict, String, Nested, List
+from marshmallow.fields import Field, DateTime, Dict, String, Nested, List, TimeDelta
 from marshmallow import Schema
 
 from apispec import APISpec
@@ -1197,3 +1197,21 @@ class TestList:
 
         result = get_schemas(spec)["SchemaWithList"]["properties"]["list_field"]
         assert result == {"items": build_ref(spec, "schema", "Pet"), "type": "array"}
+
+
+class TestTimeDelta:
+    def test_timedelta_x_unit(self, spec):
+        class SchemaWithTimeDelta(Schema):
+            sec = TimeDelta("seconds")
+            day = TimeDelta("days")
+
+        spec.components.schema("SchemaWithTimeDelta", schema=SchemaWithTimeDelta)
+
+        assert (
+            get_schemas(spec)["SchemaWithTimeDelta"]["properties"]["sec"]["x-unit"]
+            == "seconds"
+        )
+        assert (
+            get_schemas(spec)["SchemaWithTimeDelta"]["properties"]["day"]["x-unit"]
+            == "days"
+        )
diff --git a/tests/test_ext_marshmallow_field.py b/tests/test_ext_marshmallow_field.py
index 084b343..d2a33a7 100644
--- a/tests/test_ext_marshmallow_field.py
+++ b/tests/test_ext_marshmallow_field.py
@@ -28,6 +28,7 @@ def test_field2choices_preserving_order(openapi):
         (fields.DateTime, "string"),
         (fields.Date, "string"),
         (fields.Time, "string"),
+        (fields.TimeDelta, "integer"),
         (fields.Email, "string"),
         (fields.URL, "string"),
         # Custom fields inherit types from their parents