Repository: gitpython-developers/GitPython

Issue:
Wrong timezone on commit
This happens when committing via call to repo.index.commit(message). The timezone value is off by 20 minutes. When I issue 'git log', the following shows:
<pre>
commit 8767403c5e175b5bf69b4ba1d23e8462c722867e
Author: Amol <removed@email>
Date:   Sat May 20 18:50:10 2017 +0530

    'Solved.'

commit 88a0953df5dc4036633e9f5a295291084b144064
Author: Amol <removed@email>
Date:   Sat May 20 17:28:13 2017 +0550

    Solved.

commit 3a0daff1a2655469d412cfd4ab61b00b8da3f63d
Author: Amol <removed@email>
Date:   Sat May 20 14:42:33 2017 +0530

    Updated readme.
</pre>

From the bottom, here's how the commits were made:
1. via 'git commit' on command line
2. via call to repo.index.commit
3. via 'git commit' on command line, again, to make sure it wasn't a problem with git configuration

As you can see, when committed through GitPython, value for timezone is off by 20 minutes. It is an issue with code related to the commit operation. To verify, I added files to the repo using repo.index.add and then committed via 'git commit' on the command line, the timezone was right in this case.

I checked timezone reported by:
1. system ('date +%z'), +0530
2. python (time.timezone), -19800

Both show the correct timezone, +0530, i.e., India Standard Time.

Discussion:

Patch:
diff --git a/git/objects/util.py b/git/objects/util.py
index f405d628..af279154 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -137,22 +137,25 @@ def get_object_type_by_name(
 
 
 def utctz_to_altz(utctz: str) -> int:
-    """we convert utctz to the timezone in seconds, it is the format time.altzone
-    returns. Git stores it as UTC timezone which has the opposite sign as well,
-    which explains the -1 * ( that was made explicit here )
-
-    :param utctz: git utc timezone string, i.e. +0200"""
-    return -1 * int(float(utctz) / 100 * 3600)
-
-
-def altz_to_utctz_str(altz: float) -> str:
-    """As above, but inverses the operation, returning a string that can be used
-    in commit objects"""
-    utci = -1 * int((float(altz) / 3600) * 100)
-    utcs = str(abs(utci))
-    utcs = "0" * (4 - len(utcs)) + utcs
-    prefix = (utci < 0 and "-") or "+"
-    return prefix + utcs
+    """Convert a git timezone offset into a timezone offset west of
+    UTC in seconds (compatible with time.altzone).
+
+    :param utctz: git utc timezone string, i.e. +0200
+    """
+    int_utctz = int(utctz)
+    seconds = ((abs(int_utctz) // 100) * 3600 + (abs(int_utctz) % 100) * 60)
+    return seconds if int_utctz < 0 else -seconds
+
+
+def altz_to_utctz_str(altz: int) -> str:
+    """Convert a timezone offset west of UTC in seconds into a git timezone offset string
+
+    :param altz: timezone offset in seconds west of UTC
+    """
+    hours = abs(altz) // 3600
+    minutes = (abs(altz) % 3600) // 60
+    sign = "-" if altz >= 60 else "+"
+    return "{}{:02}{:02}".format(sign, hours, minutes)
 
 
 def verify_utctz(offset: str) -> str:


Test patch:
diff --git a/test/test_util.py b/test/test_util.py
index 90dd89a9..c17efce3 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -333,6 +333,27 @@ class TestUtils(TestBase):
         self.assertRaises(IndexError, ilist.__delitem__, 0)
         self.assertRaises(IndexError, ilist.__delitem__, "something")
 
+    def test_utctz_to_altz(self):
+        self.assertEqual(utctz_to_altz("+0000"), 0)
+        self.assertEqual(utctz_to_altz("+1400"), -(14 * 3600))
+        self.assertEqual(utctz_to_altz("-1200"), 12 * 3600)
+        self.assertEqual(utctz_to_altz("+0001"), -60)
+        self.assertEqual(utctz_to_altz("+0530"), -(5 * 3600 + 1800))
+        self.assertEqual(utctz_to_altz("-0930"), 9 * 3600 + 1800)
+
+    def test_altz_to_utctz_str(self):
+        self.assertEqual(altz_to_utctz_str(0), "+0000")
+        self.assertEqual(altz_to_utctz_str(-(14 * 3600)), "+1400")
+        self.assertEqual(altz_to_utctz_str(12 * 3600), "-1200")
+        self.assertEqual(altz_to_utctz_str(-60), "+0001")
+        self.assertEqual(altz_to_utctz_str(-(5 * 3600 + 1800)), "+0530")
+        self.assertEqual(altz_to_utctz_str(9 * 3600 + 1800), "-0930")
+
+        self.assertEqual(altz_to_utctz_str(1), "+0000")
+        self.assertEqual(altz_to_utctz_str(59), "+0000")
+        self.assertEqual(altz_to_utctz_str(-1), "+0000")
+        self.assertEqual(altz_to_utctz_str(-59), "+0000")
+
     def test_from_timestamp(self):
         # Correct offset: UTC+2, should return datetime + tzoffset(+2)
         altz = utctz_to_altz("+0200")