summaryrefslogtreecommitdiffstats
path: root/src/isodate/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/isodate/tests')
-rw-r--r--src/isodate/tests/__init__.py50
-rw-r--r--src/isodate/tests/test_date.py129
-rw-r--r--src/isodate/tests/test_datetime.py146
-rw-r--r--src/isodate/tests/test_duration.py601
-rw-r--r--src/isodate/tests/test_pickle.py54
-rw-r--r--src/isodate/tests/test_strf.py135
-rw-r--r--src/isodate/tests/test_time.py143
7 files changed, 1258 insertions, 0 deletions
diff --git a/src/isodate/tests/__init__.py b/src/isodate/tests/__init__.py
new file mode 100644
index 0000000..09dba2e
--- /dev/null
+++ b/src/isodate/tests/__init__.py
@@ -0,0 +1,50 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Collect all test suites into one TestSuite instance.
+'''
+
+import unittest
+from isodate.tests import (test_date, test_time, test_datetime, test_duration,
+ test_strf, test_pickle)
+
+
+def test_suite():
+ '''
+ Return a new TestSuite instance consisting of all available TestSuites.
+ '''
+ return unittest.TestSuite([
+ test_date.test_suite(),
+ test_time.test_suite(),
+ test_datetime.test_suite(),
+ test_duration.test_suite(),
+ test_strf.test_suite(),
+ test_pickle.test_suite(),
+ ])
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_date.py b/src/isodate/tests/test_date.py
new file mode 100644
index 0000000..fdc1043
--- /dev/null
+++ b/src/isodate/tests/test_date.py
@@ -0,0 +1,129 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Test cases for the isodate module.
+'''
+import unittest
+from datetime import date
+from isodate import parse_date, ISO8601Error, date_isoformat
+from isodate import DATE_CENTURY, DATE_YEAR, DATE_MONTH
+from isodate import DATE_EXT_COMPLETE, DATE_BAS_COMPLETE
+from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
+from isodate import DATE_BAS_WEEK, DATE_BAS_WEEK_COMPLETE
+from isodate import DATE_EXT_WEEK, DATE_EXT_WEEK_COMPLETE
+
+# the following list contains tuples of ISO date strings and the expected
+# result from the parse_date method. A result of None means an ISO8601Error
+# is expected. The test cases are grouped into dates with 4 digit years
+# and 6 digit years.
+TEST_CASES = {4: [('19', date(1901, 1, 1), DATE_CENTURY),
+ ('1985', date(1985, 1, 1), DATE_YEAR),
+ ('1985-04', date(1985, 4, 1), DATE_MONTH),
+ ('1985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
+ ('19850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
+ ('1985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
+ ('1985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
+ ('1985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
+ ('1985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
+ ('1985W15', date(1985, 4, 8), DATE_BAS_WEEK),
+ ('1985-W15', date(1985, 4, 8), DATE_EXT_WEEK),
+ ('1989-W15', date(1989, 4, 10), DATE_EXT_WEEK),
+ ('1989-W15-5', date(1989, 4, 14), DATE_EXT_WEEK_COMPLETE),
+ ('1-W1-1', None, DATE_BAS_WEEK_COMPLETE)],
+ 6: [('+0019', date(1901, 1, 1), DATE_CENTURY),
+ ('+001985', date(1985, 1, 1), DATE_YEAR),
+ ('+001985-04', date(1985, 4, 1), DATE_MONTH),
+ ('+001985-04-12', date(1985, 4, 12), DATE_EXT_COMPLETE),
+ ('+0019850412', date(1985, 4, 12), DATE_BAS_COMPLETE),
+ ('+001985102', date(1985, 4, 12), DATE_BAS_ORD_COMPLETE),
+ ('+001985-102', date(1985, 4, 12), DATE_EXT_ORD_COMPLETE),
+ ('+001985W155', date(1985, 4, 12), DATE_BAS_WEEK_COMPLETE),
+ ('+001985-W15-5', date(1985, 4, 12), DATE_EXT_WEEK_COMPLETE),
+ ('+001985W15', date(1985, 4, 8), DATE_BAS_WEEK),
+ ('+001985-W15', date(1985, 4, 8), DATE_EXT_WEEK)]}
+
+
+def create_testcase(yeardigits, datestring, expectation, format):
+ '''
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ TEST_CASES list, so that a failed test won't stop other tests.
+ '''
+
+ class TestDate(unittest.TestCase):
+ '''
+ A test case template to parse an ISO date string into a date
+ object.
+ '''
+
+ def test_parse(self):
+ '''
+ Parse an ISO date string and compare it to the expected value.
+ '''
+ if expectation is None:
+ self.assertRaises(ISO8601Error, parse_date, datestring,
+ yeardigits)
+ else:
+ result = parse_date(datestring, yeardigits)
+ self.assertEqual(result, expectation)
+
+ def test_format(self):
+ '''
+ Take date object and create ISO string from it.
+ This is the reverse test to test_parse.
+ '''
+ if expectation is None:
+ self.assertRaises(AttributeError,
+ date_isoformat, expectation, format,
+ yeardigits)
+ else:
+ self.assertEqual(date_isoformat(expectation, format,
+ yeardigits),
+ datestring)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDate)
+
+
+def test_suite():
+ '''
+ Construct a TestSuite instance for all test cases.
+ '''
+ suite = unittest.TestSuite()
+ for yeardigits, tests in TEST_CASES.items():
+ for datestring, expectation, format in tests:
+ suite.addTest(create_testcase(yeardigits, datestring,
+ expectation, format))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_datetime.py b/src/isodate/tests/test_datetime.py
new file mode 100644
index 0000000..ddad5da
--- /dev/null
+++ b/src/isodate/tests/test_datetime.py
@@ -0,0 +1,146 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Test cases for the isodatetime module.
+'''
+import unittest
+from datetime import datetime
+
+from isodate import parse_datetime, UTC, FixedOffset, datetime_isoformat
+from isodate import ISO8601Error
+from isodate import DATE_BAS_COMPLETE, TIME_BAS_MINUTE, TIME_BAS_COMPLETE
+from isodate import DATE_EXT_COMPLETE, TIME_EXT_MINUTE, TIME_EXT_COMPLETE
+from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
+from isodate import DATE_BAS_ORD_COMPLETE, DATE_EXT_ORD_COMPLETE
+from isodate import DATE_BAS_WEEK_COMPLETE, DATE_EXT_WEEK_COMPLETE
+
+# the following list contains tuples of ISO datetime strings and the expected
+# result from the parse_datetime method. A result of None means an ISO8601Error
+# is expected.
+TEST_CASES = [('19850412T1015', datetime(1985, 4, 12, 10, 15),
+ DATE_BAS_COMPLETE + 'T' + TIME_BAS_MINUTE,
+ '19850412T1015'),
+ ('1985-04-12T10:15', datetime(1985, 4, 12, 10, 15),
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_MINUTE,
+ '1985-04-12T10:15'),
+ ('1985102T1015Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+ DATE_BAS_ORD_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
+ '1985102T1015Z'),
+ ('1985-102T10:15Z', datetime(1985, 4, 12, 10, 15, tzinfo=UTC),
+ DATE_EXT_ORD_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_EXT,
+ '1985-102T10:15Z'),
+ ('1985W155T1015+0400', datetime(1985, 4, 12, 10, 15,
+ tzinfo=FixedOffset(4, 0,
+ '+0400')),
+ DATE_BAS_WEEK_COMPLETE + 'T' + TIME_BAS_MINUTE + TZ_BAS,
+ '1985W155T1015+0400'),
+ ('1985-W15-5T10:15+04', datetime(1985, 4, 12, 10, 15,
+ tzinfo=FixedOffset(4, 0,
+ '+0400'),),
+ DATE_EXT_WEEK_COMPLETE + 'T' + TIME_EXT_MINUTE + TZ_HOUR,
+ '1985-W15-5T10:15+04'),
+ ('20110410T101225.123000Z',
+ datetime(2011, 4, 10, 10, 12, 25, 123000, tzinfo=UTC),
+ DATE_BAS_COMPLETE + 'T' + TIME_BAS_COMPLETE + ".%f" + TZ_BAS,
+ '20110410T101225.123000Z'),
+ ('2012-10-12T08:29:46.069178Z',
+ datetime(2012, 10, 12, 8, 29, 46, 69178, tzinfo=UTC),
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
+ '2012-10-12T08:29:46.069178Z'),
+ ('2012-10-12T08:29:46.691780Z',
+ datetime(2012, 10, 12, 8, 29, 46, 691780, tzinfo=UTC),
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
+ '2012-10-12T08:29:46.691780Z'),
+ ('2012-10-30T08:55:22.1234567Z',
+ datetime(2012, 10, 30, 8, 55, 22, 123457, tzinfo=UTC),
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
+ '2012-10-30T08:55:22.123457Z'),
+ ('2012-10-30T08:55:22.1234561Z',
+ datetime(2012, 10, 30, 8, 55, 22, 123456, tzinfo=UTC),
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
+ '2012-10-30T08:55:22.123456Z'),
+ ('2014-08-18 14:55:22.123456Z', None,
+ DATE_EXT_COMPLETE + 'T' + TIME_EXT_COMPLETE + '.%f' + TZ_BAS,
+ '2014-08-18T14:55:22.123456Z'),
+ ]
+
+
+def create_testcase(datetimestring, expectation, format, output):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDateTime(unittest.TestCase):
+ '''
+ A test case template to parse an ISO datetime string into a
+ datetime object.
+ '''
+
+ def test_parse(self):
+ '''
+ Parse an ISO datetime string and compare it to the expected value.
+ '''
+ if expectation is None:
+ self.assertRaises(ISO8601Error, parse_datetime, datetimestring)
+ else:
+ self.assertEqual(parse_datetime(datetimestring), expectation)
+
+ def test_format(self):
+ '''
+ Take datetime object and create ISO string from it.
+ This is the reverse test to test_parse.
+ '''
+ if expectation is None:
+ self.assertRaises(AttributeError,
+ datetime_isoformat, expectation, format)
+ else:
+ self.assertEqual(datetime_isoformat(expectation, format),
+ output)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDateTime)
+
+
+def test_suite():
+ '''
+ Construct a TestSuite instance for all test cases.
+ '''
+ suite = unittest.TestSuite()
+ for datetimestring, expectation, format, output in TEST_CASES:
+ suite.addTest(create_testcase(datetimestring, expectation,
+ format, output))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_duration.py b/src/isodate/tests/test_duration.py
new file mode 100644
index 0000000..0b80a54
--- /dev/null
+++ b/src/isodate/tests/test_duration.py
@@ -0,0 +1,601 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Test cases for the isoduration module.
+'''
+import unittest
+import operator
+from datetime import timedelta, date, datetime
+
+from isodate import Duration, parse_duration, ISO8601Error
+from isodate import D_DEFAULT, D_WEEK, D_ALT_EXT, duration_isoformat
+
+# the following list contains tuples of ISO duration strings and the expected
+# result from the parse_duration method. A result of None means an ISO8601Error
+# is expected.
+PARSE_TEST_CASES = {'P18Y9M4DT11H9M8S': (Duration(4, 8, 0, 0, 9, 11, 0, 9, 18),
+ D_DEFAULT, None),
+ 'P2W': (timedelta(weeks=2), D_WEEK, None),
+ 'P3Y6M4DT12H30M5S': (Duration(4, 5, 0, 0, 30, 12, 0, 6, 3),
+ D_DEFAULT, None),
+ 'P23DT23H': (timedelta(hours=23, days=23),
+ D_DEFAULT, None),
+ 'P4Y': (Duration(years=4), D_DEFAULT, None),
+ 'P1M': (Duration(months=1), D_DEFAULT, None),
+ 'PT1M': (timedelta(minutes=1), D_DEFAULT, None),
+ 'P0.5Y': (Duration(years=0.5), D_DEFAULT, None),
+ 'PT36H': (timedelta(hours=36), D_DEFAULT, 'P1DT12H'),
+ 'P1DT12H': (timedelta(days=1, hours=12), D_DEFAULT, None),
+ '+P11D': (timedelta(days=11), D_DEFAULT, 'P11D'),
+ '-P2W': (timedelta(weeks=-2), D_WEEK, None),
+ '-P2.2W': (timedelta(weeks=-2.2), D_DEFAULT,
+ '-P15DT9H36M'),
+ 'P1DT2H3M4S': (timedelta(days=1, hours=2, minutes=3,
+ seconds=4), D_DEFAULT, None),
+ 'P1DT2H3M': (timedelta(days=1, hours=2, minutes=3),
+ D_DEFAULT, None),
+ 'P1DT2H': (timedelta(days=1, hours=2), D_DEFAULT, None),
+ 'PT2H': (timedelta(hours=2), D_DEFAULT, None),
+ 'PT2.3H': (timedelta(hours=2.3), D_DEFAULT, 'PT2H18M'),
+ 'PT2H3M4S': (timedelta(hours=2, minutes=3, seconds=4),
+ D_DEFAULT, None),
+ 'PT3M4S': (timedelta(minutes=3, seconds=4), D_DEFAULT,
+ None),
+ 'PT22S': (timedelta(seconds=22), D_DEFAULT, None),
+ 'PT22.22S': (timedelta(seconds=22.22), 'PT%S.%fS',
+ 'PT22.220000S'),
+ '-P2Y': (Duration(years=-2), D_DEFAULT, None),
+ '-P3Y6M4DT12H30M5S': (Duration(-4, -5, 0, 0, -30, -12, 0,
+ -6, -3), D_DEFAULT, None),
+ '-P1DT2H3M4S': (timedelta(days=-1, hours=-2, minutes=-3,
+ seconds=-4), D_DEFAULT, None),
+ # alternative format
+ 'P0018-09-04T11:09:08': (Duration(4, 8, 0, 0, 9, 11, 0, 9,
+ 18), D_ALT_EXT, None),
+ # 'PT000022.22': timedelta(seconds=22.22),
+ }
+
+# d1 d2 '+', '-', '>'
+# A list of test cases to test addition and subtraction between datetime and
+# Duration objects.
+# each tuple contains 2 duration strings, and a result string for addition and
+# one for subtraction. The last value says, if the first duration is greater
+# than the second.
+MATH_TEST_CASES = (('P5Y7M1DT9H45M16.72S', 'PT27M24.68S',
+ 'P5Y7M1DT10H12M41.4S', 'P5Y7M1DT9H17M52.04S', None),
+ ('PT28M12.73S', 'PT56M29.92S',
+ 'PT1H24M42.65S', '-PT28M17.19S', False),
+ ('P3Y7M23DT5H25M0.33S', 'PT1H1.95S',
+ 'P3Y7M23DT6H25M2.28S', 'P3Y7M23DT4H24M58.38S', None),
+ ('PT1H1.95S', 'P3Y7M23DT5H25M0.33S',
+ 'P3Y7M23DT6H25M2.28S', '-P3Y7M23DT4H24M58.38S', None),
+ ('P1332DT55M0.33S', 'PT1H1.95S',
+ 'P1332DT1H55M2.28S', 'P1331DT23H54M58.38S', True),
+ ('PT1H1.95S', 'P1332DT55M0.33S',
+ 'P1332DT1H55M2.28S', '-P1331DT23H54M58.38S', False))
+
+
+# A list of test cases to test addition and subtraction of date/datetime
+# and Duration objects. They are tested against the results of an
+# equal long timedelta duration.
+DATE_TEST_CASES = ((date(2008, 2, 29),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (date(2008, 1, 31),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (datetime(2008, 2, 29),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (datetime(2008, 1, 31),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (datetime(2008, 4, 21),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (datetime(2008, 5, 5),
+ timedelta(days=10, hours=12, minutes=20),
+ Duration(days=10, hours=12, minutes=20)),
+ (datetime(2000, 1, 1),
+ timedelta(hours=-33),
+ Duration(hours=-33)),
+ (datetime(2008, 5, 5),
+ Duration(years=1, months=1, days=10, hours=12,
+ minutes=20),
+ Duration(months=13, days=10, hours=12, minutes=20)),
+ (datetime(2000, 3, 30),
+ Duration(years=1, months=1, days=10, hours=12,
+ minutes=20),
+ Duration(months=13, days=10, hours=12, minutes=20)),
+ )
+
+# A list of test cases of additon of date/datetime and Duration. The results
+# are compared against a given expected result.
+DATE_CALC_TEST_CASES = (
+ (date(2000, 2, 1),
+ Duration(years=1, months=1),
+ date(2001, 3, 1)),
+ (date(2000, 2, 29),
+ Duration(years=1, months=1),
+ date(2001, 3, 29)),
+ (date(2000, 2, 29),
+ Duration(years=1),
+ date(2001, 2, 28)),
+ (date(1996, 2, 29),
+ Duration(years=4),
+ date(2000, 2, 29)),
+ (date(2096, 2, 29),
+ Duration(years=4),
+ date(2100, 2, 28)),
+ (date(2000, 2, 1),
+ Duration(years=-1, months=-1),
+ date(1999, 1, 1)),
+ (date(2000, 2, 29),
+ Duration(years=-1, months=-1),
+ date(1999, 1, 29)),
+ (date(2000, 2, 1),
+ Duration(years=1, months=1, days=1),
+ date(2001, 3, 2)),
+ (date(2000, 2, 29),
+ Duration(years=1, months=1, days=1),
+ date(2001, 3, 30)),
+ (date(2000, 2, 29),
+ Duration(years=1, days=1),
+ date(2001, 3, 1)),
+ (date(1996, 2, 29),
+ Duration(years=4, days=1),
+ date(2000, 3, 1)),
+ (date(2096, 2, 29),
+ Duration(years=4, days=1),
+ date(2100, 3, 1)),
+ (date(2000, 2, 1),
+ Duration(years=-1, months=-1, days=-1),
+ date(1998, 12, 31)),
+ (date(2000, 2, 29),
+ Duration(years=-1, months=-1, days=-1),
+ date(1999, 1, 28)),
+ (date(2001, 4, 1),
+ Duration(years=-1, months=-1, days=-1),
+ date(2000, 2, 29)),
+ (date(2000, 4, 1),
+ Duration(years=-1, months=-1, days=-1),
+ date(1999, 2, 28)),
+ (Duration(years=1, months=2),
+ Duration(years=0, months=0, days=1),
+ Duration(years=1, months=2, days=1)),
+ (Duration(years=-1, months=-1, days=-1),
+ date(2000, 4, 1),
+ date(1999, 2, 28)),
+ (Duration(years=1, months=1, weeks=5),
+ date(2000, 1, 30),
+ date(2001, 4, 4)),
+ (parse_duration("P1Y1M5W"),
+ date(2000, 1, 30),
+ date(2001, 4, 4)),
+ (parse_duration("P0.5Y"),
+ date(2000, 1, 30),
+ None),
+ (Duration(years=1, months=1, hours=3),
+ datetime(2000, 1, 30, 12, 15, 00),
+ datetime(2001, 2, 28, 15, 15, 00)),
+ (parse_duration("P1Y1MT3H"),
+ datetime(2000, 1, 30, 12, 15, 00),
+ datetime(2001, 2, 28, 15, 15, 00)),
+ (Duration(years=1, months=2),
+ timedelta(days=1),
+ Duration(years=1, months=2, days=1)),
+ (timedelta(days=1),
+ Duration(years=1, months=2),
+ Duration(years=1, months=2, days=1)),
+ (datetime(2008, 1, 1, 0, 2),
+ Duration(months=1),
+ datetime(2008, 2, 1, 0, 2)),
+ (datetime.strptime("200802", "%Y%M"),
+ parse_duration("P1M"),
+ datetime(2008, 2, 1, 0, 2)),
+ (datetime(2008, 2, 1),
+ Duration(months=1),
+ datetime(2008, 3, 1)),
+ (datetime.strptime("200802", "%Y%m"),
+ parse_duration("P1M"),
+ datetime(2008, 3, 1)),
+ # (date(2000, 1, 1),
+ # Duration(years=1.5),
+ # date(2001, 6, 1)),
+ # (date(2000, 1, 1),
+ # Duration(years=1, months=1.5),
+ # date(2001, 2, 14)),
+ )
+
+# A list of test cases of multiplications of durations
+# are compared against a given expected result.
+DATE_MUL_TEST_CASES = (
+ (Duration(years=1, months=1),
+ 3,
+ Duration(years=3, months=3)),
+ (Duration(years=1, months=1),
+ -3,
+ Duration(years=-3, months=-3)),
+ (3,
+ Duration(years=1, months=1),
+ Duration(years=3, months=3)),
+ (-3,
+ Duration(years=1, months=1),
+ Duration(years=-3, months=-3)),
+ (5,
+ Duration(years=2, minutes=40),
+ Duration(years=10, hours=3, minutes=20)),
+ (-5,
+ Duration(years=2, minutes=40),
+ Duration(years=-10, hours=-3, minutes=-20)),
+ (7,
+ Duration(years=1, months=2, weeks=40),
+ Duration(years=8, months=2, weeks=280)))
+
+
+class DurationTest(unittest.TestCase):
+ '''
+ This class tests various other aspects of the isoduration module,
+ which are not covered with the test cases listed above.
+ '''
+
+ def test_associative(self):
+ '''
+ Adding 2 durations to a date is not associative.
+ '''
+ days1 = Duration(days=1)
+ months1 = Duration(months=1)
+ start = date(2000, 3, 30)
+ res1 = start + days1 + months1
+ res2 = start + months1 + days1
+ self.assertNotEqual(res1, res2)
+
+ def test_typeerror(self):
+ '''
+ Test if TypError is raised with certain parameters.
+ '''
+ self.assertRaises(TypeError, parse_duration, date(2000, 1, 1))
+ self.assertRaises(TypeError, operator.sub, Duration(years=1),
+ date(2000, 1, 1))
+ self.assertRaises(TypeError, operator.sub, 'raise exc',
+ Duration(years=1))
+ self.assertRaises(TypeError, operator.add,
+ Duration(years=1, months=1, weeks=5),
+ 'raise exception')
+ self.assertRaises(TypeError, operator.add, 'raise exception',
+ Duration(years=1, months=1, weeks=5))
+ self.assertRaises(TypeError, operator.mul,
+ Duration(years=1, months=1, weeks=5),
+ 'raise exception')
+ self.assertRaises(TypeError, operator.mul, 'raise exception',
+ Duration(years=1, months=1, weeks=5))
+ self.assertRaises(TypeError, operator.mul,
+ Duration(years=1, months=1, weeks=5),
+ 3.14)
+ self.assertRaises(TypeError, operator.mul, 3.14,
+ Duration(years=1, months=1, weeks=5))
+
+ def test_parseerror(self):
+ '''
+ Test for unparseable duration string.
+ '''
+ self.assertRaises(ISO8601Error, parse_duration, 'T10:10:10')
+
+ def test_repr(self):
+ '''
+ Test __repr__ and __str__ for Duration objects.
+ '''
+ dur = Duration(10, 10, years=10, months=10)
+ self.assertEqual('10 years, 10 months, 10 days, 0:00:10', str(dur))
+ self.assertEqual('isodate.duration.Duration(10, 10, 0,'
+ ' years=10, months=10)', repr(dur))
+
+ def test_hash(self):
+ '''
+ Test __hash__ for Duration objects.
+ '''
+ dur1 = Duration(10, 10, years=10, months=10)
+ dur2 = Duration(9, 9, years=9, months=9)
+ dur3 = Duration(10, 10, years=10, months=10)
+ self.assertNotEqual(hash(dur1), hash(dur2))
+ self.assertNotEqual(id(dur1), id(dur2))
+ self.assertEqual(hash(dur1), hash(dur3))
+ self.assertNotEqual(id(dur1), id(dur3))
+ durSet = set()
+ durSet.add(dur1)
+ durSet.add(dur2)
+ durSet.add(dur3)
+ self.assertEqual(len(durSet), 2)
+
+ def test_neg(self):
+ '''
+ Test __neg__ for Duration objects.
+ '''
+ self.assertEqual(-Duration(0), Duration(0))
+ self.assertEqual(-Duration(years=1, months=1),
+ Duration(years=-1, months=-1))
+ self.assertEqual(-Duration(years=1, months=1), Duration(months=-13))
+ self.assertNotEqual(-Duration(years=1), timedelta(days=-365))
+ self.assertNotEqual(-timedelta(days=365), Duration(years=-1))
+ # FIXME: this test fails in python 3... it seems like python3
+ # treats a == b the same b == a
+ # self.assertNotEqual(-timedelta(days=10), -Duration(days=10))
+
+ def test_format(self):
+ '''
+ Test various other strftime combinations.
+ '''
+ self.assertEqual(duration_isoformat(Duration(0)), 'P0D')
+ self.assertEqual(duration_isoformat(-Duration(0)), 'P0D')
+ self.assertEqual(duration_isoformat(Duration(seconds=10)), 'PT10S')
+ self.assertEqual(duration_isoformat(Duration(years=-1, months=-1)),
+ '-P1Y1M')
+ self.assertEqual(duration_isoformat(-Duration(years=1, months=1)),
+ '-P1Y1M')
+ self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
+ 'P1Y1M')
+ self.assertEqual(duration_isoformat(-Duration(years=-1, months=-1)),
+ 'P1Y1M')
+ dur = Duration(years=3, months=7, days=23, hours=5, minutes=25,
+ milliseconds=330)
+ self.assertEqual(duration_isoformat(dur), 'P3Y7M23DT5H25M0.33S')
+ self.assertEqual(duration_isoformat(-dur), '-P3Y7M23DT5H25M0.33S')
+
+ def test_equal(self):
+ '''
+ Test __eq__ and __ne__ methods.
+ '''
+ self.assertEqual(Duration(years=1, months=1),
+ Duration(years=1, months=1))
+ self.assertEqual(Duration(years=1, months=1), Duration(months=13))
+ self.assertNotEqual(Duration(years=1, months=2),
+ Duration(years=1, months=1))
+ self.assertNotEqual(Duration(years=1, months=1), Duration(months=14))
+ self.assertNotEqual(Duration(years=1), timedelta(days=365))
+ self.assertFalse(Duration(years=1, months=1) !=
+ Duration(years=1, months=1))
+ self.assertFalse(Duration(years=1, months=1) != Duration(months=13))
+ self.assertTrue(Duration(years=1, months=2) !=
+ Duration(years=1, months=1))
+ self.assertTrue(Duration(years=1, months=1) != Duration(months=14))
+ self.assertTrue(Duration(years=1) != timedelta(days=365))
+ self.assertEqual(Duration(days=1), timedelta(days=1))
+ # FIXME: this test fails in python 3... it seems like python3
+ # treats a != b the same b != a
+ # self.assertNotEqual(timedelta(days=1), Duration(days=1))
+
+ def test_totimedelta(self):
+ '''
+ Test conversion form Duration to timedelta.
+ '''
+ dur = Duration(years=1, months=2, days=10)
+ self.assertEqual(dur.totimedelta(datetime(1998, 2, 25)),
+ timedelta(434))
+ # leap year has one day more in february
+ self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)),
+ timedelta(435))
+ dur = Duration(months=2)
+ # march is longer than february, but april is shorter than
+ # march (cause only one day difference compared to 2)
+ self.assertEqual(dur.totimedelta(datetime(2000, 2, 25)), timedelta(60))
+ self.assertEqual(dur.totimedelta(datetime(2001, 2, 25)), timedelta(59))
+ self.assertEqual(dur.totimedelta(datetime(2001, 3, 25)), timedelta(61))
+
+
+def create_parsetestcase(durationstring, expectation, format, altstr):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ PARSE_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestParseDuration(unittest.TestCase):
+ '''
+ A test case template to parse an ISO duration string into a
+ timedelta or Duration object.
+ '''
+
+ def test_parse(self):
+ '''
+ Parse an ISO duration string and compare it to the expected value.
+ '''
+ result = parse_duration(durationstring)
+ self.assertEqual(result, expectation)
+
+ def test_format(self):
+ '''
+ Take duration/timedelta object and create ISO string from it.
+ This is the reverse test to test_parse.
+ '''
+ if altstr:
+ self.assertEqual(duration_isoformat(expectation, format),
+ altstr)
+ else:
+ # if durationstring == '-P2W':
+ # import pdb; pdb.set_trace()
+ self.assertEqual(duration_isoformat(expectation, format),
+ durationstring)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestParseDuration)
+
+
+def create_mathtestcase(dur1, dur2, resadd, ressub, resge):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ MATH_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ dur1 = parse_duration(dur1)
+ dur2 = parse_duration(dur2)
+ resadd = parse_duration(resadd)
+ ressub = parse_duration(ressub)
+
+ class TestMathDuration(unittest.TestCase):
+ '''
+ A test case template test addition, subtraction and >
+ operators for Duration objects.
+ '''
+
+ def test_add(self):
+ '''
+ Test operator + (__add__, __radd__)
+ '''
+ self.assertEqual(dur1 + dur2, resadd)
+
+ def test_sub(self):
+ '''
+ Test operator - (__sub__, __rsub__)
+ '''
+ self.assertEqual(dur1 - dur2, ressub)
+
+ def test_ge(self):
+ '''
+ Test operator > and <
+ '''
+ def dogetest():
+ ''' Test greater than.'''
+ return dur1 > dur2
+
+ def doletest():
+ ''' Test less than.'''
+ return dur1 < dur2
+ if resge is None:
+ self.assertRaises(TypeError, dogetest)
+ self.assertRaises(TypeError, doletest)
+ else:
+ self.assertEqual(dogetest(), resge)
+ self.assertEqual(doletest(), not resge)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestMathDuration)
+
+
+def create_datetestcase(start, tdelta, duration):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ DATE_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDateCalc(unittest.TestCase):
+ '''
+ A test case template test addition, subtraction
+ operators for Duration objects.
+ '''
+
+ def test_add(self):
+ '''
+ Test operator +.
+ '''
+ self.assertEqual(start + tdelta, start + duration)
+
+ def test_sub(self):
+ '''
+ Test operator -.
+ '''
+ self.assertEqual(start - tdelta, start - duration)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
+
+
+def create_datecalctestcase(start, duration, expectation):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDateCalc(unittest.TestCase):
+ '''
+ A test case template test addition operators for Duration objects.
+ '''
+
+ def test_calc(self):
+ '''
+ Test operator +.
+ '''
+ if expectation is None:
+ self.assertRaises(ValueError, operator.add, start, duration)
+ else:
+ self.assertEqual(start + duration, expectation)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDateCalc)
+
+
+def create_datemultestcase(operand1, operand2, expectation):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ DATE_CALC_TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDateMul(unittest.TestCase):
+ '''
+ A test case template test addition operators for Duration objects.
+ '''
+
+ def test_mul(self):
+ '''
+ Test operator *.
+ '''
+ self.assertEqual(operand1 * operand2, expectation)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDateMul)
+
+
+def test_suite():
+ '''
+ Return a test suite containing all test defined above.
+ '''
+ suite = unittest.TestSuite()
+ for durationstring, (expectation, format,
+ altstr) in PARSE_TEST_CASES.items():
+ suite.addTest(create_parsetestcase(durationstring, expectation,
+ format, altstr))
+ for testdata in MATH_TEST_CASES:
+ suite.addTest(create_mathtestcase(*testdata))
+ for testdata in DATE_TEST_CASES:
+ suite.addTest(create_datetestcase(*testdata))
+ for testdata in DATE_CALC_TEST_CASES:
+ suite.addTest(create_datecalctestcase(*testdata))
+ for testdata in DATE_MUL_TEST_CASES:
+ suite.addTest(create_datemultestcase(*testdata))
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(DurationTest))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_pickle.py b/src/isodate/tests/test_pickle.py
new file mode 100644
index 0000000..b52f8cb
--- /dev/null
+++ b/src/isodate/tests/test_pickle.py
@@ -0,0 +1,54 @@
+import unittest
+import cPickle as pickle
+import isodate
+
+
+class TestPickle(unittest.TestCase):
+ '''
+ A test case template to parse an ISO datetime string into a
+ datetime object.
+ '''
+
+ def test_pickle_datetime(self):
+ '''
+ Parse an ISO datetime string and compare it to the expected value.
+ '''
+ dti = isodate.parse_datetime('2012-10-26T09:33+00:00')
+ for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
+ pikl = pickle.dumps(dti, proto)
+ self.assertEqual(dti, pickle.loads(pikl),
+ "pickle proto %d failed" % proto)
+
+ def test_pickle_duration(self):
+ '''
+ Pickle / unpickle duration objects.
+ '''
+ from isodate.duration import Duration
+ dur = Duration()
+ failed = []
+ for proto in range(0, pickle.HIGHEST_PROTOCOL + 1):
+ try:
+ pikl = pickle.dumps(dur, proto)
+ if dur != pickle.loads(pikl):
+ raise Exception("not equal")
+ except Exception, e:
+ failed.append("pickle proto %d failed (%s)" % (proto, repr(e)))
+ self.assertEqual(len(failed), 0, "pickle protos failed: %s" %
+ str(failed))
+
+
+def test_suite():
+ '''
+ Construct a TestSuite instance for all test cases.
+ '''
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestPickle))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_strf.py b/src/isodate/tests/test_strf.py
new file mode 100644
index 0000000..37a135b
--- /dev/null
+++ b/src/isodate/tests/test_strf.py
@@ -0,0 +1,135 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Test cases for the isodate module.
+'''
+import unittest
+import time
+from datetime import datetime, timedelta
+from isodate import strftime
+from isodate import LOCAL
+from isodate import DT_EXT_COMPLETE
+from isodate import tzinfo
+
+
+TEST_CASES = ((datetime(2012, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
+ "2012-12-25T13:30:00+10:00"),
+ # DST ON
+ (datetime(1999, 12, 25, 13, 30, 0, 0, LOCAL), DT_EXT_COMPLETE,
+ "1999-12-25T13:30:00+11:00"),
+ # microseconds
+ (datetime(2012, 10, 12, 8, 29, 46, 69178),
+ "%Y-%m-%dT%H:%M:%S.%f",
+ "2012-10-12T08:29:46.069178"),
+ (datetime(2012, 10, 12, 8, 29, 46, 691780),
+ "%Y-%m-%dT%H:%M:%S.%f",
+ "2012-10-12T08:29:46.691780"),
+ )
+
+
+def create_testcase(dt, format, expectation):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestDate(unittest.TestCase):
+ '''
+ A test case template to test ISO date formatting.
+ '''
+
+ # local time zone mock function
+ def localtime_mock(self, secs):
+ """
+ mock time.localtime so that it always returns a time_struct with
+ tm_idst=1
+ """
+ tt = self.ORIG['localtime'](secs)
+ # befor 2000 everything is dst, after 2000 no dst.
+ if tt.tm_year < 2000:
+ dst = 1
+ else:
+ dst = 0
+ tt = (tt.tm_year, tt.tm_mon, tt.tm_mday,
+ tt.tm_hour, tt.tm_min, tt.tm_sec,
+ tt.tm_wday, tt.tm_yday, dst)
+ return time.struct_time(tt)
+
+ def setUp(self):
+ self.ORIG = {}
+ self.ORIG['STDOFFSET'] = tzinfo.STDOFFSET
+ self.ORIG['DSTOFFSET'] = tzinfo.DSTOFFSET
+ self.ORIG['DSTDIFF'] = tzinfo.DSTDIFF
+ self.ORIG['localtime'] = time.localtime
+ # ovveride all saved values with fixtures.
+ # calculate LOCAL TZ offset, so that this test runs in
+ # every time zone
+ tzinfo.STDOFFSET = timedelta(seconds=36000) # assume LOC = +10:00
+ tzinfo.DSTOFFSET = timedelta(seconds=39600) # assume DST = +11:00
+ tzinfo.DSTDIFF = tzinfo.DSTOFFSET - tzinfo.STDOFFSET
+ time.localtime = self.localtime_mock
+
+ def tearDown(self):
+ # restore test fixtures
+ tzinfo.STDOFFSET = self.ORIG['STDOFFSET']
+ tzinfo.DSTOFFSET = self.ORIG['DSTOFFSET']
+ tzinfo.DSTDIFF = self.ORIG['DSTDIFF']
+ time.localtime = self.ORIG['localtime']
+
+ def test_format(self):
+ '''
+ Take date object and create ISO string from it.
+ This is the reverse test to test_parse.
+ '''
+ if expectation is None:
+ self.assertRaises(AttributeError,
+ strftime(dt, format))
+ else:
+ self.assertEqual(strftime(dt, format),
+ expectation)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestDate)
+
+
+def test_suite():
+ '''
+ Construct a TestSuite instance for all test cases.
+ '''
+ suite = unittest.TestSuite()
+ for dt, format, expectation in TEST_CASES:
+ suite.addTest(create_testcase(dt, format, expectation))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
diff --git a/src/isodate/tests/test_time.py b/src/isodate/tests/test_time.py
new file mode 100644
index 0000000..cc5ec08
--- /dev/null
+++ b/src/isodate/tests/test_time.py
@@ -0,0 +1,143 @@
+##############################################################################
+# Copyright 2009, Gerhard Weis
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+# this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+# * Neither the name of the authors nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT
+##############################################################################
+'''
+Test cases for the isotime module.
+'''
+import unittest
+from datetime import time
+
+from isodate import parse_time, UTC, FixedOffset, ISO8601Error, time_isoformat
+from isodate import TIME_BAS_COMPLETE, TIME_BAS_MINUTE
+from isodate import TIME_EXT_COMPLETE, TIME_EXT_MINUTE
+from isodate import TIME_HOUR
+from isodate import TZ_BAS, TZ_EXT, TZ_HOUR
+
+# the following list contains tuples of ISO time strings and the expected
+# result from the parse_time method. A result of None means an ISO8601Error
+# is expected.
+TEST_CASES = [('232050', time(23, 20, 50), TIME_BAS_COMPLETE + TZ_BAS),
+ ('23:20:50', time(23, 20, 50), TIME_EXT_COMPLETE + TZ_EXT),
+ ('2320', time(23, 20), TIME_BAS_MINUTE),
+ ('23:20', time(23, 20), TIME_EXT_MINUTE),
+ ('23', time(23), TIME_HOUR),
+ ('232050,5', time(23, 20, 50, 500000), None),
+ ('23:20:50.5', time(23, 20, 50, 500000), None),
+ # test precision
+ ('15:33:42.123456', time(15, 33, 42, 123456), None),
+ ('15:33:42.1234564', time(15, 33, 42, 123456), None),
+ ('15:33:42.1234557', time(15, 33, 42, 123456), None),
+ ('2320,8', time(23, 20, 48), None),
+ ('23:20,8', time(23, 20, 48), None),
+ ('23,3', time(23, 18), None),
+ ('232030Z', time(23, 20, 30, tzinfo=UTC),
+ TIME_BAS_COMPLETE + TZ_BAS),
+ ('2320Z', time(23, 20, tzinfo=UTC), TIME_BAS_MINUTE + TZ_BAS),
+ ('23Z', time(23, tzinfo=UTC), TIME_HOUR + TZ_BAS),
+ ('23:20:30Z', time(23, 20, 30, tzinfo=UTC),
+ TIME_EXT_COMPLETE + TZ_EXT),
+ ('23:20Z', time(23, 20, tzinfo=UTC), TIME_EXT_MINUTE + TZ_EXT),
+ ('152746+0100', time(15, 27, 46,
+ tzinfo=FixedOffset(1, 0, '+0100')), TIME_BAS_COMPLETE + TZ_BAS),
+ ('152746-0500', time(15, 27, 46,
+ tzinfo=FixedOffset(-5, 0, '-0500')),
+ TIME_BAS_COMPLETE + TZ_BAS),
+ ('152746+01', time(15, 27, 46,
+ tzinfo=FixedOffset(1, 0, '+01:00')),
+ TIME_BAS_COMPLETE + TZ_HOUR),
+ ('152746-05', time(15, 27, 46,
+ tzinfo=FixedOffset(-5, -0, '-05:00')),
+ TIME_BAS_COMPLETE + TZ_HOUR),
+ ('15:27:46+01:00', time(15, 27, 46,
+ tzinfo=FixedOffset(1, 0, '+01:00')),
+ TIME_EXT_COMPLETE + TZ_EXT),
+ ('15:27:46-05:00', time(15, 27, 46,
+ tzinfo=FixedOffset(-5, -0, '-05:00')),
+ TIME_EXT_COMPLETE + TZ_EXT),
+ ('15:27:46+01', time(15, 27, 46,
+ tzinfo=FixedOffset(1, 0, '+01:00')),
+ TIME_EXT_COMPLETE + TZ_HOUR),
+ ('15:27:46-05', time(15, 27, 46,
+ tzinfo=FixedOffset(-5, -0, '-05:00')),
+ TIME_EXT_COMPLETE + TZ_HOUR),
+ ('1:17:30', None, TIME_EXT_COMPLETE)]
+
+
+def create_testcase(timestring, expectation, format):
+ """
+ Create a TestCase class for a specific test.
+
+ This allows having a separate TestCase for each test tuple from the
+ TEST_CASES list, so that a failed test won't stop other tests.
+ """
+
+ class TestTime(unittest.TestCase):
+ '''
+ A test case template to parse an ISO time string into a time
+ object.
+ '''
+
+ def test_parse(self):
+ '''
+ Parse an ISO time string and compare it to the expected value.
+ '''
+ if expectation is None:
+ self.assertRaises(ISO8601Error, parse_time, timestring)
+ else:
+ result = parse_time(timestring)
+ self.assertEqual(result, expectation)
+
+ def test_format(self):
+ '''
+ Take time object and create ISO string from it.
+ This is the reverse test to test_parse.
+ '''
+ if expectation is None:
+ self.assertRaises(AttributeError,
+ time_isoformat, expectation, format)
+ elif format is not None:
+ self.assertEqual(time_isoformat(expectation, format),
+ timestring)
+
+ return unittest.TestLoader().loadTestsFromTestCase(TestTime)
+
+
+def test_suite():
+ '''
+ Construct a TestSuite instance for all test cases.
+ '''
+ suite = unittest.TestSuite()
+ for timestring, expectation, format in TEST_CASES:
+ suite.addTest(create_testcase(timestring, expectation, format))
+ return suite
+
+
+# load_tests Protocol
+def load_tests(loader, tests, pattern):
+ return test_suite()
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')