Rev 2426: Fix formatting of timezones in bundles and merge directives. in http://sourcefrog.net/bzr/gmtime

Martin Pool mbp at sourcefrog.net
Thu Apr 19 08:17:17 BST 2007


At http://sourcefrog.net/bzr/gmtime

------------------------------------------------------------
revno: 2426
revision-id: mbp at sourcefrog.net-20070419071716-tcuv5i38vhci6fuf
parent: pqm at pqm.ubuntu.com-20070417080415-5vn25svmf95ki88z
committer: Martin Pool <mbp at sourcefrog.net>
branch nick: gmtime
timestamp: Thu 2007-04-19 17:17:16 +1000
message:
  Fix formatting of timezones in bundles and merge directives.
  Always give epoch time in utc.
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/tests/test_merge_directive.py test_merge_directive-20070228184838-ja62280spt1g7f4x-2
  bzrlib/tests/test_timestamp.py test_timestamp.py-20070306153932-r3ejn242c20asagv-1
  bzrlib/timestamp.py            timestamp.py-20070306142322-ttbb9oulf3jotljd-1
=== modified file 'NEWS'
--- a/NEWS	2007-04-17 07:09:43 +0000
+++ b/NEWS	2007-04-19 07:17:16 +0000
@@ -83,6 +83,12 @@
     * FTP now works even when the FTP server does not support atomic rename.
       (Aaron Bentley, #89436)
 
+    * Correct handling in bundles and merge directives of timezones with
+      that are not an integer number of hours offset from UTC.  Always 
+      represent the epoch time in UTC to avoid problems with formatting 
+      earlier times on win32.  (Martin Pool, Alexander Belchenko, John
+      Arbash Meinel)
+
   TESTING:
 
     * Added ``bzrlib.strace.strace`` which will strace a single callable and

=== modified file 'bzrlib/tests/test_merge_directive.py'
--- a/bzrlib/tests/test_merge_directive.py	2007-04-01 04:06:14 +0000
+++ b/bzrlib/tests/test_merge_directive.py	2007-04-19 07:17:16 +0000
@@ -68,8 +68,8 @@
 class TestMergeDirective(tests.TestCase):
 
     def test_merge_source(self):
-        time = 500.0
-        timezone = 5
+        time = 500000.0
+        timezone = 5 * 3600
         self.assertRaises(errors.NoMergeSource, merge_directive.MergeDirective,
             'example:', 'sha', time, timezone, 'http://example.com')
         self.assertRaises(errors.NoMergeSource, merge_directive.MergeDirective,
@@ -87,7 +87,7 @@
 
     def test_require_patch(self):
         time = 500.0
-        timezone = 5
+        timezone = 120
         self.assertRaises(errors.PatchMissing, merge_directive.MergeDirective,
             'example:', 'sha', time, timezone, 'http://example.com',
             patch_type='bundle')
@@ -97,8 +97,8 @@
         self.assertEqual(md.patch, '')
 
     def test_serialization(self):
-        time = 501
-        timezone = 72
+        time = 453
+        timezone = 120
         md = merge_directive.MergeDirective('example:', 'sha', time, timezone,
             'http://example.com', patch='booga', patch_type='bundle')
         self.assertEqualDiff(OUTPUT1, ''.join(md.to_lines()))
@@ -108,6 +108,7 @@
         self.assertEqualDiff(OUTPUT2, ''.join(md.to_lines()))
 
     def test_deserialize_junk(self):
+        time = 501
         self.assertRaises(errors.NotAMergeDirective,
                           merge_directive.MergeDirective.from_lines, 'lala')
 
@@ -121,15 +122,15 @@
         self.assertEqual('sha', md.testament_sha1)
         self.assertEqual('http://example.com', md.target_branch)
         self.assertEqual('http://example.org', md.source_branch)
-        self.assertEqual(501, md.time)
-        self.assertEqual(72, md.timezone)
+        self.assertEqual(453, md.time)
+        self.assertEqual(120, md.timezone)
         self.assertEqual('booga', md.patch)
         self.assertEqual('diff', md.patch_type)
         self.assertEqual('Hi mom!', md.message)
 
     def test_roundtrip(self):
-        time = 501
-        timezone = 72
+        time = 500000
+        timezone = 7.5 * 3600
         md = merge_directive.MergeDirective('example:', 'sha', time, timezone,
             'http://example.com', source_branch="http://example.org",
             patch='booga', patch_type='diff')
@@ -204,7 +205,7 @@
     def test_generate_patch(self):
         tree_a, tree_b, branch_c = self.make_trees()
         md2 = merge_directive.MergeDirective.from_objects(
-            tree_a.branch.repository, 'rev2a', 500, 144, tree_b.branch.base,
+            tree_a.branch.repository, 'rev2a', 500, 120, tree_b.branch.base,
             patch_type='diff', public_branch=tree_a.branch.base)
         self.assertNotContainsRe(md2.patch, 'Bazaar revision bundle')
         self.assertContainsRe(md2.patch, '\\+content_c')
@@ -246,7 +247,7 @@
     def test_message(self):
         tree_a, tree_b, branch_c = self.make_trees()
         md3 = merge_directive.MergeDirective.from_objects(
-            tree_a.branch.repository, 'rev2a', 500, 144, tree_b.branch.base,
+            tree_a.branch.repository, 'rev2a', 500, 120, tree_b.branch.base,
             patch_type=None, public_branch=branch_c.base,
             message='Merge message')
         md3.to_lines()
@@ -256,7 +257,7 @@
     def test_generate_bundle(self):
         tree_a, tree_b, branch_c = self.make_trees()
         md1 = merge_directive.MergeDirective.from_objects(
-            tree_a.branch.repository, 'rev2a', 500, 144, tree_b.branch.base,
+            tree_a.branch.repository, 'rev2a', 500, 120, tree_b.branch.base,
             public_branch=branch_c.base)
         self.assertContainsRe(md1.patch, 'Bazaar revision bundle')
         self.assertContainsRe(md1.patch, '\\+content_c')
@@ -265,8 +266,8 @@
         self.assertNotContainsRe(md1.patch, '\\+content_a')
 
     def test_signing(self):
-        time = 501
-        timezone = 72
+        time = 453
+        timezone = 7200
         class FakeBranch(object):
             def get_config(self):
                 return self
@@ -288,7 +289,7 @@
     def test_email(self):
         tree_a, tree_b, branch_c = self.make_trees()
         md = merge_directive.MergeDirective.from_objects(
-            tree_a.branch.repository, 'rev2a', 500, 36, tree_b.branch.base,
+            tree_a.branch.repository, 'rev2a', 476, 60, tree_b.branch.base,
             patch_type=None, public_branch=tree_a.branch.base)
         message = md.to_email('pqm at example.com', tree_a.branch)
         self.assertContainsRe(message.as_string(), EMAIL1)

=== modified file 'bzrlib/tests/test_timestamp.py'
--- a/bzrlib/tests/test_timestamp.py	2007-03-07 20:14:06 +0000
+++ b/bzrlib/tests/test_timestamp.py	2007-04-19 07:17:16 +0000
@@ -22,23 +22,33 @@
 class TestPatchHeader(tests.TestCase):
 
     def test_format_patch_date(self):
+        # epoch is always in utc
         self.assertEqual('1970-01-01 00:00:00 +0000',
             timestamp.format_patch_date(0))
-        self.assertEqual('1970-01-01 05:00:00 +0500',
+        self.assertEqual('1970-01-01 00:00:00 +0000',
             timestamp.format_patch_date(0, 5 * 3600))
-        self.assertEqual('1969-12-31 19:00:00 -0500',
-            timestamp.format_patch_date(0, -5 * 3600))
-        self.assertEqual('1969-12-31 19:00:00 -0500',
-            timestamp.format_patch_date(0, -5 * 3600))
+        self.assertEqual('1970-01-01 00:00:00 +0000',
+            timestamp.format_patch_date(0, -5 * 3600))
+        # regular timestamp with typical timezone
         self.assertEqual('2007-03-06 10:04:19 -0500',
             timestamp.format_patch_date(1173193459, -5 * 3600))
+        # the timezone part is HHMM
+        self.assertEqual('2007-03-06 09:34:19 -0530',
+            timestamp.format_patch_date(1173193459, -5.5 * 3600))
+        # timezones can be offset by single minutes (but no less)
+        self.assertEqual('2007-03-06 15:05:19 +0001',
+            timestamp.format_patch_date(1173193459, +1 * 60))
 
     def test_parse_patch_date(self):
         self.assertEqual((0, 0),
             timestamp.parse_patch_date('1970-01-01 00:00:00 +0000'))
+        # even though we don't emit pre-epoch dates, we can parse them
         self.assertEqual((0, -5 * 3600),
             timestamp.parse_patch_date('1969-12-31 19:00:00 -0500'))
         self.assertEqual((0, +5 * 3600),
             timestamp.parse_patch_date('1970-01-01 05:00:00 +0500'))
         self.assertEqual((1173193459, -5 * 3600),
             timestamp.parse_patch_date('2007-03-06 10:04:19 -0500'))
+        # offset of three minutes
+        self.assertEqual((1173193459, +3 * 60),
+            timestamp.parse_patch_date('2007-03-06 15:07:19 +0003'))

=== modified file 'bzrlib/timestamp.py'
--- a/bzrlib/timestamp.py	2007-03-09 22:41:47 +0000
+++ b/bzrlib/timestamp.py	2007-04-19 07:17:16 +0000
@@ -127,10 +127,20 @@
 
     Inverse of parse_patch_date.
     """
-    assert offset % 36 == 0
+    assert offset % 60 == 0, \
+        "can't represent timezone %s offset by fractional minutes" % offset
+    # so that we don't need to do calculations on pre-epoch times, 
+    # which doesn't work with win32 python gmtime, we always
+    # give the epoch in utc
+    if secs == 0:
+        offset = 0
+    if secs + offset < 0:
+        from warnings import warn
+        warn("gmtime of negative time (%s, %s) may not work on Windows" %
+                (secs, offset))
     tm = time.gmtime(secs+offset)
     time_str = time.strftime('%Y-%m-%d %H:%M:%S', tm)
-    return '%s %+05d' % (time_str, offset/36)
+    return '%s %+03d%02d' % (time_str, offset/3600, abs(offset/60) % 60)
 
 
 def parse_patch_date(date_str):
@@ -139,8 +149,11 @@
     Inverse of format_patch_date.
     """
     secs_str = date_str[:-6]
-    offset_str = date_str[-6:]
-    offset = int(offset_str) * 36
+    offset_str = date_str[-5:]
+    assert len(offset_str) == 5, \
+            "invalid timezone %r" % offset_str
+    offset_hours, offset_mins = offset_str[:3], offset_str[3:]
+    offset = int(offset_hours) * 3600 + int(offset_mins) * 60
     tm_time = time.strptime(secs_str, '%Y-%m-%d %H:%M:%S')
     # adjust seconds according to offset before converting to POSIX
     # timestamp, to avoid edge problems




More information about the bazaar-commits mailing list