Repository: gitpython-developers/GitPython
Pull request #414 (2016-04-19 12:32:17)

Add support for getting "aware" datetime info
This adds 2 properties to commits.  Their values are derived from the existing data stored on them, but this makes them more conveniently queryable:

- authored_datetime
- committed_datetime

These return "aware" datetimes, so they are effectively companions to their raw timestamp equivalents, respectively `authored_date` and `committed_date`.

These datetime instances are convenient structures since they show the author-local commit date and their UTC offset.

(I've implemented a tzinfo subclass here manually to avoid adding a dependency to dateutil or pytz here.)


Patch:
diff --git a/git/objects/commit.py b/git/objects/commit.py
index d067b718..dc722f97 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -21,7 +21,8 @@ from .util import (
     Serializable,
     parse_date,
     altz_to_utctz_str,
-    parse_actor_and_date
+    parse_actor_and_date,
+    from_timestamp,
 )
 from git.compat import text_type
 
@@ -144,6 +145,14 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
             super(Commit, self)._set_cache_(attr)
         # END handle attrs
 
+    @property
+    def authored_datetime(self):
+        return from_timestamp(self.authored_date, self.author_tz_offset)
+
+    @property
+    def committed_datetime(self):
+        return from_timestamp(self.committed_date, self.committer_tz_offset)
+
     @property
     def summary(self):
         """:return: First line of the commit message"""
diff --git a/git/objects/util.py b/git/objects/util.py
index 8fd92a0a..cbb9fe3c 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -15,10 +15,13 @@ from collections import deque as Deque
 from string import digits
 import time
 import calendar
+from datetime import datetime, timedelta, tzinfo
 
 __all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
            'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
-           'verify_utctz', 'Actor')
+           'verify_utctz', 'Actor', 'tzoffset', 'utc')
+
+ZERO = timedelta(0)
 
 #{ Functions
 
@@ -97,6 +100,31 @@ def verify_utctz(offset):
     return offset
 
 
+class tzoffset(tzinfo):
+    def __init__(self, secs_west_of_utc, name=None):
+        self._offset = timedelta(seconds=-secs_west_of_utc)
+        self._name = name or 'fixed'
+
+    def utcoffset(self, dt):
+        return self._offset
+
+    def tzname(self, dt):
+        return self._name
+
+    def dst(self, dt):
+        return ZERO
+
+
+utc = tzoffset(0, 'UTC')
+
+
+def from_timestamp(timestamp, tz_offset):
+    """Converts a timestamp + tz_offset into an aware datetime instance."""
+    utc_dt = datetime.fromtimestamp(timestamp, utc)
+    local_dt = utc_dt.astimezone(tzoffset(tz_offset))
+    return local_dt
+
+
 def parse_date(string_date):
     """
     Parse the given date as one of the following


Test patch:
diff --git a/git/test/test_commit.py b/git/test/test_commit.py
index 3e958edf..23b7154a 100644
--- a/git/test/test_commit.py
+++ b/git/test/test_commit.py
@@ -32,6 +32,8 @@ import time
 import sys
 import re
 import os
+from datetime import datetime
+from git.objects.util import tzoffset, utc
 
 
 def assert_commit_serialization(rwrepo, commit_id, print_performance_info=False):
@@ -343,3 +345,12 @@ JzJMZDRLQLFvnzqZuCjE
         cstream = BytesIO()
         cmt._serialize(cstream)
         assert not re.search(r"^gpgsig ", cstream.getvalue().decode('ascii'), re.MULTILINE)
+
+    def test_datetimes(self):
+        commit = self.rorepo.commit('4251bd5')
+        assert commit.authored_date == 1255018625
+        assert commit.committed_date == 1255026171
+        assert commit.authored_datetime == datetime(2009, 10, 8, 18, 17, 5, tzinfo=tzoffset(-7200)), commit.authored_datetime  # noqa
+        assert commit.authored_datetime == datetime(2009, 10, 8, 16, 17, 5, tzinfo=utc), commit.authored_datetime
+        assert commit.committed_datetime == datetime(2009, 10, 8, 20, 22, 51, tzinfo=tzoffset(-7200))
+        assert commit.committed_datetime == datetime(2009, 10, 8, 18, 22, 51, tzinfo=utc), commit.committed_datetime