From b12a7f9ee84ad6d1ebee7dcdd3af2724e91d8525 Mon Sep 17 00:00:00 2001
From: Zooko O'Whielacronx <zooko@zooko.com>
Date: Fri, 3 Apr 2009 15:59:04 -0700
Subject: [PATCH] leases, time_format: modify time stamping in lease
 description  * emit lease expiry date in ISO-8601'ish format as well as
 Brian's format  * rename iso_utc_time_to_localseconds() to
 iso_utc_time_to_seconds()  * add iso_utc_date()  * simplify the body of
 iso_utc_time_to_seconds()

---
 src/allmydata/scripts/tahoe_backup.py |  2 +-
 src/allmydata/test/test_storage.py    |  6 +++---
 src/allmydata/test/test_util.py       | 18 +++++++++++++-----
 src/allmydata/util/time_format.py     | 20 ++++++++++++++------
 src/allmydata/web/storage.py          |  8 +++++---
 5 files changed, 36 insertions(+), 18 deletions(-)

diff --git a/src/allmydata/scripts/tahoe_backup.py b/src/allmydata/scripts/tahoe_backup.py
index b6e26441..e2b9bced 100644
--- a/src/allmydata/scripts/tahoe_backup.py
+++ b/src/allmydata/scripts/tahoe_backup.py
@@ -28,7 +28,7 @@ def parse_old_timestamp(s, options):
         # misleading. This returns seconds-since-epoch for an
         # ISO-8601-ish-formatted UTC time string. This might raise
         # ValueError if the string is not in the right format.
-        when = time_format.iso_utc_time_to_localseconds(s[:-1])
+        when = time_format.iso_utc_time_to_seconds(s[:-1])
         return when
     except ValueError:
         pass
diff --git a/src/allmydata/test/test_storage.py b/src/allmydata/test/test_storage.py
index aa0bd29e..d062426b 100644
--- a/src/allmydata/test/test_storage.py
+++ b/src/allmydata/test/test_storage.py
@@ -2026,9 +2026,9 @@ class LeaseCrawler(unittest.TestCase, pollmixin.PollMixin, WebRenderingMixin):
             s = remove_tags(html)
             self.failUnlessIn("Expiration Enabled:"
                               " expired leases will be removed", s)
-            date = time.strftime("%d-%b-%Y", time.gmtime(then))
-            self.failUnlessIn("Leases created or last renewed before %s"
-                              " will be considered expired." % date, s)
+            date = time.strftime("%Y-%m-%d (%d-%b-%Y) UTC", time.gmtime(then))
+            substr = "Leases created or last renewed before %s will be considered expired." % date
+            self.failUnlessIn(substr, s)
             self.failUnlessIn(" recovered: 2 shares, 2 buckets (1 mutable / 1 immutable), ", s)
         d.addCallback(_check_html)
         return d
diff --git a/src/allmydata/test/test_util.py b/src/allmydata/test/test_util.py
index 19ae70f5..5b747b96 100644
--- a/src/allmydata/test/test_util.py
+++ b/src/allmydata/test/test_util.py
@@ -763,28 +763,36 @@ class Limiter(unittest.TestCase):
 
 class TimeFormat(unittest.TestCase):
     def test_epoch(self):
-        s = time_format.iso_utc_time_to_localseconds("1970-01-01T00:00:01")
+        s = time_format.iso_utc_time_to_seconds("1970-01-01T00:00:01")
         self.failUnlessEqual(s, 1.0)
-        s = time_format.iso_utc_time_to_localseconds("1970-01-01_00:00:01")
+        s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01")
         self.failUnlessEqual(s, 1.0)
-        s = time_format.iso_utc_time_to_localseconds("1970-01-01 00:00:01")
+        s = time_format.iso_utc_time_to_seconds("1970-01-01 00:00:01")
         self.failUnlessEqual(s, 1.0)
 
         self.failUnlessEqual(time_format.iso_utc(1.0), "1970-01-01_00:00:01")
         self.failUnlessEqual(time_format.iso_utc(1.0, sep=" "),
                              "1970-01-01 00:00:01")
         now = time.time()
+        isostr = time_format.iso_utc(now)
+        timestamp = time_format.iso_utc_time_to_seconds(isostr)
+        self.failUnlessEqual(int(timestamp), int(now))
+
         def my_time():
             return 1.0
         self.failUnlessEqual(time_format.iso_utc(t=my_time),
                              "1970-01-01_00:00:01")
         e = self.failUnlessRaises(ValueError,
-                                  time_format.iso_utc_time_to_localseconds,
+                                  time_format.iso_utc_time_to_seconds,
                                   "invalid timestring")
         self.failUnless("not a complete ISO8601 timestamp" in str(e))
-        s = time_format.iso_utc_time_to_localseconds("1970-01-01_00:00:01.500")
+        s = time_format.iso_utc_time_to_seconds("1970-01-01_00:00:01.500")
         self.failUnlessEqual(s, 1.5)
 
+        # Look for daylight-savings-related errors.
+        thatmomentinmarch = time_format.iso_utc_time_to_seconds("2009-03-20 21:49:02.226536")
+        self.failUnlessEqual(thatmomentinmarch, 1237585742.226536)
+
 class CacheDir(unittest.TestCase):
     def test_basic(self):
         basedir = "test_util/CacheDir/test_basic"
diff --git a/src/allmydata/util/time_format.py b/src/allmydata/util/time_format.py
index bb935896..926d7179 100644
--- a/src/allmydata/util/time_format.py
+++ b/src/allmydata/util/time_format.py
@@ -9,12 +9,17 @@
 
 import datetime, re, time
 
+def iso_utc_date(now=None, t=time.time):
+    if now is None:
+        now = t()
+    return datetime.datetime.utcfromtimestamp(now).isoformat()[:10]
+
 def iso_utc(now=None, sep='_', t=time.time):
     if now is None:
         now = t()
     return datetime.datetime.utcfromtimestamp(now).isoformat(sep)
 
-def iso_utc_time_to_localseconds(isotime, _conversion_re=re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})[T_ ](?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<subsecond>\.\d+)?")):
+def iso_utc_time_to_seconds(isotime, _conversion_re=re.compile(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})[T_ ](?P<hour>\d{2}):(?P<minute>\d{2}):(?P<second>\d{2})(?P<subsecond>\.\d+)?")):
     """
     The inverse of iso_utc().
 
@@ -26,13 +31,16 @@ def iso_utc_time_to_localseconds(isotime, _conversion_re=re.compile(r"(?P<year>\
         raise ValueError, (isotime, "not a complete ISO8601 timestamp")
     year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
     hour, minute, second = int(m.group('hour')), int(m.group('minute')), int(m.group('second'))
-    utcseconds = time.mktime( (year, month, day, hour, minute, second, 0, 1, 0) )
-    localseconds = utcseconds - time.timezone
     subsecstr = m.group('subsecond')
     if subsecstr:
         subsecfloat = float(subsecstr)
-        localseconds += subsecfloat
-    return localseconds
+    else:
+        subsecfloat = 0
+
+    localsecondsnodst = time.mktime( (year, month, day, hour, minute, second, 0, 1, 0) )
+    localsecondsnodst += subsecfloat
+    utcseconds = localsecondsnodst - time.timezone
+    return utcseconds
 
 def parse_duration(s):
     orig = s
@@ -62,5 +70,5 @@ def parse_duration(s):
 def parse_date(s):
     # return seconds-since-epoch for the UTC midnight that starts the given
     # day
-    return int(iso_utc_time_to_localseconds(s + "T00:00:00"))
+    return int(iso_utc_time_to_seconds(s + "T00:00:00"))
 
diff --git a/src/allmydata/web/storage.py b/src/allmydata/web/storage.py
index d14e4295..29f2c3a9 100644
--- a/src/allmydata/web/storage.py
+++ b/src/allmydata/web/storage.py
@@ -3,6 +3,7 @@ import time, simplejson
 from nevow import rend, tags as T, inevow
 from allmydata.web.common import getxmlfile, abbreviate_time, get_arg
 from allmydata.util.abbreviate import abbreviate_space
+from allmydata.util import time_format
 
 def remove_prefix(s, prefix):
     if not s.startswith(prefix):
@@ -140,9 +141,10 @@ class StorageStatus(rend.Page):
                         % abbreviate_time(lc.override_lease_duration)]
         else:
             assert lc.mode == "cutoff-date"
-            date = time.strftime("%d-%b-%Y", time.gmtime(lc.cutoff_date))
-            ctx.tag["Leases created or last renewed before %s "
-                    "will be considered expired." % date]
+            localizedutcdate = time.strftime("%d-%b-%Y", time.gmtime(lc.cutoff_date))
+            isoutcdate = time_format.iso_utc_date(lc.cutoff_date)
+            ctx.tag["Leases created or last renewed before %s (%s) UTC "
+                    "will be considered expired." % (isoutcdate, localizedutcdate, )]
         if len(lc.mode) > 2:
             ctx.tag[" The following sharetypes will be expired: ",
                     " ".join(sorted(lc.sharetypes_to_expire)), "."]
-- 
2.45.2