From 65a83792615d6aafdd1f26494e0d083b028741fd Mon Sep 17 00:00:00 2001
From: Thor77 <thor77@thor77.org>
Date: Sat, 20 May 2017 00:31:26 +0200
Subject: [PATCH] Use pendulum instead of plain datetime

because it is more intuitive to use and doesn't require the
tz_aware_datetime-workaround.
---
 requirements.txt               |  1 +
 tsstats/log.py                 | 17 +++++++---------
 tsstats/template.py            | 10 ++++-----
 tsstats/tests/test_log.py      | 37 ++++++++++++++++++++++------------
 tsstats/tests/test_template.py |  4 ++--
 tsstats/utils.py               | 31 ----------------------------
 6 files changed, 38 insertions(+), 62 deletions(-)

diff --git a/requirements.txt b/requirements.txt
index f10e84e..a673ff4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,2 @@
 Jinja2>=2.8
+pendulum
diff --git a/tsstats/log.py b/tsstats/log.py
index 1a4ff80..ca28156 100644
--- a/tsstats/log.py
+++ b/tsstats/log.py
@@ -4,13 +4,13 @@ import logging
 import re
 from codecs import open
 from collections import namedtuple
-from datetime import datetime
 from glob import glob
 from os.path import basename
 from time import time
 
+import pendulum
+
 from tsstats.client import Client, Clients
-from tsstats.utils import tz_aware_datime
 
 re_log_filename = re.compile(r'ts3server_(?P<date>\d{4}-\d\d-\d\d)'
                              '__(?P<time>\d\d_\d\d_\d\d.\d+)_(?P<sid>\d).log')
@@ -23,8 +23,6 @@ re_disconnect_invoker = re.compile(
     r'invokername=(.*)\ invokeruid=(.*)\ reasonmsg'
 )
 
-log_timestamp_format = '%Y-%m-%d %H:%M:%S.%f'
-
 TimedLog = namedtuple('TimedLog', ['path', 'timestamp'])
 Server = namedtuple('Server', ['sid', 'clients'])
 
@@ -81,9 +79,9 @@ def _bundle_logs(logs):
         match = re_log_filename.match(basename(log))
         if match:
             match = match.groupdict()
-            timestamp = datetime.strptime('{0} {1}'.format(
-                match['date'], match['time'].replace('_', ':')),
-                log_timestamp_format)
+            timestamp = pendulum.parse('{0} {1}'.format(
+                match['date'], match['time'].replace('_', ':'))
+            )
             tl = TimedLog(log, timestamp)
             sid = match['sid']
             if sid in vserver_logfiles:
@@ -136,8 +134,7 @@ def _parse_details(log_path, ident_map=None, clients=None, online_dc=True):
             logger.debug('No match: "%s"', line)
             continue
         match = match.groupdict()
-        logdatetime = tz_aware_datime(datetime.strptime(match['timestamp'],
-                                      log_timestamp_format))
+        logdatetime = pendulum.parse(match['timestamp'])
         message = match['message']
         if message.startswith('client'):
             match = re_dis_connect.match(message)
@@ -178,7 +175,7 @@ def _parse_details(log_path, ident_map=None, clients=None, online_dc=True):
             ]
     if online_dc:
         def _reconnect(client):
-            client.disconnect(tz_aware_datime(datetime.utcnow()))
+            client.disconnect(pendulum.now())
             client.connected += 1
         [_reconnect(client) for client in clients if client.connected]
     logger.debug(
diff --git a/tsstats/template.py b/tsstats/template.py
index abe438f..fd290a4 100644
--- a/tsstats/template.py
+++ b/tsstats/template.py
@@ -2,14 +2,13 @@
 
 import logging
 from collections import namedtuple
-from datetime import datetime
 from os.path import dirname, join
 
+import pendulum
 from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PackageLoader
 
 from tsstats.log import Server
-from tsstats.utils import (filter_threshold, seconds_to_text, sort_clients,
-                           tz_aware_datime)
+from tsstats.utils import filter_threshold, seconds_to_text, sort_clients
 
 logger = logging.getLogger('tsstats')
 
@@ -43,8 +42,7 @@ def prepare_clients(clients, onlinetime_threshold=-1):
         clients, lambda c: c.onlinetime.total_seconds()
     )
     # filter clients not matching threshold
-    onlinetime_ = filter_threshold(onlinetime_,
-                                   onlinetime_threshold)
+    onlinetime_ = filter_threshold(onlinetime_, onlinetime_threshold)
     # convert timespans to text
     onlinetime = [
         (client, seconds_to_text(int(onlinetime)))
@@ -105,6 +103,6 @@ def render_servers(servers, output, title='TeamspeakStats',
     logger.debug('Rendering template %s', template)
     template.stream(title=title, servers=prepared_servers,
                     debug=logger.level <= logging.DEBUG,
-                    creation_time=tz_aware_datime(datetime.utcnow()))\
+                    creation_time=pendulum.utcnow())\
         .dump(output, encoding='utf-8')
     logger.debug('Wrote rendered template to %s', output)
diff --git a/tsstats/tests/test_log.py b/tsstats/tests/test_log.py
index 1ef16bd..785aee8 100644
--- a/tsstats/tests/test_log.py
+++ b/tsstats/tests/test_log.py
@@ -1,6 +1,4 @@
-from datetime import datetime, timedelta
-from time import sleep
-
+import pendulum
 import pytest
 
 from tsstats.exceptions import InvalidLog
@@ -20,8 +18,10 @@ def test_log_client_count(clients):
 
 
 def test_log_onlinetime(clients):
-    assert clients['1'].onlinetime == timedelta(0, 402, 149208)
-    assert clients['2'].onlinetime == timedelta(0, 19, 759644)
+    assert clients['1'].onlinetime == pendulum.Interval(
+        seconds=402, microseconds=149208)
+    assert clients['2'].onlinetime == pendulum.Interval(
+        seconds=19, microseconds=759644)
 
 
 def test_log_kicks(clients):
@@ -52,12 +52,20 @@ def test_log_pbans(clients):
         ],
         {
             '1': [
-                TimedLog('ts3server_2016-06-06__14_22_09.527229_1.log',
-                         datetime(year=2016, month=6, day=6, hour=14,
-                                  minute=22, second=9, microsecond=527229)),
-                TimedLog('ts3server_2017-07-07__15_23_10.638340_1.log',
-                         datetime(year=2017, month=7, day=7, hour=15,
-                                  minute=23, second=10, microsecond=638340))
+                TimedLog(
+                    'ts3server_2016-06-06__14_22_09.527229_1.log',
+                    pendulum.create(
+                        year=2016, month=6, day=6, hour=14, minute=22,
+                        second=9, microsecond=527229
+                    )
+                ),
+                TimedLog(
+                    'ts3server_2017-07-07__15_23_10.638340_1.log',
+                    pendulum.create(
+                        year=2017, month=7, day=7, hour=15, minute=23,
+                        second=10, microsecond=638340
+                    )
+                )
             ]
         }
     )
@@ -71,11 +79,14 @@ def test_log_invalid():
         _parse_details('tsstats/tests/res/test.log.broken')
 
 
-@pytest.mark.slowtest
 def test_log_client_online():
+    current_time = pendulum.now()
+
+    pendulum.set_test_now(current_time)
     clients = _parse_details(testlog_path)
     old_onlinetime = int(clients['1'].onlinetime.total_seconds())
-    sleep(2)
+
+    pendulum.set_test_now(current_time.add(seconds=2))  # add 2s to .now()
     clients = _parse_details(testlog_path)
     assert int(clients['1'].onlinetime.total_seconds()) == old_onlinetime + 2
 
diff --git a/tsstats/tests/test_template.py b/tsstats/tests/test_template.py
index f7422d3..d48c0c3 100644
--- a/tsstats/tests/test_template.py
+++ b/tsstats/tests/test_template.py
@@ -1,6 +1,6 @@
 import logging
-from datetime import timedelta
 
+import pendulum
 import pytest
 from bs4 import BeautifulSoup
 
@@ -44,7 +44,7 @@ def test_onlinetime(soup):
         onlinetime = onlinetime.text
         # find corresponding client-object
         client = list(filter(
-            lambda c: c.nick == nick and c.onlinetime > timedelta(0),
+            lambda c: c.nick == nick and c.onlinetime > pendulum.Interval(),
             clients
         ))
         # assert existence
diff --git a/tsstats/utils.py b/tsstats/utils.py
index 12b8459..62044d7 100644
--- a/tsstats/utils.py
+++ b/tsstats/utils.py
@@ -1,7 +1,4 @@
 # -*- coding: utf-8 -*-
-import datetime
-
-
 def sort_clients(clients, key_l):
     '''
     sort `clients` by `key`
@@ -52,34 +49,6 @@ def filter_threshold(clients, threshold):
     return list(filter(lambda c: c[1] > threshold, clients))
 
 
-class UTC(datetime.tzinfo):
-    '''
-    Reimplementation of `timezone.utc` for Python2-Compatibility
-    '''
-
-    def utcoffset(self, dt):
-        return datetime.timedelta(0)
-
-    def dst(self, dt):
-        return datetime.timedelta(0)
-
-    def tzname(self, dt):
-        return 'UTC'
-
-
-def tz_aware_datime(datetime, timezone=UTC()):
-    '''
-    Make `datetime` aware of it's timezone (UTC by default)
-
-    :param datetime: Target datetime
-    :param timezone: Target timezone
-
-    :type datetime: datetime.datetime
-    :type timezone: datetime.timezone
-    '''
-    return datetime.replace(tzinfo=timezone)
-
-
 def transform_pretty_identmap(pretty_identmap):
     '''
     Transforms a list of client ID mappings from a more descriptive format