Index: ow/tests/test_utilities.py
===================================================================
--- ow/tests/test_utilities.py	(revision d459ee209b465946444c8f6a6851142ccd7eb650)
+++ ow/tests/test_utilities.py	(revision d51700126233ed052536eeee3c85ff112e362720)
@@ -25,5 +25,6 @@
     save_map_screenshot,
     timedelta_to_hms,
-    get_week_days
+    get_week_days,
+    part_of_day
 )
 
@@ -235,4 +236,18 @@
             assert m[0] == m[1]
 
+    def test_part_of_day(self):
+        parts = [
+            ((5, 11), 'Morning'),
+            ((12, 17), 'Afternoon'),
+            ((18, 22), 'Evening'),
+            ((23, 4), 'Night'),
+        ]
+        for part in parts:
+            hours_range = range(*part[0])
+            expected = part[1]
+            for hour in hours_range:
+                dt = datetime(2019, 1, 15, hour, 0)
+                assert part_of_day(dt) == expected
+
 
 class TestGPXParseMinidom(object):
Index: ow/tests/views/test_user.py
===================================================================
--- ow/tests/views/test_user.py	(revision d459ee209b465946444c8f6a6851142ccd7eb650)
+++ ow/tests/views/test_user.py	(revision d51700126233ed052536eeee3c85ff112e362720)
@@ -447,5 +447,4 @@
         assert original_image.size == returned_image.size
 
-
     def test_edit_profile_get(self, dummy_request, john):
         """
Index: ow/tests/views/test_workout.py
===================================================================
--- ow/tests/views/test_workout.py	(revision d459ee209b465946444c8f6a6851142ccd7eb650)
+++ ow/tests/views/test_workout.py	(revision d51700126233ed052536eeee3c85ff112e362720)
@@ -126,9 +126,28 @@
         assert len(response['form'].form.errors) == 6
 
-    def test_add_workout_manually_post_valid(self, valid_post_request):
+    add_workout_params = [
+        # no title, no sport, we generate a title based on when the
+        # workout started
+        ({'title': None, 'sport': None}, 'Morning workout'),
+        # no title, sport given, we use the sport too in the automatically
+        # generated title
+        ({'title': None, 'sport': 'cycling'}, 'Morning cycling workout'),
+        # title given, no sport, we use the provided title
+        ({'title': 'Example workout', 'sport': None}, 'Example workout'),
+        # title given, sport too, we use the provided title
+        ({'title': 'Example workout', 'sport': 'cycling'}, 'Example workout'),
+    ]
+
+    @pytest.mark.parametrize(('params', 'expected'), add_workout_params)
+    def test_add_workout_manually_post_valid(self, params, expected,
+                                             valid_post_request):
         """
         POST request to add a workout manually, providing the needed data
         """
         request = valid_post_request
+        if params['title'] is not None:
+            request.POST['title'] = params['title']
+        if params['sport'] is not None:
+            request.POST['sport'] = params['sport']
         user = request.root['john']
         assert len(user.workouts()) == 1
@@ -137,4 +156,5 @@
         assert response.location.endswith('/2/')
         assert len(user.workouts()) == 2
+        assert user['2'].title == expected
 
     def test_add_workout_get(self, dummy_request):
Index: ow/utilities.py
===================================================================
--- ow/utilities.py	(revision d459ee209b465946444c8f6a6851142ccd7eb650)
+++ ow/utilities.py	(revision d51700126233ed052536eeee3c85ff112e362720)
@@ -13,4 +13,8 @@
 from ZODB.blob import Blob
 from splinter import Browser
+
+from pyramid.i18n import TranslationStringFactory
+
+_ = TranslationStringFactory('OpenWorkouts')
 
 
@@ -263,2 +267,18 @@
             return weeks.index(week)
     return None
+
+
+def part_of_day(dt):
+    """
+    Given a datetime object (dt), return which part of the day was it
+    (morning, afternoon, evening, night), translated in the proper
+    """
+    parts = {
+        _('Morning'): (5, 11),
+        _('Afternoon'): (12, 17),
+        _('Evening'): (18, 22),
+        _('Night'): (23, 4)
+    }
+    for key, value in parts.items():
+        if value[0] <= dt.hour <= value[1]:
+            return key
Index: ow/views/workout.py
===================================================================
--- ow/views/workout.py	(revision d459ee209b465946444c8f6a6851142ccd7eb650)
+++ ow/views/workout.py	(revision d51700126233ed052536eeee3c85ff112e362720)
@@ -10,4 +10,5 @@
 from pyramid_simpleform import Form
 from pyramid_simpleform.renderers import FormRenderer
+from pyramid.i18n import TranslationStringFactory
 
 from ..schemas.workout import (
@@ -18,6 +19,9 @@
 from ..models.workout import Workout
 from ..models.user import User
-from ..utilities import slugify, save_map_screenshot
+from ..utilities import slugify, save_map_screenshot, part_of_day
 from ..catalog import get_catalog, reindex_object, remove_from_catalog
+
+
+_ = TranslationStringFactory('OpenWorkouts')
 
 
@@ -51,4 +55,10 @@
                                  tzinfo=timezone.utc)
         workout.start = start
+        if not workout.title:
+            workout.title = part_of_day(start)
+            if workout.sport:
+                workout.title += ' ' + workout.sport
+            workout.title += ' ' + _('workout')
+
         context.add_workout(workout)
         return HTTPFound(location=request.resource_url(workout))
