1From bb7fa19d636d999bf844d80939e155b8f212ef3e Mon Sep 17 00:00:00 2001
2From: Andreas Maier <maiera@de.ibm.com>
3Date: Fri, 23 Jun 2017 19:13:51 +0200
4Subject: [PATCH] Made CIMDateTime always timezone-aware, re-enabled
5 test_cim_types.py
6
7Details:
8- Ensured that 'CIMDateTime' objects for point in time values are
9 timezone-aware when supplied with a timezone-naive 'datetime'
10 object. This does not change the behavior, but increases code
11 clarity.
12- Clarified that in the documentation of 'CIMDateTime'.
13- Added testcases for CIMDateTime.
14- Re-enabled the testing of test_cim_types.py. Since its change
15 to using pytest it was not run because the test methods were
16 all static methods which apparently does not work. Not sure
17 this ever worked.
18
19Signed-off-by: Andreas Maier <maiera@de.ibm.com>
20---
21 docs/changes.rst | 5 +
22 pywbem/cim_types.py | 22 +--
23 testsuite/test_cim_types.py | 369 +++++++++++++++++++++++---------------------
24 3 files changed, 207 insertions(+), 189 deletions(-)
25
26diff --git a/docs/changes.rst b/docs/changes.rst
27index 272ed30d..6fdfbcf4 100644
28--- a/docs/changes.rst
29+++ b/docs/changes.rst
30@@ -72,6 +72,11 @@ Enhancements
31
32 * Added unit test for recorder. See issue #676
33
34+* Ensured that `CIMDateTime` objects for point in time values are
35+ timezone-aware when supplied with a timezone-naive `datetime` object.
36+ This does not change the behavior, but increases code clarity.
37+ Clarified that in the documentation of `CIMDateTime`. See issue #698.
38+
39 Bug fixes
40 ^^^^^^^^^
41
42diff --git a/pywbem/cim_types.py b/pywbem/cim_types.py
43index 6d1f140c..5ecc7707 100644
44--- a/pywbem/cim_types.py
45+++ b/pywbem/cim_types.py
46@@ -74,6 +74,7 @@
47 import re
48 import warnings
49 import six
50+import copy
51
52 from . import config
53
54@@ -294,9 +295,11 @@ def __init__(self, dtarg):
55 * A :term:`string` object will be
56 interpreted as CIM datetime format (see :term:`DSP0004`) and
57 will result in a point in time or a time interval.
58- * A :class:`py:datetime.datetime` object must be timezone-aware
59- (see :class:`~pywbem.MinutesFromUTC`) and will result in a point
60- in time.
61+ * A :class:`py:datetime.datetime` object will result in a point
62+ in time. If the :class:`py:datetime.datetime` object is
63+ timezone-aware (see :class:`~pywbem.MinutesFromUTC`), the
64+ specified timezone will be used. Otherwise, a default timezone
65+ of UTC will be assumed.
66 * A :class:`py:datetime.timedelta` object will result in a time
67 interval.
68 * Another :class:`~pywbem.CIMDateTime` object will be copied.
69@@ -342,14 +345,15 @@ def __init__(self, dtarg):
70 raise ValueError('dtarg argument "%s" has an invalid CIM '
71 'datetime format' % dtarg)
72 elif isinstance(dtarg, datetime):
73- self.__datetime = dtarg
74+ if dtarg.tzinfo is None:
75+ self.__datetime = dtarg.replace(tzinfo=MinutesFromUTC(0))
76+ else:
77+ self.__datetime = copy.copy(dtarg)
78 elif isinstance(dtarg, timedelta):
79- self.__timedelta = dtarg
80+ self.__timedelta = copy.copy(dtarg)
81 elif isinstance(dtarg, CIMDateTime):
82- # pylint: disable=protected-access
83- self.__datetime = dtarg.__datetime
84- # pylint: disable=protected-access
85- self.__timedelta = dtarg.__timedelta
86+ self.__datetime = copy.copy(dtarg.datetime)
87+ self.__timedelta = copy.copy(dtarg.timedelta)
88 else:
89 raise TypeError('dtarg argument "%s" has an invalid type: %s '
90 '(expected datetime, timedelta, string, or '
91diff --git a/testsuite/test_cim_types.py b/testsuite/test_cim_types.py
92index 4ae354d3..b1f54d06 100755
93--- a/testsuite/test_cim_types.py
94+++ b/testsuite/test_cim_types.py
95@@ -43,105 +43,99 @@ def integer_tuple(request):
96 return request.param
97
98
99-class TestIntegers:
100- """
101- Test CIM integer data type classes.
102- """
103-
104- @staticmethod
105- def test_class_attrs_class(integer_tuple):
106- """Test class attrs via class level"""
107- obj_type, exp_cimtype, exp_minvalue, exp_maxvalue = integer_tuple
108- assert obj_type.cimtype == exp_cimtype
109- assert obj_type.minvalue == exp_minvalue
110- assert obj_type.maxvalue == exp_maxvalue
111-
112- @staticmethod
113- def test_class_attrs_inst(integer_tuple):
114- """Test class attrs via instance level"""
115- obj_type, exp_cimtype, exp_minvalue, exp_maxvalue = integer_tuple
116- obj = obj_type(42)
117- assert obj.cimtype == exp_cimtype
118- assert obj.minvalue == exp_minvalue
119- assert obj.maxvalue == exp_maxvalue
120-
121- @staticmethod
122- def test_inheritance(integer_tuple):
123- """Test inheritance"""
124- obj_type = integer_tuple[0]
125- obj = obj_type(42)
126- assert isinstance(obj, obj_type)
127- assert isinstance(obj, CIMType)
128- assert isinstance(obj, CIMInt)
129- assert not isinstance(obj, CIMFloat)
130-
131- @staticmethod
132- def test_init_int(integer_tuple):
133- """Test initialization from integer value"""
134- obj_type = integer_tuple[0]
135- obj = obj_type(42)
136- assert obj == 42
137-
138- @staticmethod
139- def test_init_str(integer_tuple):
140- """Test initialization from string value"""
141- obj_type = integer_tuple[0]
142- obj = obj_type('42')
143- assert obj == 42
144-
145- @staticmethod
146- def test_init_str_base10(integer_tuple):
147- """Test initialization from string value with base 10"""
148- obj_type = integer_tuple[0]
149- obj = obj_type('42', 10)
150- assert obj == 42
151-
152- @staticmethod
153- def test_init_str_base16(integer_tuple):
154- """Test initialization from string value with base 16"""
155- obj_type = integer_tuple[0]
156- obj = obj_type('2A', 16)
157- assert obj == 42
158-
159- @staticmethod
160- def test_init_minimum(integer_tuple):
161- """Test initialization from integer value at minimum"""
162- obj_type = integer_tuple[0]
163- exp_minvalue = integer_tuple[2]
164- obj = obj_type(exp_minvalue)
165- assert obj == exp_minvalue
166-
167- @staticmethod
168- def test_init_maximum(integer_tuple):
169- """Test initialization from integer value at maximum"""
170- obj_type = integer_tuple[0]
171- exp_maxvalue = integer_tuple[3]
172- obj = obj_type(exp_maxvalue)
173- assert obj == exp_maxvalue
174-
175- @staticmethod
176- def test_init_too_low(integer_tuple):
177- """Test initialization from integer value below minimum"""
178- obj_type = integer_tuple[0]
179- exp_minvalue = integer_tuple[2]
180- try:
181- obj_type(exp_minvalue - 1)
182- except ValueError:
183- pass
184- else:
185- raise AssertionError("ValueError was not raised.")
186-
187- @staticmethod
188- def test_init_too_high(integer_tuple):
189- """Test initialization from integer value above maximum"""
190- obj_type = integer_tuple[0]
191- exp_maxvalue = integer_tuple[3]
192- try:
193- obj_type(exp_maxvalue + 1)
194- except ValueError:
195- pass
196- else:
197- raise AssertionError("ValueError was not raised.")
198+def test_integer_class_attrs_class(integer_tuple):
199+ """Test class attrs via class level"""
200+ obj_type, exp_cimtype, exp_minvalue, exp_maxvalue = integer_tuple
201+ assert obj_type.cimtype == exp_cimtype
202+ assert obj_type.minvalue == exp_minvalue
203+ assert obj_type.maxvalue == exp_maxvalue
204+
205+
206+def test_integer_class_attrs_inst(integer_tuple):
207+ """Test class attrs via instance level"""
208+ obj_type, exp_cimtype, exp_minvalue, exp_maxvalue = integer_tuple
209+ obj = obj_type(42)
210+ assert obj.cimtype == exp_cimtype
211+ assert obj.minvalue == exp_minvalue
212+ assert obj.maxvalue == exp_maxvalue
213+
214+
215+def test_integer_inheritance(integer_tuple):
216+ """Test inheritance"""
217+ obj_type = integer_tuple[0]
218+ obj = obj_type(42)
219+ assert isinstance(obj, obj_type)
220+ assert isinstance(obj, CIMType)
221+ assert isinstance(obj, CIMInt)
222+ assert not isinstance(obj, CIMFloat)
223+
224+
225+def test_integer_init_int(integer_tuple):
226+ """Test initialization from integer value"""
227+ obj_type = integer_tuple[0]
228+ obj = obj_type(42)
229+ assert obj == 42
230+
231+
232+def test_integer_init_str(integer_tuple):
233+ """Test initialization from string value"""
234+ obj_type = integer_tuple[0]
235+ obj = obj_type('42')
236+ assert obj == 42
237+
238+
239+def test_integer_init_str_base10(integer_tuple):
240+ """Test initialization from string value with base 10"""
241+ obj_type = integer_tuple[0]
242+ obj = obj_type('42', 10)
243+ assert obj == 42
244+
245+
246+def test_integer_init_str_base16(integer_tuple):
247+ """Test initialization from string value with base 16"""
248+ obj_type = integer_tuple[0]
249+ obj = obj_type('2A', 16)
250+ assert obj == 42
251+
252+
253+def test_integer_init_minimum(integer_tuple):
254+ """Test initialization from integer value at minimum"""
255+ obj_type = integer_tuple[0]
256+ exp_minvalue = integer_tuple[2]
257+ obj = obj_type(exp_minvalue)
258+ assert obj == exp_minvalue
259+
260+
261+def test_integer_init_maximum(integer_tuple):
262+ """Test initialization from integer value at maximum"""
263+ obj_type = integer_tuple[0]
264+ exp_maxvalue = integer_tuple[3]
265+ obj = obj_type(exp_maxvalue)
266+ assert obj == exp_maxvalue
267+
268+
269+def test_integer_init_too_low(integer_tuple):
270+ """Test initialization from integer value below minimum"""
271+ obj_type = integer_tuple[0]
272+ exp_minvalue = integer_tuple[2]
273+ try:
274+ obj_type(exp_minvalue - 1)
275+ except ValueError:
276+ pass
277+ else:
278+ raise AssertionError("ValueError was not raised.")
279+
280+
281+def test_integer_init_too_high(integer_tuple):
282+ """Test initialization from integer value above maximum"""
283+ obj_type = integer_tuple[0]
284+ exp_maxvalue = integer_tuple[3]
285+ try:
286+ obj_type(exp_maxvalue + 1)
287+ except ValueError:
288+ pass
289+ else:
290+ raise AssertionError("ValueError was not raised.")
291
292
293 #
294@@ -164,47 +158,41 @@ def real_tuple(request):
295 return request.param
296
297
298-class TestReals:
299- """
300- Test CIM real data type classes.
301- """
302-
303- @staticmethod
304- def test_class_attrs_class(real_tuple):
305- """Test class attrs via class level"""
306- obj_type, exp_cimtype = real_tuple
307- assert obj_type.cimtype == exp_cimtype
308-
309- @staticmethod
310- def test_class_attrs_inst(real_tuple):
311- """Test class attrs via instance level"""
312- obj_type, exp_cimtype = real_tuple
313- obj = obj_type(42)
314- assert obj.cimtype == exp_cimtype
315-
316- @staticmethod
317- def test_inheritance(real_tuple):
318- """Test inheritance"""
319- obj_type = real_tuple[0]
320- obj = obj_type(42)
321- assert isinstance(obj, obj_type)
322- assert isinstance(obj, CIMType)
323- assert isinstance(obj, CIMFloat)
324- assert not isinstance(obj, CIMInt)
325-
326- @staticmethod
327- def test_init_float(real_tuple):
328- """Test initialization from floating point value"""
329- obj_type = real_tuple[0]
330- obj = obj_type(42.0)
331- assert obj == 42.0
332-
333- @staticmethod
334- def test_init_str(real_tuple):
335- """Test initialization from string value"""
336- obj_type = real_tuple[0]
337- obj = obj_type('42.0')
338- assert obj == 42.0
339+def test_real_class_attrs_class(real_tuple):
340+ """Test class attrs via class level"""
341+ obj_type, exp_cimtype = real_tuple
342+ assert obj_type.cimtype == exp_cimtype
343+
344+
345+def test_real_class_attrs_inst(real_tuple):
346+ """Test class attrs via instance level"""
347+ obj_type, exp_cimtype = real_tuple
348+ obj = obj_type(42)
349+ assert obj.cimtype == exp_cimtype
350+
351+
352+def test_real_inheritance(real_tuple):
353+ """Test inheritance"""
354+ obj_type = real_tuple[0]
355+ obj = obj_type(42)
356+ assert isinstance(obj, obj_type)
357+ assert isinstance(obj, CIMType)
358+ assert isinstance(obj, CIMFloat)
359+ assert not isinstance(obj, CIMInt)
360+
361+
362+def test_real_init_float(real_tuple):
363+ """Test initialization from floating point value"""
364+ obj_type = real_tuple[0]
365+ obj = obj_type(42.0)
366+ assert obj == 42.0
367+
368+
369+def test_real_init_str(real_tuple):
370+ """Test initialization from string value"""
371+ obj_type = real_tuple[0]
372+ obj = obj_type('42.0')
373+ assert obj == 42.0
374
375
376 #
377@@ -271,6 +259,26 @@ def test_init_str(real_tuple):
378 '20140924193040.654321+120'
379 ),
380 (
381+ datetime(year=2014, month=9, day=24, hour=19, minute=30, second=40,
382+ microsecond=654321, tzinfo=MinutesFromUTC(0)),
383+ 'timestamp',
384+ datetime(year=2014, month=9, day=24, hour=19, minute=30, second=40,
385+ microsecond=654321, tzinfo=MinutesFromUTC(0)),
386+ None,
387+ 0,
388+ '20140924193040.654321+000'
389+ ),
390+ (
391+ datetime(year=2014, month=9, day=24, hour=19, minute=30, second=40,
392+ microsecond=654321),
393+ 'timestamp',
394+ datetime(year=2014, month=9, day=24, hour=19, minute=30, second=40,
395+ microsecond=654321, tzinfo=MinutesFromUTC(0)),
396+ None,
397+ 0,
398+ '20140924193040.654321+000'
399+ ),
400+ (
401 '20140924193040.654321+120',
402 'timestamp',
403 datetime(year=2014, month=9, day=24, hour=19, minute=30, second=40,
404@@ -325,46 +333,47 @@ def datetime_init_tuple(request):
405 return request.param
406
407
408-class TestDatetime:
409- """
410- Test CIM real data type classes.
411- """
412-
413- @staticmethod
414- def test_class_attrs_class():
415- """Test class attrs via class level"""
416- assert CIMDateTime.cimtype == 'datetime'
417-
418- @staticmethod
419- def test_class_attrs_inst():
420- """Test class attrs via instance level"""
421- obj = CIMDateTime('00000000000000.000000:000')
422- assert obj.cimtype == 'datetime'
423-
424- @staticmethod
425- def test_inheritance():
426- """Test inheritance"""
427- obj = CIMDateTime('00000000000000.000000:000')
428- assert isinstance(obj, CIMDateTime)
429- assert isinstance(obj, CIMType)
430- assert not isinstance(obj, CIMFloat)
431- assert not isinstance(obj, CIMInt)
432-
433- @staticmethod
434- def test_init(datetime_init_tuple):
435- """Test initialization from all input types"""
436- (dtarg, exp_kind, exp_datetime, exp_timedelta, exp_minutesfromutc,
437- exp_str) = datetime_init_tuple
438- try:
439- obj = CIMDateTime(dtarg)
440- except Exception as exc:
441- assert isinstance(exc, exp_kind)
442- else:
443- assert obj.is_interval == (exp_kind == 'interval')
444- assert obj.datetime == exp_datetime
445- assert obj.timedelta == exp_timedelta
446- assert obj.minutes_from_utc == exp_minutesfromutc
447- assert str(obj) == exp_str
448+def test_datetime_class_attrs_class():
449+ """Test class attrs via class level"""
450+ assert CIMDateTime.cimtype == 'datetime'
451+
452+
453+def test_datetime_class_attrs_inst():
454+ """Test class attrs via instance level"""
455+ obj = CIMDateTime('00000000000000.000000:000')
456+ assert obj.cimtype == 'datetime'
457+
458+
459+def test_datetime_inheritance():
460+ """Test inheritance"""
461+ obj = CIMDateTime('00000000000000.000000:000')
462+ assert isinstance(obj, CIMDateTime)
463+ assert isinstance(obj, CIMType)
464+ assert not isinstance(obj, CIMFloat)
465+ assert not isinstance(obj, CIMInt)
466+
467+
468+def test_datetime_init(datetime_init_tuple):
469+ """Test initialization from all input types"""
470+ (dtarg, exp_kind, exp_datetime, exp_timedelta, exp_minutesfromutc,
471+ exp_str) = datetime_init_tuple
472+ try:
473+ obj = CIMDateTime(dtarg)
474+ except Exception as exc:
475+ assert isinstance(exc, exp_kind)
476+ else:
477+ assert obj.is_interval == (exp_kind == 'interval')
478+ assert obj.datetime == exp_datetime
479+ if obj.datetime is not None:
480+ assert isinstance(obj.datetime, datetime)
481+ # We ensure that the datetime is always timezone-aware:
482+ assert obj.datetime.tzinfo is not None
483+ assert obj.timedelta == exp_timedelta
484+ if obj.timedelta is not None:
485+ assert isinstance(obj.timedelta, timedelta)
486+ assert obj.minutes_from_utc == exp_minutesfromutc
487+ assert str(obj) == exp_str
488+
489
490 # TODO: Add testcases for get_local_utcoffset()
491 # TODO: Add testcases for now()