Hi,

For the dates older than 1900, Python is unable to convert the datetime
representation to string using strftime:

https://bugs.python.org/issue1777412

Work around the issue adding a custom method to convert the datetime
objects to LDAP generalized time strings.

https://fedorahosted.org/freeipa/ticket/5579

Tomas
From d746dd233c07b0dc81f539f502844a16e5cc97e2 Mon Sep 17 00:00:00 2001
From: Tomas Babej <tba...@redhat.com>
Date: Fri, 15 Jan 2016 12:20:12 +0100
Subject: [PATCH] ipapython: Use custom datetime to LDAP generalized time
 converter

For the dates older than 1900, Python is unable to convert the datetime
representation to string using strftime:

https://bugs.python.org/issue1777412

Work around the issue adding a custom method to convert the datetime
objects to LDAP generalized time strings.

https://fedorahosted.org/freeipa/ticket/5579
---
 daemons/dnssec/ipa-ods-exporter          |  5 +----
 ipalib/cli.py                            |  5 +++--
 ipalib/rpc.py                            |  6 +++---
 ipapython/ipaldap.py                     |  4 ++--
 ipapython/ipautil.py                     | 17 +++++++++++++++++
 ipaserver/install/ipa_otptoken_import.py |  4 ++--
 6 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/daemons/dnssec/ipa-ods-exporter b/daemons/dnssec/ipa-ods-exporter
index 2aa936040c373e366e7e15539ed6e3413aac7d55..b2df53dee0ecb8cc08fcde9c20e17f72588b18de 100755
--- a/daemons/dnssec/ipa-ods-exporter
+++ b/daemons/dnssec/ipa-ods-exporter
@@ -83,9 +83,6 @@ def dnskey_flags_to_text_set(flags):
         mask <<= 1
     return flags_set
 
-def datetime2ldap(dt):
-    return dt.strftime(ipalib.constants.LDAP_GENERALIZED_TIME_FORMAT)
-
 def sql2datetime(sql_time):
     """Convert SQL date format from local time zone into UTC."""
     localtz = dateutil.tz.tzlocal()
@@ -276,7 +273,7 @@ def get_ods_keys(zone_name):
 
         key_data.update(sql2ldap_algorithm(row['algorithm']))
         key_id = "%s-%s-%s" % (key_type,
-                               datetime2ldap(key_data['idnsSecKeyCreated']),
+                               ipautil.datetime_to_ldap_gentime(key_data['idnsSecKeyCreated']),
                                row['HSMkey_id'])
 
         key_data.update(sql2ldap_keyid(row['HSMkey_id']))
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 3b1b5a39371845d59bab07ac2fc32de598a469be..58fbf048fdda4278bec0846486837fd35a581526 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -56,11 +56,12 @@ from ipalib import plugable
 from ipalib.errors import (PublicError, CommandError, HelpError, InternalError,
                            NoSuchNamespaceError, ValidationError, NotFound,
                            NotConfiguredError, PromptFailed)
-from ipalib.constants import CLI_TAB, LDAP_GENERALIZED_TIME_FORMAT
+from ipalib.constants import CLI_TAB
 from ipalib.parameters import File, Str, Enum, Any, Flag
 from ipalib.text import _
 from ipalib import api  # pylint: disable=unused-import
 from ipapython.dnsutil import DNSName
+from ipapython import ipautil
 
 import datetime
 
@@ -169,7 +170,7 @@ class textui(backend.Backend):
         if type(value) is bytes:
             return base64.b64encode(value)
         elif type(value) is datetime.datetime:
-            return value.strftime(LDAP_GENERALIZED_TIME_FORMAT)
+            return ipautil.datetime_to_ldap_gentime(value)
         elif isinstance(value, DNSName):
             return unicode(value)
         else:
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index a165491adea5366a14a86d7c8bd6337e36fd1b44..a2ca7cb3374e28074332c8827ab51088cc83a5e7 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -185,7 +185,7 @@ def xml_wrap(value, version):
         if capabilities.client_has_capability(version, 'datetime_values'):
             return DateTime(value)
         else:
-            return value.strftime(LDAP_GENERALIZED_TIME_FORMAT)
+            return ipautil.datetime_to_ldap_gentime(value)
 
     if isinstance(value, DNSName):
         if capabilities.client_has_capability(version, 'dns_name_values'):
@@ -304,9 +304,9 @@ def json_encode_binary(val, version):
         return str(val)
     elif isinstance(val, datetime.datetime):
         if capabilities.client_has_capability(version, 'datetime_values'):
-            return {'__datetime__': val.strftime(LDAP_GENERALIZED_TIME_FORMAT)}
+            return {'__datetime__': ipautil.datetime_to_ldap_gentime(val)}
         else:
-            return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
+            return ipautil.datetime_to_ldap_gentime(val)
     elif isinstance(val, DNSName):
         if capabilities.client_has_capability(version, 'dns_name_values'):
             return {'__dns_name__': unicode(val)}
diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py
index 28bfcb5c2ee2140d38f17248fc9c90861cd251e4..d916fd62698a0ff6fe023357238ec33b5ae099b9 100644
--- a/ipapython/ipaldap.py
+++ b/ipapython/ipaldap.py
@@ -38,7 +38,7 @@ import six
 
 from ipalib import errors, _
 from ipalib.constants import LDAP_GENERALIZED_TIME_FORMAT
-from ipapython.ipautil import (
+from ipapython.ipautil import (datetime_to_ldap_gentime,
     format_netloc, wait_for_open_socket, wait_for_open_ports, CIDict)
 from ipapython.ipa_log_manager import log_mgr
 from ipapython.dn import DN
@@ -847,7 +847,7 @@ class LDAPClient(object):
             dct = dict((self.encode(k), self.encode(v)) for k, v in val.items())
             return dct
         elif isinstance(val, datetime.datetime):
-            return val.strftime(LDAP_GENERALIZED_TIME_FORMAT)
+            return datetime_to_ldap_gentime(val)
         elif val is None:
             return None
         else:
diff --git a/ipapython/ipautil.py b/ipapython/ipautil.py
index 7949bdf05cd72c512e6c2bc24b1bc52012e63317..1bdd4586c4918a45caed549b7aada7a83a4080c1 100644
--- a/ipapython/ipautil.py
+++ b/ipapython/ipautil.py
@@ -832,6 +832,23 @@ def parse_generalized_time(timestr):
     except ValueError:
         return None
 
+def datetime_to_ldap_gentime(value):
+    """
+    Converts a datetime.datetime instance to a string representation,
+    circumventing Python bug which occurs for dates older than 1900:
+        https://bugs.python.org/issue1777412
+
+    Note: The inverse conversion using strptime works fine even for
+    such dates, therefore a symmetric conversion function is not necessary.
+    """
+
+    if not isinstance(value, datetime.datetime):
+        raise ValueError("Only datetime.datetime can be converted to"
+                         "LDAP generalized time")
+
+    return ('{0.year:4d}{0.month:02d}{0.day:02d}{0.hour:02d}'
+            '{0.minute:02d}{0.second:02d}Z').format(value)
+
 def ipa_generate_password(characters=None,pwd_len=None):
     ''' Generates password. Password cannot start or end with a whitespace
     character. It also cannot be formed by whitespace characters only.
diff --git a/ipaserver/install/ipa_otptoken_import.py b/ipaserver/install/ipa_otptoken_import.py
index 8ea67fce144f5058ed152378bd677e3f937583eb..731e9ae458865df9934f3c2dafb87254cfa54fd0 100644
--- a/ipaserver/install/ipa_otptoken_import.py
+++ b/ipaserver/install/ipa_otptoken_import.py
@@ -34,7 +34,7 @@ import gssapi
 import six
 from six.moves import xrange
 
-from ipapython import admintool
+from ipapython import admintool, ipautil
 from ipalib import api, errors
 from ipaserver.plugins.ldap2 import AUTOBIND_DISABLED
 
@@ -409,7 +409,7 @@ class PSKCKeyPackage(object):
         dates = (data.get(key + '.sw', None), data.get(key + '.hw', None))
         dates = [x for x in dates if x is not None]
         if dates:
-            out['ipatoken' + key] = unicode(reducer(dates).strftime("%Y%m%d%H%M%SZ"))
+            out['ipatoken' + key] = unicode(ipautil.datetime_to_ldap_gentime(reducer(dates)))
 
 
 class PSKCDocument(object):
-- 
2.5.0

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to