1# This program is free software; you can redistribute it and/or modify it under 2# the terms of the (LGPL) GNU Lesser General Public License as published by the 3# Free Software Foundation; either version 3 of the License, or (at your 4# option) any later version. 5# 6# This program is distributed in the hope that it will be useful, but WITHOUT 7# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 8# FOR A PARTICULAR PURPOSE. See the GNU Library Lesser General Public License 9# for more details at ( http://www.gnu.org/licenses/lgpl.html ). 10# 11# You should have received a copy of the GNU Lesser General Public License 12# along with this program; if not, write to the Free Software Foundation, Inc., 13# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 14# written by: Jeff Ortel ( jortel@redhat.com ) 15 16""" 17Date & time related suds Python library unit tests. 18 19Implemented using the 'pytest' testing framework. 20 21""" 22 23if __name__ == "__main__": 24 import __init__ 25 __init__.runUsingPyTest(globals()) 26 27 28from suds.sax.date import (FixedOffsetTimezone, Date, DateTime, Time, 29 UtcTimezone) 30from suds.xsd.sxbuiltin import XDate, XDateTime, XTime 31import tests 32 33import pytest 34 35import datetime 36 37 38class _Dummy: 39 """Class for testing unknown object class handling.""" 40 pass 41 42 43"""Invalid date strings reused for both date & datetime testing.""" 44_invalid_date_strings = ( 45 "", 46 "abla", 47 "12", 48 "12-01", 49 "-12-01", 50 "1900-01", 51 "+1900-10-01", # Plus sign not allowed. 52 "1900-13-01", # Invalid month. 53 "1900-02-30", # Invalid day. 54 "2001-02-29", # Not a leap year. 55 "2100-02-29", # Not a leap year. 56 " 1900-01-01", 57 "1900- 01-01", 58 "1900-01 -01", 59 "1900-01-01 ", 60 "1900-13-011", 61 "1900-01-01X", 62 "1900-01-01T", # 'T' is a date/time separator for DateTime. 63 # Invalid time zone indicators. 64 "1900-01-01 +17:00", 65 "1900-01-01+ 17:00", 66 "1900-01-01*17:00", 67 "1900-01-01 17:00", 68 "1900-01-01+17:", 69 "1900-01-01+170", 70 "1900-01-01+1730", 71 "1900-01-01+170:00", 72 "1900-01-01+17:00:00", 73 "1900-01-01-:4", 74 "1900-01-01-2a:00", 75 "1900-01-01-222:00", 76 "1900-01-01-12:000" 77 "1900-01-01+00:60", 78 "1900-01-01-00:99") 79 80"""Invalid date strings reused for both time & datetime testing.""" 81_invalid_time_strings = ( 82 "", 83 "bunga", 84 "12", 85 "::", 86 "12:", 87 "12:01", 88 "12:01:", 89 "12:01: 00", 90 "12:01: 00", 91 "23: 01:00", 92 " 23:01:00", 93 "23 :01:00", 94 "23::00", 95 "23:000:00", 96 "023:00:00", 97 "23:00:000", 98 "25:01:00", 99 "-1:01:00", 100 "24:01:00", 101 "23:-1:00", 102 "23:61:00", 103 "23:60:00", 104 "23:59:-1", 105 "23:59:61", 106 "23:59:60", 107 "7.59.13", 108 "7-59-13", 109 "-0:01:00", 110 "23:-0:00", 111 "23:59:-0", 112 "23:59:6.a", 113 "23:59:6.", 114 "23:59:6:0", 115 "23:59:6.12x", 116 "23:59:6.12x45", 117 "23:59:6.999999 ", 118 "23:59:6.999999x", 119 "T23:59:6", 120 # Invalid time zone indicators. 121 "13:27:04 -10:00", 122 "13:27:04- 10:00", 123 "13:27:04*17:00", 124 "13:27:04 17:00", 125 "13:27:04-003", 126 "13:27:04-003:00", 127 "13:27:04+00:002", 128 "13:27:04-13:60", 129 "13:27:04-121", 130 "13:27:04-1210", 131 "13:27:04-121:00", 132 "13:27:04+12:", 133 "13:27:04+12:00:00", 134 "13:27:04-:13" 135 "13:27:04-24:00" 136 "13:27:04+99:00") 137 138 139class TestDate: 140 """Tests for the suds.sax.date.Date class.""" 141 142 def testConstructFromDate(self): 143 date = datetime.date(2001, 12, 10) 144 assert Date(date).value is date 145 146 def testConstructFromDateTime_naive(self): 147 date = datetime.datetime(2001, 12, 10, 10, 50, 21, 32132) 148 assert Date(date).value == datetime.date(2001, 12, 10) 149 150 @pytest.mark.parametrize("hours", (5, 20)) 151 def testConstructFromDateTime_tzAware(self, hours): 152 tz = FixedOffsetTimezone(10) 153 date = datetime.datetime(2001, 12, 10, hours, 50, 21, 32132, tzinfo=tz) 154 assert Date(date).value == datetime.date(2001, 12, 10) 155 156 @pytest.mark.parametrize(("string", "y", "m", "d"), ( 157 ("1900-01-01", 1900, 1, 1), 158 ("1900-1-1", 1900, 1, 1), 159 ("1900-01-01z", 1900, 1, 1), 160 ("1900-01-01Z", 1900, 1, 1), 161 ("1900-01-01-02", 1900, 1, 1), 162 ("1900-01-01+2", 1900, 1, 1), 163 ("1900-01-01+02:00", 1900, 1, 1), 164 ("1900-01-01+99:59", 1900, 1, 1), 165 ("1900-01-01-21:13", 1900, 1, 1), 166 ("2000-02-29", 2000, 2, 29))) # Leap year. 167 def testConstructFromString(self, string, y, m, d): 168 assert Date(string).value == datetime.date(y, m, d) 169 170 @pytest.mark.parametrize("string", _invalid_date_strings) 171 def testConstructFromString_failure(self, string): 172 pytest.raises(ValueError, Date, string) 173 174 @pytest.mark.parametrize("source", ( 175 None, 176 object(), 177 _Dummy(), 178 datetime.time(10, 10))) 179 def testConstructFromUnknown(self, source): 180 pytest.raises(ValueError, Date, source) 181 182 @pytest.mark.parametrize(("input", "output"), ( 183 ("1900-01-01", "1900-01-01"), 184 ("2000-02-29", "2000-02-29"), 185 ("1900-1-1", "1900-01-01"), 186 ("1900-01-01z", "1900-01-01"), 187 ("1900-01-01Z", "1900-01-01"), 188 ("1900-01-01-02", "1900-01-01"), 189 ("1900-01-01+2", "1900-01-01"), 190 ("1900-01-01+02:00", "1900-01-01"), 191 ("1900-01-01+99:59", "1900-01-01"), 192 ("1900-01-01-21:13", "1900-01-01"))) 193 def testConvertToString(self, input, output): 194 assert str(Date(input)) == output 195 196 197class TestDateTime: 198 """Tests for the suds.sax.date.DateTime class.""" 199 200 def testConstructFromDateTime(self): 201 dt = datetime.datetime(2001, 12, 10, 1, 1) 202 assert DateTime(dt).value is dt 203 dt.replace(tzinfo=UtcTimezone()) 204 assert DateTime(dt).value is dt 205 206 @pytest.mark.parametrize( 207 ("string", "y", "M", "d", "h", "m", "s", "micros"), ( 208 ("2013-11-19T14:05:23.428068", 2013, 11, 19, 14, 5, 23, 428068), 209 ("2013-11-19 14:05:23.4280", 2013, 11, 19, 14, 5, 23, 428000))) 210 def testConstructFromString(self, string, y, M, d, h, m, s, micros): 211 assert DateTime(string).value == datetime.datetime(y, M, d, h, m, s, 212 micros) 213 214 @pytest.mark.parametrize("string", 215 [x + "T00:00:00" for x in _invalid_date_strings] + 216 ["2000-12-31T" + x for x in _invalid_time_strings] + [ 217 # Invalid date/time separator characters. 218 "2013-11-1914:05:23.428068", 219 "2013-11-19X14:05:23.428068"]) 220 def testConstructFromString_failure(self, string): 221 pytest.raises(ValueError, DateTime, string) 222 223 @pytest.mark.parametrize( 224 ("string", "y", "M", "d", "h", "m", "s", "micros"), ( 225 ("2000-2-28T23:59:59.9999995", 2000, 2, 29, 0, 0, 0, 0), 226 ("2000-2-29T23:59:59.9999995", 2000, 3, 1, 0, 0, 0, 0), 227 ("2013-12-31T23:59:59.9999994", 2013, 12, 31, 23, 59, 59, 999999), 228 ("2013-12-31T23:59:59.99999949", 2013, 12, 31, 23, 59, 59, 999999), 229 ("2013-12-31T23:59:59.9999995", 2014, 1, 1, 0, 0, 0, 0))) 230 def testConstructFromString_subsecondRounding(self, string, y, M, d, h, m, 231 s, micros): 232 ref = datetime.datetime(y, M, d, h, m, s, micros) 233 assert DateTime(string).value == ref 234 235 @pytest.mark.parametrize( 236 ("string", "y", "M", "d", "h", "m", "s", "micros", "tz_h", "tz_m"), ( 237 ("2013-11-19T14:05:23.428068-3", 238 2013, 11, 19, 14, 5, 23, 428068, -3, 0), 239 ("2013-11-19T14:05:23.068+03", 240 2013, 11, 19, 14, 5, 23, 68000, 3, 0), 241 ("2013-11-19T14:05:23.428068-02:00", 242 2013, 11, 19, 14, 5, 23, 428068, -2, 0), 243 ("2013-11-19T14:05:23.428068+02:00", 244 2013, 11, 19, 14, 5, 23, 428068, 2, 0), 245 ("2013-11-19T14:05:23.428068-23:59", 246 2013, 11, 19, 14, 5, 23, 428068, -23, -59))) 247 def testConstructFromString_timezone(self, string, y, M, d, h, m, s, 248 micros, tz_h, tz_m): 249 tzdelta = datetime.timedelta(hours=tz_h, minutes=tz_m) 250 tzinfo = FixedOffsetTimezone(tzdelta) 251 ref = datetime.datetime(y, M, d, h, m, s, micros, tzinfo=tzinfo) 252 assert DateTime(string).value == ref 253 254 @pytest.mark.parametrize("source", ( 255 None, 256 object(), 257 _Dummy(), 258 datetime.date(2010, 10, 27), 259 datetime.time(10, 10))) 260 def testConstructFromUnknown(self, source): 261 pytest.raises(ValueError, DateTime, source) 262 263 @pytest.mark.parametrize(("input", "output"), ( 264 ("2013-11-19T14:05:23.428068", "2013-11-19T14:05:23.428068"), 265 ("2013-11-19 14:05:23.4280", "2013-11-19T14:05:23.428000"), 266 ("2013-12-31T23:59:59.9999995", "2014-01-01T00:00:00"), 267 ("2013-11-19T14:05:23.428068-3", "2013-11-19T14:05:23.428068-03:00"), 268 ("2013-11-19T14:05:23.068+03", "2013-11-19T14:05:23.068000+03:00"), 269 ("2013-11-19T14:05:23.4-02:00", "2013-11-19T14:05:23.400000-02:00"), 270 ("2013-11-19T14:05:23.410+02:00", "2013-11-19T14:05:23.410000+02:00"), 271 ("2013-11-19T14:05:23.428-23:59", "2013-11-19T14:05:23.428000-23:59"))) 272 def testConvertToString(self, input, output): 273 assert str(DateTime(input)) == output 274 275 276class TestTime: 277 """Tests for the suds.sax.date.Time class.""" 278 279 def testConstructFromTime(self): 280 time = datetime.time(1, 1) 281 assert Time(time).value is time 282 time.replace(tzinfo=UtcTimezone()) 283 assert Time(time).value is time 284 285 @pytest.mark.parametrize(("string", "h", "m", "s", "micros"), ( 286 ("10:59:47", 10, 59, 47, 0), 287 ("9:9:13", 9, 9, 13, 0), 288 ("18:0:09.2139", 18, 0, 9, 213900), 289 ("18:0:09.02139", 18, 0, 9, 21390), 290 ("18:0:09.002139", 18, 0, 9, 2139), 291 ("0:00:00.00013", 0, 0, 0, 130), 292 ("0:00:00.000001", 0, 0, 0, 1), 293 ("0:00:00.000000", 0, 0, 0, 0), 294 ("23:59:6.999999", 23, 59, 6, 999999), 295 ("1:13:50.0", 1, 13, 50, 0))) 296 def testConstructFromString(self, string, h, m, s, micros): 297 assert Time(string).value == datetime.time(h, m, s, micros) 298 299 @pytest.mark.parametrize("string", _invalid_time_strings) 300 def testConstructFromString_failure(self, string): 301 pytest.raises(ValueError, Time, string) 302 303 @pytest.mark.parametrize(("string", "h", "m", "s", "micros"), ( 304 ("0:0:0.0000000", 0, 0, 0, 0), 305 ("0:0:0.0000001", 0, 0, 0, 0), 306 ("0:0:0.0000004", 0, 0, 0, 0), 307 ("0:0:0.0000005", 0, 0, 0, 1), 308 ("0:0:0.0000006", 0, 0, 0, 1), 309 ("0:0:0.0000009", 0, 0, 0, 1), 310 ("0:0:0.5", 0, 0, 0, 500000), 311 ("0:0:0.5000004", 0, 0, 0, 500000), 312 ("0:0:0.5000005", 0, 0, 0, 500001), 313 ("0:0:0.50000050", 0, 0, 0, 500001), 314 ("0:0:0.50000051", 0, 0, 0, 500001), 315 ("0:0:0.50000055", 0, 0, 0, 500001), 316 ("0:0:0.50000059", 0, 0, 0, 500001), 317 ("0:0:0.5000006", 0, 0, 0, 500001), 318 ("0:0:0.9999990", 0, 0, 0, 999999), 319 ("0:0:0.9999991", 0, 0, 0, 999999), 320 ("0:0:0.9999994", 0, 0, 0, 999999), 321 ("0:0:0.99999949", 0, 0, 0, 999999), 322 ("0:0:0.9999995", 0, 0, 1, 0), 323 ("0:0:0.9999996", 0, 0, 1, 0), 324 ("0:0:0.9999999", 0, 0, 1, 0))) 325 def testConstructFromString_subsecondRounding(self, string, h, m, s, 326 micros): 327 assert Time(string).value == datetime.time(h, m, s, micros) 328 329 @pytest.mark.parametrize( 330 ("string", "h", "m", "s", "micros", "tz_h", "tz_m"), ( 331 ("18:0:09.2139z", 18, 0, 9, 213900, 0, 0), 332 ("18:0:09.2139Z", 18, 0, 9, 213900, 0, 0), 333 ("18:0:09.2139+3", 18, 0, 9, 213900, 3, 0), 334 ("18:0:09.2139-3", 18, 0, 9, 213900, -3, 0), 335 ("18:0:09.2139-03", 18, 0, 9, 213900, -3, 0), 336 ("18:0:09.2139+9:3", 18, 0, 9, 213900, 9, 3), 337 ("18:0:09.2139+10:31", 18, 0, 9, 213900, 10, 31), 338 ("18:0:09.2139-10:31", 18, 0, 9, 213900, -10, -31))) 339 def testConstructFromString_timezone(self, string, h, m, s, micros, tz_h, 340 tz_m): 341 tzdelta = datetime.timedelta(hours=tz_h, minutes=tz_m) 342 tzinfo = FixedOffsetTimezone(tzdelta) 343 ref = datetime.time(h, m, s, micros, tzinfo=tzinfo) 344 assert Time(string).value == ref 345 346 @pytest.mark.parametrize("source", ( 347 None, 348 object(), 349 _Dummy(), 350 datetime.date(2010, 10, 27), 351 datetime.datetime(2010, 10, 27, 10, 10))) 352 def testConstructFromUnknown(self, source): 353 pytest.raises(ValueError, Time, source) 354 355 @pytest.mark.parametrize(("input", "output"), ( 356 ("14:05:23.428068", "14:05:23.428068"), 357 ("14:05:23.4280", "14:05:23.428000"), 358 ("23:59:59.9999995", "00:00:00"), 359 ("14:05:23.428068-3", "14:05:23.428068-03:00"), 360 ("14:05:23.068+03", "14:05:23.068000+03:00"), 361 ("14:05:23.4-02:00", "14:05:23.400000-02:00"), 362 ("14:05:23.410+02:00", "14:05:23.410000+02:00"), 363 ("14:05:23.428-23:59", "14:05:23.428000-23:59"))) 364 def testConvertToString(self, input, output): 365 assert str(Time(input)) == output 366 367 368class TestXDate: 369 """ 370 Tests for the suds.xsd.sxbuiltin.XDate class. 371 372 Python object <--> string conversion details already tested in TestDate. 373 374 """ 375 376 def testTranslateEmptyStringToPythonObject(self): 377 assert XDate.translate("") == None 378 379 def testTranslateStringToPythonObject(self): 380 assert XDate.translate("1941-12-7") == datetime.date(1941, 12, 7) 381 382 def testTranslatePythonObjectToString(self): 383 date = datetime.date(2013, 7, 24) 384 translated = XDate.translate(date, topython=False) 385 assert isinstance(translated, str) 386 assert translated == "2013-07-24" 387 388 def testTranslatePythonObjectToString_datetime(self): 389 dt = datetime.datetime(2013, 7, 24, 11, 59, 4) 390 translated = XDate.translate(dt, topython=False) 391 assert isinstance(translated, str) 392 assert translated == "2013-07-24" 393 394 @pytest.mark.parametrize("source", ( 395 None, 396 object(), 397 _Dummy(), 398 datetime.time())) 399 def testTranslatePythonObjectToString_failed(self, source): 400 assert XDate.translate(source, topython=False) is source 401 402 403class TestXDateTime: 404 """ 405 Tests for the suds.xsd.sxbuiltin.XDateTime class. 406 407 Python object <--> string conversion details already tested in 408 TestDateTime. 409 410 """ 411 412 def testTranslateEmptyStringToPythonObject(self): 413 assert XDateTime.translate("") == None 414 415 def testTranslateStringToPythonObject(self): 416 dt = datetime.datetime(1941, 12, 7, 10, 30, 22, 454000) 417 assert XDateTime.translate("1941-12-7T10:30:22.454") == dt 418 419 def testTranslatePythonObjectToString(self): 420 dt = datetime.datetime(2021, 12, 31, 11, 25, tzinfo=UtcTimezone()) 421 translated = XDateTime.translate(dt, topython=False) 422 assert isinstance(translated, str) 423 assert translated == "2021-12-31T11:25:00+00:00" 424 425 @pytest.mark.parametrize("source", ( 426 None, 427 object(), 428 _Dummy(), 429 datetime.time(22, 47, 9, 981), 430 datetime.date(2101, 1, 1))) 431 def testTranslatePythonObjectToString_failed(self, source): 432 assert XDateTime.translate(source, topython=False) is source 433 434 435class TestXTime: 436 """ 437 Tests for the suds.xsd.sxbuiltin.XTime class. 438 439 Python object <--> string conversion details already tested in 440 TestDateTime. 441 442 """ 443 444 def testTranslateEmptyStringToPythonObject(self): 445 assert XTime.translate("") == None 446 447 def testTranslateStringToPythonObject(self): 448 assert XTime.translate("10:30:22") == datetime.time(10, 30, 22) 449 450 def testTranslatePythonObjectToString(self): 451 time = datetime.time(16, 53, 12, tzinfo=FixedOffsetTimezone(4)) 452 translated = XTime.translate(time, topython=False) 453 assert isinstance(translated, str) 454 assert translated == "16:53:12+04:00" 455 456 @pytest.mark.parametrize("source", ( 457 None, 458 object(), 459 _Dummy(), 460 datetime.date(2101, 1, 1), 461 datetime.datetime(2101, 1, 1, 22, 47, 9, 981))) 462 def testTranslatePythonObjectToString_failed(self, source): 463 assert XTime.translate(source, topython=False) is source 464