| 1345 | |
| 1346 | @staticmethod |
| 1347 | def parse8601(timestamp=None): |
| 1348 | if timestamp is None: |
| 1349 | return None |
| 1350 | try: |
| 1351 | match = Exchange._PARSE8601_ISO8601_PATTERN.search(timestamp) |
| 1352 | if match is None: |
| 1353 | return None |
| 1354 | yyyy, mm, dd, h, m, s, ms, sign, hours, minutes = match.groups() |
| 1355 | # Parse milliseconds |
| 1356 | if ms: |
| 1357 | ms = ms[1:] # Remove leading dot |
| 1358 | ms = ms + '0' * (3 - len(ms)) # Pad to 3 digits |
| 1359 | msint = int(ms) |
| 1360 | else: |
| 1361 | msint = 0 |
| 1362 | # Parse timezone offset |
| 1363 | if sign: |
| 1364 | offset_sign = -1 if sign == '+' else 1 |
| 1365 | offset_hours = int(hours) * offset_sign |
| 1366 | offset_minutes = int(minutes) * offset_sign |
| 1367 | else: |
| 1368 | offset_hours = 0 |
| 1369 | offset_minutes = 0 |
| 1370 | # Build datetime directly |
| 1371 | dt = datetime.datetime(int(yyyy), int(mm), int(dd), int(h), int(m), int(s)) |
| 1372 | dt = dt + datetime.timedelta(hours=offset_hours, minutes=offset_minutes) |
| 1373 | return calendar.timegm(dt.utctimetuple()) * 1000 + msint |
| 1374 | except (TypeError, OverflowError, OSError, ValueError): |
| 1375 | return None |
| 1376 | |
| 1377 | @staticmethod |
| 1378 | def hash(request, algorithm='md5', digest='hex'): |