diff --git a/lib/hawat/blueprints/events/__init__.py b/lib/hawat/blueprints/events/__init__.py
index 7c60d29ab9d5384e2d00befb08929e0ece92b6f7..ceb57f672c5cc5a5f681b4385aac042614d88f35 100644
--- a/lib/hawat/blueprints/events/__init__.py
+++ b/lib/hawat/blueprints/events/__init__.py
@@ -18,6 +18,7 @@ __author__ = "Jan Mach <jan.mach@cesnet.cz>"
 __credits__ = "Pavel Kácha <pavel.kacha@cesnet.cz>, Andrea Kropáčová <andrea.kropacova@cesnet.cz>"
 
 import datetime
+import pytz
 
 import flask
 from flask_babel import lazy_gettext
@@ -349,7 +350,8 @@ class AbstractDashboardView(SQLAlchemyMixin, BaseSearchView):  # pylint: disable
                     dt_from=dt_from,
                     dt_to=dt_to,
                     max_count=flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS'],
-                    min_step=300
+                    min_step=300,
+                    timezone=pytz.timezone(flask.session.get('timezone', 'UTC'))
                 )
             )
 
diff --git a/lib/hawat/blueprints/hosts/__init__.py b/lib/hawat/blueprints/hosts/__init__.py
index a25b9346f7338be9ace02c3b699f0454882612a4..0f1d65e913fdcf78db0deade9ff86cdb3effdd24 100644
--- a/lib/hawat/blueprints/hosts/__init__.py
+++ b/lib/hawat/blueprints/hosts/__init__.py
@@ -96,7 +96,8 @@ class AbstractSearchView(PsycopgMixin, BaseSearchView):  # pylint: disable=local
                     items,
                     dt_from=dt_from,
                     dt_to=dt_to,
-                    max_count=flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS']
+                    max_count=flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS'],
+                    timezone=pytz.timezone(flask.session.get('timezone', 'UTC'))
                 )
             )
             self.response_context.pop('items', None)
diff --git a/lib/hawat/blueprints/timeline/__init__.py b/lib/hawat/blueprints/timeline/__init__.py
index 6bf2701d98216a47118b7507700f40b4fcca1872..3688f8d2194ca7b2deb4d2c3bb26799c97f1665a 100644
--- a/lib/hawat/blueprints/timeline/__init__.py
+++ b/lib/hawat/blueprints/timeline/__init__.py
@@ -147,13 +147,15 @@ class AbstractSearchView(PsycopgMixin, CustomSearchView):  # pylint: disable=loc
         timeline_cfg = mentat.stats.idea.calculate_timeline_config(
             form_data['dt_from'],
             form_data['dt_to'],
-            flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS']
+            flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS'],
+            timezone=pytz.timezone(flask.session.get('timezone', 'UTC'))
         )
         bucket_list = self.get_db().query_direct(
-            'SELECT generate_series(%s, %s, %s) AS bucket ORDER BY bucket',
+            '(SELECT %s AS bucket) UNION (SELECT generate_series(%s, %s, %s) AS bucket) ORDER BY bucket',
             None,
             [
                 timeline_cfg['dt_from'],
+                timeline_cfg['first_step'],
                 timeline_cfg['dt_to'],
                 timeline_cfg['step']
             ]
@@ -168,6 +170,7 @@ class AbstractSearchView(PsycopgMixin, CustomSearchView):  # pylint: disable=loc
 
         # Put calculated parameters together with other search form parameters.
         form_data['dt_from'] = timeline_cfg['dt_from']
+        form_data['first_step'] = timeline_cfg['first_step']
         form_data['dt_to'] = timeline_cfg['dt_to']
         form_data['step'] = timeline_cfg['step']
 
@@ -423,7 +426,8 @@ class AbstractLegacySearchView(PsycopgMixin, BaseSearchView):  # pylint: disable
                     items,
                     dt_from=dt_from,
                     dt_to=dt_to,
-                    max_count=flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS']
+                    max_count=flask.current_app.config['HAWAT_CHART_TIMELINE_MAXSTEPS'],
+                    timezone=pytz.timezone(flask.session.get('timezone', 'UTC'))
                 )
             )
             self.response_context.pop('items', None)
diff --git a/lib/mentat/services/eventstorage.py b/lib/mentat/services/eventstorage.py
index 5a626aef4cd51e8e2b4aad173ec3cb9103656771..6027c356e45d446db5f5908f36c5536bc6d02f5f 100644
--- a/lib/mentat/services/eventstorage.py
+++ b/lib/mentat/services/eventstorage.py
@@ -275,10 +275,11 @@ def _bq_qbase_aggregate(parameters = None, qname = None, query = None, params =
 
 def _bq_qbase_timeline(parameters = None, qname = None):  # pylint: disable=locally-disabled,unused-argument
     params = []
-    query = psycopg2.sql.SQL('SELECT %s + %s * (width_bucket(detecttime, (SELECT array_agg(buckets) FROM generate_series(%s, %s, %s) AS buckets)) - 1) AS bucket,')
+    query = psycopg2.sql.SQL('SELECT GREATEST(%s, %s + %s * (width_bucket(detecttime, (SELECT array_agg(buckets) FROM generate_series(%s, %s, %s) AS buckets)) - 1)) AS bucket,')
     params.append(parameters['dt_from'])
+    params.append(parameters['first_step'])
     params.append(parameters['step'])
-    params.append(parameters['dt_from'])
+    params.append(parameters['first_step'])
     params.append(parameters['dt_to'])
     params.append(parameters['step'])
     return _bq_qbase_aggregate(parameters, qname, query, params)
diff --git a/lib/mentat/services/test_eventstorage.py b/lib/mentat/services/test_eventstorage.py
index f16e9d9aba68ea9a6e48fff981d08e314d7c901b..b4f6db73d3840e9c11a1c381705753e79fc16800 100644
--- a/lib/mentat/services/test_eventstorage.py
+++ b/lib/mentat/services/test_eventstorage.py
@@ -912,36 +912,39 @@ class TestMentatStorage(unittest.TestCase):
                 {
                     'parameters': {
                         'dt_from': datetime.datetime(2012, 11, 3, 10, 0, 7),
+                        'first_step': datetime.datetime(2012, 11, 3),
                         'dt_to': datetime.datetime(2012, 12, 3, 10, 0, 7),
                         'step': datetime.timedelta(days = 1),
                     },
                     'qtype': 'timeline'
                 },
-                b'SELECT \'2012-11-03T10:00:07\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime,(SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T10:00:07\'::timestamp,\'2012-12-03T10:00:07\'::timestamp,\'1 days 0.000000 seconds\'::interval) AS buckets)) - 1) AS bucket,COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket ORDER BY bucket ASC'
+                b'SELECT GREATEST(\'2012-11-03T10:00:07\'::timestamp, \'2012-11-03T00:00:00\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime, (SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T00:00:00\'::timestamp, \'2012-12-03T10:00:07\'::timestamp, \'1 days 0.000000 seconds\'::interval) AS buckets)) - 1)) AS bucket, COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket ORDER BY bucket ASC'
             ),
             (
                 {
                     'parameters': {
                         'dt_from': datetime.datetime(2012, 11, 3, 10, 0, 7),
+                        'first_step': datetime.datetime(2012, 11, 3),
                         'dt_to': datetime.datetime(2012, 12, 3, 10, 0, 7),
                         'step': datetime.timedelta(days = 1),
                         'aggr_set': 'eventclass',
                     },
                     'qtype': 'timeline'
                 },
-                b'SELECT \'2012-11-03T10:00:07\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime,(SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T10:00:07\'::timestamp,\'2012-12-03T10:00:07\'::timestamp,\'1 days 0.000000 seconds\'::interval) AS buckets)) - 1) AS bucket,"eventclass" AS set,COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket, set ORDER BY bucket ASC'
+                b'SELECT GREATEST(\'2012-11-03T10:00:07\'::timestamp, \'2012-11-03T00:00:00\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime, (SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T00:00:00\'::timestamp, \'2012-12-03T10:00:07\'::timestamp, \'1 days 0.000000 seconds\'::interval) AS buckets)) - 1)) AS bucket, "eventclass" AS set,COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket, set ORDER BY bucket ASC'
             ),
             (
                 {
                     'parameters': {
                         'dt_from': datetime.datetime(2012, 11, 3, 10, 0, 7),
+                        'first_step': datetime.datetime(2012, 11, 3),
                         'dt_to': datetime.datetime(2012, 12, 3, 10, 0, 7),
                         'step': datetime.timedelta(days = 1),
                         'aggr_set': 'category',
                     },
                     'qtype': 'timeline'
                 },
-                b'SELECT \'2012-11-03T10:00:07\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime,(SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T10:00:07\'::timestamp,\'2012-12-03T10:00:07\'::timestamp,\'1 days 0.000000 seconds\'::interval) AS buckets)) - 1) AS bucket,unnest("category") AS set,COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket, set ORDER BY bucket ASC'
+                b'SELECT GREATEST(\'2012-11-03T10:00:07\'::timestamp, \'2012-11-03T00:00:00\'::timestamp + \'1 days 0.000000 seconds\'::interval * (width_bucket(detecttime, (SELECT array_agg(buckets) FROM generate_series(\'2012-11-03T00:00:00\'::timestamp, \'2012-12-03T10:00:07\'::timestamp, \'1 days 0.000000 seconds\'::interval) AS buckets)) - 1)) AS bucket, unnest("category") AS set, COUNT(*) FROM events WHERE "detecttime" >= \'2012-11-03T10:00:07\'::timestamp AND "detecttime" <= \'2012-12-03T10:00:07\'::timestamp GROUP BY bucket, set ORDER BY bucket ASC'
             ),
         ]
 
diff --git a/lib/mentat/stats/idea.py b/lib/mentat/stats/idea.py
index de25327b06a0acd6e4d12f39850a81d90bba7b7b..0571de004de04221b7142f2ae5c124ad4d13ece0 100644
--- a/lib/mentat/stats/idea.py
+++ b/lib/mentat/stats/idea.py
@@ -151,12 +151,15 @@ LIST_CALCSTAT_KEYS = tuple(
 """List of subkey names of all calculated statistics."""
 
 
-LIST_OPTIMAL_STEPS = (
-    1,         2,         3,         4,         5,         6,         10,         12,         15,        20,    30,     # seconds
-    1*60,      2*60,      3*60,      4*60,      5*60,      6*60,      10*60,      12*60,      15*60,     20*60, 30*60,  # minutes
-    1*3600,    2*3600,    3*3600,    4*3600,    6*3600,    8*3600,    12*3600,                                          # hours
-    1*24*3600, 2*24*3600, 3*24*3600, 4*24*3600, 5*24*3600, 6*24*3600,  7*24*3600, 10*24*3600, 14*24*3600                # days
-)
+LIST_OPTIMAL_STEPS = [
+    datetime.timedelta(seconds=x) for x in
+    (
+        1,         2,         3,         4,         5,         6,         10,        12,         15,        20,    30,     # seconds
+        1*60,      2*60,      3*60,      4*60,      5*60,      6*60,      10*60,     12*60,      15*60,     20*60, 30*60,  # minutes
+        1*3600,    2*3600,    3*3600,    4*3600,    6*3600,    8*3600,    12*3600,                                         # hours
+        1*24*3600, 2*24*3600, 3*24*3600, 4*24*3600, 5*24*3600, 6*24*3600, 7*24*3600, 10*24*3600, 14*24*3600                # days
+    )
+]
 """List of optimal timeline steps. This list is populated with values, that round nicelly in time calculations."""
 
 
@@ -275,7 +278,7 @@ def evaluate_events(events, stats = None):
     return stats
 
 
-def evaluate_timeline_events(events, dt_from, dt_to, max_count, stats = None):
+def evaluate_timeline_events(events, dt_from, dt_to, max_count, timezone = None, stats = None):
     """
     Evaluate statistics for given list of IDEA events and produce statistical
     record for timeline visualisations.
@@ -301,7 +304,7 @@ def evaluate_timeline_events(events, dt_from, dt_to, max_count, stats = None):
 
     # Prepare structure for storing IDEA event timeline statistics.
     if ST_SKEY_TIMELINE not in stats:
-        stats[ST_SKEY_TIMELINE], stats[ST_SKEY_TLCFG] = _init_timeline(dt_from, dt_to, max_count)
+        stats[ST_SKEY_TIMELINE], stats[ST_SKEY_TLCFG] = _init_timeline(dt_from, dt_to, max_count, timezone=timezone)
 
     # Prepare event thresholding cache for detection of recurring events.
     tcache = SimpleMemoryThresholdingCache()
@@ -336,7 +339,7 @@ def evaluate_timeline_events(events, dt_from, dt_to, max_count, stats = None):
     return stats
 
 
-def evaluate_singlehost_events(host, events, dt_from, dt_to, max_count, stats = None):
+def evaluate_singlehost_events(host, events, dt_from, dt_to, max_count, timezone = None, stats = None):
     """
     Evaluate statistics for given list of IDEA events and produce statistical
     record for single host visualisations.
@@ -363,7 +366,7 @@ def evaluate_singlehost_events(host, events, dt_from, dt_to, max_count, stats =
 
     # Prepare structure for storing IDEA event timeline statistics.
     if ST_SKEY_TIMELINE not in stats:
-        stats[ST_SKEY_TIMELINE], stats[ST_SKEY_TLCFG] = _init_timeline(dt_from, dt_to, max_count)
+        stats[ST_SKEY_TIMELINE], stats[ST_SKEY_TLCFG] = _init_timeline(dt_from, dt_to, max_count, timezone=timezone)
 
     # Prepare event thresholding cache for detection of recurring events.
     tcache = SingleSourceThresholdingCache(host)
@@ -534,7 +537,7 @@ def evaluate_dbstats_events(stats):
 
 def group_events(events):
     """
-    Group events according to the presence of the ``_Mentat.ResolvedAbuses`` (or 
+    Group events according to the presence of the ``_Mentat.ResolvedAbuses`` (or
     ``_CESNET.ResolvedAbuses``) key.
     Each event will be added to group ``overall`` and then to either ``internal``,
     or ``external`` based on the presence of the key mentioned above.
@@ -620,7 +623,7 @@ def aggregate_stat_groups(stats_list, result = None):
     return result
 
 
-def aggregate_timeline_groups(stats_list, dt_from, dt_to, max_count, min_step = None, result = None):
+def aggregate_timeline_groups(stats_list, dt_from, dt_to, max_count, min_step = None, timezone = None, result = None):
     """
     Aggregate multiple full statistical records produced by the
     :py:func:`mentat.stats.idea.evaluate_event_groups` function and later retrieved
@@ -663,7 +666,8 @@ def aggregate_timeline_groups(stats_list, dt_from, dt_to, max_count, min_step =
                 dt_from,
                 dt_to,
                 max_count,
-                min_step
+                min_step,
+                timezone
             )
 
         # Precalculate list of timeline keys for subsequent bisection search.
@@ -703,16 +707,16 @@ def aggregate_timeline_groups(stats_list, dt_from, dt_to, max_count, min_step =
     return result
 
 
-def calculate_timeline_config(dt_from, dt_to, max_count, min_step = None):
+def calculate_timeline_config(dt_from, dt_to, max_count, min_step = None, timezone = None):
     """
     Calculate optimal configurations for timeline chart dataset.
     """
-    dt_from, dt_to, delta = _calculate_timeline_boundaries(dt_from, dt_to)  # pylint: disable=locally-disabled,unused-variable
-    step, step_count = _calculate_timeline_steps(dt_from, dt_to, max_count, min_step)
+    first_step, step, step_count = _calculate_timeline_steps(dt_from, dt_to, max_count, min_step, timezone)
     return {
         ST_SKEY_DT_FROM: dt_from,
         ST_SKEY_DT_TO: dt_to,
         'step': step,
+        'first_step': first_step,
         'count': step_count
     }
 
@@ -1039,88 +1043,103 @@ def _mask_toplist(stats, mask, dict_key, top_threshold, force = False):
     return stats
 
 
-def _calculate_timeline_boundaries(dt_from, dt_to):
-    """
-    Calculate optimal and rounded values for lower and upper timeline boundaries
-    from given timestamps.
-    """
-    delta_minute = datetime.timedelta(minutes = 1)
-    delta_hour   = datetime.timedelta(hours = 1)
-    delta_day    = datetime.timedelta(days = 1)
+def _round_datetime(datetime_, round_to, timezone = None, direction = None):
+    if timezone is None or not hasattr(timezone, 'localize'):
+        epoch = datetime.datetime(1970, 1, 1, tzinfo=timezone or datetime.timezone.utc)
+    else:
+        epoch = timezone.localize(datetime.datetime(1970, 1, 1))
 
-    delta = dt_to - dt_from
+    mod = (datetime_.astimezone(datetime.timezone.utc) - epoch) % round_to
 
-    # For delta of timeline boundaries below one hour round to the whole 5 minutes.
-    if delta <= delta_hour:
-        return (
-            dt_from.replace(
-                minute = dt_from.minute - (dt_from.minute % 5),
-                second = 0,
-                microsecond = 0
-            ),
-            dt_to.replace(
-                second = 0,
-                microsecond = 0
-            ) + (delta_minute * (5 - (dt_to.minute % 5))),
-            delta_minute * 5
-        )
+    if not mod:
+        return datetime_
 
-    # For delta of timeline boundaries below one day round to the whole hours.
-    if delta <= delta_day:
-        return (
-            dt_from.replace(
-                minute = 0,
-                second = 0,
-                microsecond = 0
-            ),
-            dt_to.replace(
-                minute = 0,
-                second = 0,
-                microsecond = 0
-            ) + delta_hour,
-            delta_hour
-        )
+    direction = direction or (mod < round_to / 2 and 'down') or 'up'
 
-    # Everything else round to the whole days.
-    return (
-        dt_from.replace(
-            hour = 0,
-            minute = 0,
-            second = 0,
-            microsecond = 0
-        ),
-        dt_to.replace(
-            hour = 0,
-            minute = 0,
-            second = 0,
-            microsecond = 0
-        ) + delta_day,
-        delta_day
+    if direction == 'up':
+        return datetime_ + (round_to - mod)
+    return datetime_ - mod
+
+
+def _round_timedelta(delta, round_to, direction = None):
+    directions = {
+        'up': math.ceil,
+        'down': math.floor,
+    }
+    round_to_seconds = round_to.total_seconds()
+    delta_seconds = delta.total_seconds()
+    return datetime.timedelta(
+        seconds=round_to_seconds * directions.get(direction, round)(delta_seconds/ round_to_seconds)
     )
 
 
-def _calculate_timeline_steps(dt_from, dt_to, max_count, min_step = None):
+def _optimize_step(step):
+    if step < LIST_OPTIMAL_STEPS[0]:
+        # Set the step size to lowest larger than step 1, 2, 5 or 10 times
+        # the largest smaller or equal than step negative power of 10
+        lower_bound = datetime.timedelta(
+            seconds=10 ** math.floor(math.log10(step.total_seconds()))
+        )
+        return lower_bound * next(
+            filter(lambda x: x * lower_bound >= step, (1, 2, 5, 10)),
+            10  # This value should not be reachable
+        )
+
+    if step <= LIST_OPTIMAL_STEPS[-1]:
+        # Set the step size to the nearest larger or equal size from LIST_OPTIMAL_STEPS
+        idx = bisect.bisect_left(LIST_OPTIMAL_STEPS, step)
+        return LIST_OPTIMAL_STEPS[idx]
+
+    # Otherwise round the step to whole days
+    delta_day = datetime.timedelta(days = 1)
+    return _round_timedelta(step, delta_day, direction='up')
+
+
+def _calculate_timeline_steps(dt_from, dt_to, max_count, min_step = None, timezone = None):
     """
-    Calculate optimal timeline step/window from given optimal lower and upper
-    boundary and maximal number of steps.
+    Calculates the first step, the step size and actual step count
+
+    Example for calculated step size of 5s where:
+        dt_from = YYYY-MM-DDThh:mm:02,
+        dt_to   = YYYY-MM-DDThh:mm:52,
+        max_count = 12
+
+       |---|-----|-----|-----|-----|-----|-----|-----|-----|-----|--|
+       │   ╰───────╮                                 ╰──┬──╯        │
+    dt_from    first_step (rounded up to nearest 5s)   step = 5s  dt_to
+
+    step_count = 11
     """
     delta = dt_to - dt_from
-    step_size = int(math.ceil(delta.total_seconds()/max_count))
 
-    if min_step and step_size < min_step:
-        step_size = min_step
+    if not min_step:
+        min_step_delta = datetime.timedelta(microseconds=1)
+    else:
+        min_step_delta = datetime.timedelta(seconds=min_step)
+
+    if delta <= datetime.timedelta(0):
+        return dt_from, min_step_delta, 0
 
-    # Attempt to optimalize the step size
-    idx = bisect.bisect_left(LIST_OPTIMAL_STEPS, step_size)
-    if idx != len(LIST_OPTIMAL_STEPS):
-        step_size = LIST_OPTIMAL_STEPS[idx]
+    step = max(delta / max_count, min_step_delta)
+    step = _optimize_step(step)
 
-    step = datetime.timedelta(seconds = step_size)
+    first_step = _round_datetime(dt_from, step, timezone=timezone, direction='up')
 
     # Calculate actual step count, that will cover the requested timeline.
-    step_count = int(math.ceil(delta/step))
+    step_count = math.ceil((dt_to - first_step) / step)
+
+    if step_count == max_count and first_step != dt_from:
+        # In case the step count would be higher than the max_count
+        # due to the shift of the first step, recalculate
+        step = max(delta / (max_count - 1), min_step_delta)
+        step = _optimize_step(step)
+        first_step = _round_datetime(dt_from, step, timezone=timezone, direction='up')
+        step_count = math.ceil((dt_to - first_step) / step)
+
+    if first_step != dt_from:
+        step_count += 1
 
-    return (step, step_count)
+    return first_step, step, step_count
 
 
 def _init_time_scatter():
@@ -1130,17 +1149,30 @@ def _init_time_scatter():
     return [[{} for y in range(24)] for x in range(7)]
 
 
-def _init_timeline(dt_from, dt_to, max_count, min_step = None):
+def _steps_iter(dt_from, first_step, step, count):
+    yield dt_from
+    count -= 1
+    if first_step != dt_from:
+        yield first_step
+        count -= 1
+    for _ in range(count):
+        first_step += step
+        yield first_step
+
+
+def _init_timeline(dt_from, dt_to, max_count, min_step = None, timezone = None):
     """
     Init structure for timeline chart dataset.
     """
-    timeline_cfg = calculate_timeline_config(dt_from, dt_to, max_count, min_step)
+    timeline_cfg = calculate_timeline_config(dt_from, dt_to, max_count, min_step, timezone)
+
+    timeline = [[s, {}] for s in _steps_iter(
+        timeline_cfg[ST_SKEY_DT_FROM],
+        timeline_cfg['first_step'],
+        timeline_cfg['step'],
+        timeline_cfg['count']
+    )]
 
-    dt_from = timeline_cfg[ST_SKEY_DT_FROM]
-    timeline = list()
-    for i in range(timeline_cfg['count']):  # pylint: disable=locally-disabled,unused-variable
-        timeline.append([dt_from, {}])
-        dt_from = dt_from + timeline_cfg['step']
     return timeline, timeline_cfg
 
 
diff --git a/lib/mentat/stats/test_idea.py b/lib/mentat/stats/test_idea.py
index acd99a02ab42c5f72fdb97dbcf2bfd8fc7389e47..b0892faeeab374677cfdc03201eab8dfca4850d5 100644
--- a/lib/mentat/stats/test_idea.py
+++ b/lib/mentat/stats/test_idea.py
@@ -10,7 +10,7 @@
 
 import unittest
 from pprint import pprint
-
+import pytz
 import datetime
 
 import mentat.stats.idea
@@ -301,58 +301,193 @@ class TestMentatStatsIdea(unittest.TestCase):
             }
         })
 
-    def test_03_timeline_boundaries(self):
+    def test_03_datetime_rounding(self):
+        """Test datetime rounding"""
+        self.maxDiff = None
+
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1),
+                direction='up'
+            ),
+            datetime.datetime(2022, 10, 11, 12, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1),
+                direction='down'
+            ),
+            datetime.datetime(2022, 10, 11, 11, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1)
+            ),
+            datetime.datetime(2022, 10, 11, 12, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(minutes=5),
+                direction='up'
+            ),
+            datetime.datetime(2022, 10, 11, 11, 35, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(minutes=5),
+                direction='down'
+            ),
+            datetime.datetime(2022, 10, 11, 11, 30, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(minutes=5)
+            ),
+            datetime.datetime(2022, 10, 11, 11, 30, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1),
+                timezone=pytz.timezone('Europe/Prague'),
+                direction='up'
+            ),
+            datetime.datetime(2022, 10, 11, 12, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1),
+                timezone=pytz.timezone('Europe/Prague'),
+                direction='down'
+            ),
+            datetime.datetime(2022, 10, 11, 11, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(hours=1),
+                timezone=pytz.timezone('Europe/Prague')
+            ),
+            datetime.datetime(2022, 10, 11, 12, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(days=1),
+                timezone=pytz.timezone('Europe/Prague'),
+                direction='up'
+            ),
+            datetime.datetime(2022, 10, 11, 23, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(days=1),
+                timezone=pytz.timezone('Europe/Prague'),
+                direction='down'
+            ),
+            datetime.datetime(2022, 10, 10, 23, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(days=1),
+                timezone=pytz.timezone('Europe/Prague')
+            ),
+            datetime.datetime(2022, 10, 11, 23, 0, 0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_datetime(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 11, 11, 32, 12),
+                datetime.timedelta(days=1)
+            ),
+            datetime.datetime(2022, 10, 11, 0, 0, 0)
+        )
+
+    def test_04_timedelta_rounding(self):
         """
-        Test timeline boundary calculations.
+        Test rounding of timedelta
         """
         self.maxDiff = None
 
         self.assertEqual(
-            mentat.stats.idea._calculate_timeline_boundaries(  # pylint: disable=locally-disabled,protected-access
-                datetime.datetime(2018, 1, 1, 1, 11, 1),
-                datetime.datetime(2018, 1, 1, 1, 31, 31)
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(seconds=42),
+                datetime.timedelta(seconds=10),
+                direction='up'
             ),
-            (
-                datetime.datetime(2018, 1, 1, 1, 10, 0),
-                datetime.datetime(2018, 1, 1, 1, 35, 0),
-                datetime.timedelta(seconds=300)
-            )
+            datetime.timedelta(seconds=50)
         )
         self.assertEqual(
-            mentat.stats.idea._calculate_timeline_boundaries(  # pylint: disable=locally-disabled,protected-access
-                datetime.datetime(2018, 1, 1, 1, 1, 1),
-                datetime.datetime(2018, 1, 1, 1, 59, 31)
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(seconds=42),
+                datetime.timedelta(seconds=10),
+                direction='down'
             ),
-            (
-                datetime.datetime(2018, 1, 1, 1, 0, 0),
-                datetime.datetime(2018, 1, 1, 2, 0, 0),
-                datetime.timedelta(seconds=300)
-            )
+            datetime.timedelta(seconds=40)
         )
         self.assertEqual(
-            mentat.stats.idea._calculate_timeline_boundaries(  # pylint: disable=locally-disabled,protected-access
-                datetime.datetime(2018, 1, 1, 1, 11, 1),
-                datetime.datetime(2018, 1, 1, 23, 59, 31)
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(seconds=42),
+                datetime.timedelta(seconds=10)
             ),
-            (
-                datetime.datetime(2018, 1, 1, 1, 0, 0),
-                datetime.datetime(2018, 1, 2, 0, 0, 0),
-                datetime.timedelta(seconds=3600)
-            )
+            datetime.timedelta(seconds=40)
         )
         self.assertEqual(
-            mentat.stats.idea._calculate_timeline_boundaries(  # pylint: disable=locally-disabled,protected-access
-                datetime.datetime(2018, 1, 1, 1, 11, 1),
-                datetime.datetime(2018, 1, 11, 23, 59, 31)
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(microseconds=500),
+                direction='up'
             ),
-            (
-                datetime.datetime(2018, 1, 1, 0, 0, 0),
-                datetime.datetime(2018, 1, 12, 0, 0, 0),
-                datetime.timedelta(seconds=86400)
-            )
+            datetime.timedelta(microseconds=687500)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(microseconds=500),
+                direction='down'
+            ),
+            datetime.timedelta(microseconds=687000)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(microseconds=500)
+            ),
+            datetime.timedelta(microseconds=687000)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(seconds=2),
+                direction='up'
+            ),
+            datetime.timedelta(seconds=2)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(seconds=2),
+                direction='down'
+            ),
+            datetime.timedelta(seconds=0)
+        )
+        self.assertEqual(
+            mentat.stats.idea._round_timedelta(  # pylint: disable=locally-disabled,protected-access
+                datetime.timedelta(microseconds=687231),
+                datetime.timedelta(seconds=2)
+            ),
+            datetime.timedelta(seconds=0)
         )
 
-    def test_04_timeline_steps(self):
+    def test_05_timeline_steps(self):
         """
         Test timeline step calculations.
         """
@@ -365,17 +500,130 @@ class TestMentatStatsIdea(unittest.TestCase):
                 100
             ),
             (
+                datetime.datetime(2018, 1, 1, 3, 0, 0),
                 datetime.timedelta(seconds=10800),
                 88
             )
         )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 22, 32),
+                42
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 10),
+                datetime.timedelta(seconds=10),
+                34
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 18, 23),
+                400
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.timedelta(microseconds=200000),
+                390
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 18, 23),
+                400,
+                1
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.timedelta(seconds=1),
+                78
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 18, 23),
+                400,
+                0.314159
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.timedelta(microseconds=500000),
+                156
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                200
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.timedelta(microseconds=1),
+                0
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                42,
+                1
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 5),
+                datetime.timedelta(seconds=1),
+                0
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 0),
+                datetime.datetime(2022, 10, 24, 15, 23, 40),
+                200
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 0),
+                datetime.timedelta(seconds=2),
+                200
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(  # pylint: disable=locally-disabled,protected-access
+                datetime.datetime(2022, 10, 24, 15, 17, 1),
+                datetime.datetime(2022, 10, 24, 15, 23, 41),
+                200
+            ),
+            (
+                datetime.datetime(2022, 10, 24, 15, 17, 3),
+                datetime.timedelta(seconds=3),
+                134
+            )
+        )
+        self.assertEqual(
+            mentat.stats.idea._calculate_timeline_steps(
+                datetime.datetime(2022, 6, 2, 2, 17, 1),
+                datetime.datetime(2022, 12, 9, 12, 28, 34),
+                200,
+                timezone=pytz.timezone('Canada/Newfoundland')
+            ),
+            (
+                datetime.datetime(2022, 6, 2, 3, 30, 0),
+                datetime.timedelta(days=1),
+                192
+            )
+        )
 
-    def test_05_calc_timeline_cfg(self):
+    def test_06_calc_timeline_cfg(self):
         """
         Test timeline config calculations.
         """
 
-    def test_06_evaluate_events(self):
+    def test_07_evaluate_events(self):
         """
         Perform the message evaluation tests.
         """
@@ -429,7 +677,7 @@ class TestMentatStatsIdea(unittest.TestCase):
             'severities': {'__unknown__': 6}
         })
 
-    def test_07_truncate_stats(self):
+    def test_08_truncate_stats(self):
         """
         Perform the basic operativity tests.
         """
@@ -488,7 +736,7 @@ class TestMentatStatsIdea(unittest.TestCase):
             }
         )
 
-    def test_08_group_events(self):
+    def test_09_group_events(self):
         """
         Perform the basic operativity tests.
         """
@@ -676,7 +924,7 @@ class TestMentatStatsIdea(unittest.TestCase):
             }
         )
 
-    def test_09_evaluate_event_groups(self):
+    def test_10_evaluate_event_groups(self):
         """
         Perform the basic operativity tests.
         """
@@ -692,7 +940,7 @@ class TestMentatStatsIdea(unittest.TestCase):
             pprint(result)
         self.assertTrue(result)
 
-    def test_10_merge_stats(self):
+    def test_11_merge_stats(self):
         """
         Perform the statistics aggregation tests.
         """
@@ -746,7 +994,7 @@ class TestMentatStatsIdea(unittest.TestCase):
             }
         )
 
-    def test_11_aggregate_stat_groups(self):
+    def test_12_aggregate_stat_groups(self):
         """
         Perform the statistic group aggregation tests.
         """