extract-msg 0.48.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. extract_msg/__init__.py +71 -0
  2. extract_msg/__main__.py +115 -0
  3. extract_msg/_rtf/__init__.py +21 -0
  4. extract_msg/_rtf/create_doc.py +29 -0
  5. extract_msg/_rtf/inject_rtf.py +174 -0
  6. extract_msg/_rtf/token.py +35 -0
  7. extract_msg/_rtf/tokenize_rtf.py +253 -0
  8. extract_msg/attachments/__init__.py +144 -0
  9. extract_msg/attachments/attachment.py +188 -0
  10. extract_msg/attachments/attachment_base.py +730 -0
  11. extract_msg/attachments/broken_att.py +42 -0
  12. extract_msg/attachments/custom_att.py +173 -0
  13. extract_msg/attachments/custom_att_handler/__init__.py +80 -0
  14. extract_msg/attachments/custom_att_handler/custom_handler.py +134 -0
  15. extract_msg/attachments/custom_att_handler/lnk_obj_att.py +109 -0
  16. extract_msg/attachments/custom_att_handler/outlook_image_dib.py +141 -0
  17. extract_msg/attachments/emb_msg_att.py +134 -0
  18. extract_msg/attachments/signed_att.py +234 -0
  19. extract_msg/attachments/unsupported_att.py +40 -0
  20. extract_msg/attachments/web_att.py +74 -0
  21. extract_msg/constants/__init__.py +294 -0
  22. extract_msg/constants/ps.py +46 -0
  23. extract_msg/constants/re.py +42 -0
  24. extract_msg/constants/st.py +110 -0
  25. extract_msg/data/logging-config/logging-nt.json +64 -0
  26. extract_msg/data/logging-config/logging-posix.json +64 -0
  27. extract_msg/encoding/__init__.py +277 -0
  28. extract_msg/encoding/_dt/__init__.py +4 -0
  29. extract_msg/encoding/_dt/_mac_ce.py +8 -0
  30. extract_msg/encoding/_dt/_mac_cyrillic.py +8 -0
  31. extract_msg/encoding/_dt/_mac_greek.py +8 -0
  32. extract_msg/encoding/_dt/_mac_iceland.py +8 -0
  33. extract_msg/encoding/_dt/_mac_turkish.py +8 -0
  34. extract_msg/encoding/_dt/_win874_dec.py +8 -0
  35. extract_msg/encoding/_dt/_win950_dec.py +6 -0
  36. extract_msg/encoding/utils.py +329 -0
  37. extract_msg/enums.py +2067 -0
  38. extract_msg/exceptions.py +170 -0
  39. extract_msg/msg_classes/__init__.py +49 -0
  40. extract_msg/msg_classes/appointment.py +195 -0
  41. extract_msg/msg_classes/calendar.py +92 -0
  42. extract_msg/msg_classes/calendar_base.py +496 -0
  43. extract_msg/msg_classes/contact.py +1335 -0
  44. extract_msg/msg_classes/journal.py +131 -0
  45. extract_msg/msg_classes/meeting_cancellation.py +108 -0
  46. extract_msg/msg_classes/meeting_exception.py +55 -0
  47. extract_msg/msg_classes/meeting_forward.py +115 -0
  48. extract_msg/msg_classes/meeting_related.py +65 -0
  49. extract_msg/msg_classes/meeting_request.py +189 -0
  50. extract_msg/msg_classes/meeting_response.py +74 -0
  51. extract_msg/msg_classes/message.py +12 -0
  52. extract_msg/msg_classes/message_base.py +1382 -0
  53. extract_msg/msg_classes/message_signed.py +17 -0
  54. extract_msg/msg_classes/message_signed_base.py +168 -0
  55. extract_msg/msg_classes/msg.py +1172 -0
  56. extract_msg/msg_classes/post.py +53 -0
  57. extract_msg/msg_classes/sticky_note.py +67 -0
  58. extract_msg/msg_classes/task.py +363 -0
  59. extract_msg/msg_classes/task_request.py +86 -0
  60. extract_msg/null_date.py +34 -0
  61. extract_msg/ole_writer.py +1086 -0
  62. extract_msg/open_msg.py +204 -0
  63. extract_msg/properties/__init__.py +23 -0
  64. extract_msg/properties/named.py +481 -0
  65. extract_msg/properties/prop.py +506 -0
  66. extract_msg/properties/properties_store.py +476 -0
  67. extract_msg/py.typed +0 -0
  68. extract_msg/recipient.py +362 -0
  69. extract_msg/structures/__init__.py +34 -0
  70. extract_msg/structures/_helpers.py +345 -0
  71. extract_msg/structures/business_card.py +422 -0
  72. extract_msg/structures/cfoas.py +108 -0
  73. extract_msg/structures/contact_link_entry.py +51 -0
  74. extract_msg/structures/dev_mode_a.py +621 -0
  75. extract_msg/structures/dv_target_device.py +168 -0
  76. extract_msg/structures/entry_id.py +697 -0
  77. extract_msg/structures/misc_id.py +222 -0
  78. extract_msg/structures/mon_stream.py +55 -0
  79. extract_msg/structures/odt.py +74 -0
  80. extract_msg/structures/ole_pres.py +302 -0
  81. extract_msg/structures/ole_stream_struct.py +117 -0
  82. extract_msg/structures/recurrence_pattern.py +199 -0
  83. extract_msg/structures/report_tag.py +127 -0
  84. extract_msg/structures/system_time.py +158 -0
  85. extract_msg/structures/time_zone_definition.py +111 -0
  86. extract_msg/structures/time_zone_struct.py +102 -0
  87. extract_msg/structures/toc_entry.py +137 -0
  88. extract_msg/structures/tz_rule.py +148 -0
  89. extract_msg/utils.py +1270 -0
  90. extract_msg-0.48.2.dist-info/LICENSE.txt +674 -0
  91. extract_msg-0.48.2.dist-info/METADATA +321 -0
  92. extract_msg-0.48.2.dist-info/RECORD +95 -0
  93. extract_msg-0.48.2.dist-info/WHEEL +5 -0
  94. extract_msg-0.48.2.dist-info/entry_points.txt +2 -0
  95. extract_msg-0.48.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,170 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """
4
+ extract_msg.exceptions
5
+ ~~~~~~~~~~~~~~~~~~~~~~
6
+ This module contains the set of extract_msg exceptions.
7
+ """
8
+
9
+ __all__ = [
10
+ 'ExMsgBaseException',
11
+
12
+ 'ConversionError',
13
+ 'DataNotFoundError',
14
+ 'DeencapMalformedData',
15
+ 'DeencapNotEncapsulated',
16
+ 'ExecutableNotFound',
17
+ 'IncompatibleOptionsError',
18
+ 'InvalidFileFormatError',
19
+ 'InvalidPropertyIdError',
20
+ 'StandardViolationError',
21
+ 'TZError',
22
+ 'UnknownCodepageError',
23
+ 'UnsupportedEncodingError',
24
+ 'UnknownTypeError',
25
+ 'UnsupportedMSGTypeError',
26
+ 'UnrecognizedMSGTypeError',
27
+ 'WKError',
28
+ ]
29
+
30
+ # Base exception types.
31
+
32
+ class ExMsgBaseException(Exception):
33
+ """
34
+ The base class for all custom exceptions the module uses.
35
+ """
36
+
37
+ # I would want this to also be a subclass of NotImplementedError, but Python
38
+ # docs say that CPython can make that a bit problematic due to things from the C
39
+ # side of the code.
40
+ class FeatureNotImplemented(ExMsgBaseException):
41
+ """
42
+ The base class for a feature not yet being implemented in the module.
43
+ """
44
+
45
+
46
+ # More specific exceptions.
47
+
48
+
49
+ class ConversionError(ExMsgBaseException):
50
+ """
51
+ An error occured during type conversion.
52
+ """
53
+
54
+ class DataNotFoundError(ExMsgBaseException):
55
+ """
56
+ Requested stream type was unavailable.
57
+ """
58
+
59
+ class DeencapMalformedData(ExMsgBaseException):
60
+ """
61
+ Data to deencapsulate was malformed in some way.
62
+ """
63
+
64
+ class DeencapNotEncapsulated(ExMsgBaseException):
65
+ """
66
+ Data to deencapsulate did not contain any encapsulated data.
67
+ """
68
+
69
+ class DependencyError(ExMsgBaseException):
70
+ """
71
+ An optional dependency could not be found or was unable to be used as
72
+ expected.
73
+ """
74
+
75
+ class ExecutableNotFound(DependencyError):
76
+ """
77
+ Could not find the specified executable.
78
+ """
79
+
80
+ class IncompatibleOptionsError(ExMsgBaseException):
81
+ """
82
+ Provided options are incompatible with each other.
83
+ """
84
+
85
+ class InvalidFileFormatError(ExMsgBaseException):
86
+ """
87
+ An Invalid File Format Error occurred.
88
+ """
89
+
90
+ class InvalidPropertyIdError(ExMsgBaseException):
91
+ """
92
+ The provided property ID was invalid.
93
+ """
94
+
95
+ class NotWritableError(ExMsgBaseException):
96
+ """
97
+ Modification was attempted on an instance that is not writable.
98
+ """
99
+
100
+ class PrefixError(ExMsgBaseException):
101
+ """
102
+ An issue was detected with the provided prefix.
103
+
104
+ This should never occur if you have no manually provided a prefix.
105
+ """
106
+
107
+ class SecurityError(ExMsgBaseException):
108
+ """
109
+ A code path was triggered that would use an insecure feature, but that
110
+ insecure feature was not enabled.
111
+ """
112
+
113
+ class StandardViolationError(InvalidFileFormatError):
114
+ """
115
+ A critical violation of the MSG standards was detected and could not be
116
+ recovered from.
117
+
118
+ Recoverable violations will result in log messages instead.
119
+
120
+ Any that could reasonably be skipped, although are likely to still cause
121
+ errors down the line, can be suppressed.
122
+ """
123
+
124
+ class TooManySectorsError(ExMsgBaseException):
125
+ """
126
+ Ole writer has too much data to write to the file.
127
+ """
128
+
129
+ class TZError(ExMsgBaseException):
130
+ """
131
+ Specifically not an OSError to avoid being caught by parts of the module.
132
+
133
+ This error represents a fatal error in the datetime parsing as it usually
134
+ means your installation of tzlocal or tzdata are broken. If you have
135
+ received this error after using PyInstaller, you must include the resource
136
+ files for tzdata for it to work properly. See TeamMsgExtractor#272 and
137
+ TeamMsgExtractor#169 for information on why you are getting this error.
138
+ """
139
+
140
+ class UnknownCodepageError(ExMsgBaseException):
141
+ """
142
+ The codepage provided was not one we know of.
143
+ """
144
+
145
+ class UnsupportedEncodingError(FeatureNotImplemented):
146
+ """
147
+ The codepage provided is known but is not supported.
148
+ """
149
+
150
+ class UnknownTypeError(ExMsgBaseException):
151
+ """
152
+ The type specified is not one that is recognized.
153
+ """
154
+
155
+ class UnsupportedMSGTypeError(FeatureNotImplemented):
156
+ """
157
+ An exception that is raised when an MSG class is recognized by not
158
+ supported.
159
+ """
160
+
161
+ class UnrecognizedMSGTypeError(ExMsgBaseException):
162
+ """
163
+ An exception that is raised when the module cannot determine how to properly
164
+ open a specific class of MSG file.
165
+ """
166
+
167
+ class WKError(DependencyError):
168
+ """
169
+ An error occured while running wkhtmltopdf.
170
+ """
@@ -0,0 +1,49 @@
1
+ """
2
+ Classes for opening an MSG file with.
3
+ """
4
+
5
+ __all__ = [
6
+ # Classes:
7
+ 'AppointmentMeeting',
8
+ 'Calendar',
9
+ 'CalendarBase',
10
+ 'Contact',
11
+ 'Journal',
12
+ 'MeetingCancellation',
13
+ 'MeetingException',
14
+ 'MeetingForwardNotification',
15
+ 'MeetingRelated',
16
+ 'MeetingRequest',
17
+ 'MeetingResponse',
18
+ 'Message',
19
+ 'MessageBase',
20
+ 'MessageSigned',
21
+ 'MessageSignedBase',
22
+ 'MSGFile',
23
+ 'Post',
24
+ 'StickyNote',
25
+ 'Task',
26
+ 'TaskRequest',
27
+ ]
28
+
29
+
30
+ from .appointment import AppointmentMeeting
31
+ from .calendar_base import CalendarBase
32
+ from .calendar import Calendar
33
+ from .contact import Contact
34
+ from .journal import Journal
35
+ from .meeting_cancellation import MeetingCancellation
36
+ from .meeting_exception import MeetingException
37
+ from .meeting_forward import MeetingForwardNotification
38
+ from .meeting_related import MeetingRelated
39
+ from .meeting_request import MeetingRequest
40
+ from .meeting_response import MeetingResponse
41
+ from .message import Message
42
+ from .message_base import MessageBase
43
+ from .message_signed import MessageSigned
44
+ from .message_signed_base import MessageSignedBase
45
+ from .msg import MSGFile
46
+ from .post import Post
47
+ from .sticky_note import StickyNote
48
+ from .task import Task
49
+ from .task_request import TaskRequest
@@ -0,0 +1,195 @@
1
+ __all__ = [
2
+ 'AppointmentMeeting',
3
+ ]
4
+
5
+
6
+ import datetime
7
+ import functools
8
+ import json
9
+
10
+ from typing import Optional
11
+
12
+ from ..constants import HEADER_FORMAT_TYPE, ps
13
+ from .calendar import Calendar
14
+ from ..enums import AppointmentStateFlag, RecurPatternType, ResponseStatus
15
+ from ..structures.entry_id import EntryID
16
+
17
+
18
+ class AppointmentMeeting(Calendar):
19
+ """
20
+ Parser for Microsoft Outlook Appointment or Meeting files.
21
+
22
+ Both Appointment and Meeting have the same class type but Meeting has
23
+ additional properties. These properties are meaningless on an Appointment
24
+ object.
25
+ """
26
+
27
+ def getJson(self) -> str:
28
+ meetingStatusString = {
29
+ ResponseStatus.NONE: None,
30
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
31
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
32
+ ResponseStatus.ACCEPTED: 'Accepted',
33
+ ResponseStatus.DECLINED: 'Declined',
34
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
35
+ }[self.responseStatus]
36
+
37
+ # Get the recurrence string.
38
+ recur = '(none)'
39
+ if self.appointmentRecur:
40
+ recur = {
41
+ RecurPatternType.DAY: 'Daily',
42
+ RecurPatternType.WEEK: 'Weekly',
43
+ RecurPatternType.MONTH: 'Monthly',
44
+ RecurPatternType.MONTH_NTH: 'Monthly',
45
+ RecurPatternType.MONTH_END: 'Monthly',
46
+ RecurPatternType.HJ_MONTH: 'Monthly',
47
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
48
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
49
+ }[self.appointmentRecur.patternType]
50
+
51
+ return json.dumps({
52
+ 'recurrence': recur,
53
+ 'recurrencePattern': self.recurrencePattern,
54
+ 'body': self.body,
55
+ 'meetingStatus': meetingStatusString,
56
+ 'organizer': self.organizer,
57
+ 'requiredAttendees': self.to,
58
+ 'optionalAttendees': self.cc,
59
+ 'resources': self.bcc,
60
+ 'start': self.startDate.__format__(self.datetimeFormat) if self.endDate else None,
61
+ 'end': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
62
+ })
63
+
64
+ @functools.cached_property
65
+ def appointmentCounterProposal(self) -> bool:
66
+ """
67
+ Indicates to the organizer that there are counter proposals that have
68
+ not been accepted or rejected by the organizer.
69
+ """
70
+ return bool(self.getNamedProp('8257', ps.PSETID_APPOINTMENT))
71
+
72
+ @functools.cached_property
73
+ def appointmentLastSequence(self) -> Optional[int]:
74
+ """
75
+ The last sequence number that was sent to any attendee.
76
+ """
77
+ return self.getNamedProp('8203', ps.PSETID_APPOINTMENT)
78
+
79
+ @functools.cached_property
80
+ def appointmentProposalNumber(self) -> Optional[int]:
81
+ """
82
+ The number of attendees who have sent counter propostals that have not
83
+ been accepted or rejected by the organizer.
84
+ """
85
+ return self.getNamedProp('8259', ps.PSETID_APPOINTMENT)
86
+
87
+ @functools.cached_property
88
+ def appointmentReplyName(self) -> Optional[datetime.datetime]:
89
+ """
90
+ The user who last replied to the meeting request or meeting update.
91
+ """
92
+ return self.getNamedProp('8230', ps.PSETID_APPOINTMENT)
93
+
94
+ @functools.cached_property
95
+ def appointmentReplyTime(self) -> Optional[datetime.datetime]:
96
+ """
97
+ The date and time at which the attendee responded to a received Meeting
98
+ Request object of Meeting Update object in UTC.
99
+ """
100
+ return self.getNamedProp('8220', ps.PSETID_APPOINTMENT)
101
+
102
+ @functools.cached_property
103
+ def appointmentSequenceTime(self) -> Optional[datetime.datetime]:
104
+ """
105
+ The date and time at which the appointmentSequence property was last
106
+ modified.
107
+ """
108
+ return self.getNamedProp('8202', ps.PSETID_APPOINTMENT)
109
+
110
+ @functools.cached_property
111
+ def autoFillLocation(self) -> bool:
112
+ """
113
+ A value of True indicates that the value of the location property is set
114
+ to the value of the displayName property from the recipientRow structure
115
+ that represents a Resource object.
116
+
117
+ A value of ``False`` indicates that the value of the location property
118
+ is not automatically set.
119
+ """
120
+ return bool(self.getNamedProp('823A', ps.PSETID_APPOINTMENT))
121
+
122
+ @functools.cached_property
123
+ def fInvited(self) -> bool:
124
+ """
125
+ Whether a Meeting Request object has been sent out.
126
+ """
127
+ return bool(self.getNamedProp('8229', ps.PSETID_APPOINTMENT))
128
+
129
+ @property
130
+ def headerFormatProperties(self) -> HEADER_FORMAT_TYPE:
131
+ meetingStatusString = {
132
+ ResponseStatus.NONE: None,
133
+ ResponseStatus.ORGANIZED: 'Meeting organizer',
134
+ ResponseStatus.TENTATIVE: 'Tentatively accepted',
135
+ ResponseStatus.ACCEPTED: 'Accepted',
136
+ ResponseStatus.DECLINED: 'Declined',
137
+ ResponseStatus.NOT_RESPONDED: 'Not yet responded',
138
+ }[self.responseStatus]
139
+
140
+ # Get the recurrence string.
141
+ recur = '(none)'
142
+ if self.appointmentRecur:
143
+ recur = {
144
+ RecurPatternType.DAY: 'Daily',
145
+ RecurPatternType.WEEK: 'Weekly',
146
+ RecurPatternType.MONTH: 'Monthly',
147
+ RecurPatternType.MONTH_NTH: 'Monthly',
148
+ RecurPatternType.MONTH_END: 'Monthly',
149
+ RecurPatternType.HJ_MONTH: 'Monthly',
150
+ RecurPatternType.HJ_MONTH_NTH: 'Monthly',
151
+ RecurPatternType.HJ_MONTH_END: 'Monthly',
152
+ }[self.appointmentRecur.patternType]
153
+
154
+ return {
155
+ '-main info-': {
156
+ 'Subject': self.subject,
157
+ 'Location': self.location,
158
+ },
159
+ '-date-': {
160
+ 'Start': self.startDate.__format__(self.datetimeFormat) if self.startDate else None,
161
+ 'End': self.endDate.__format__(self.datetimeFormat) if self.endDate else None,
162
+ },
163
+ '-recurrence-': {
164
+ 'Recurrance': recur,
165
+ 'Recurrence Pattern': self.recurrencePattern,
166
+ },
167
+ '-status-': {
168
+ 'Meeting Status': meetingStatusString,
169
+ },
170
+ '-attendees-': {
171
+ 'Organizer': self.organizer,
172
+ 'Required Attendees': self.to,
173
+ 'Optional Attendees': self.cc,
174
+ 'Resources': self.bcc,
175
+ },
176
+ '-importance-': {
177
+ 'Importance': self.importanceString,
178
+ },
179
+ }
180
+
181
+ @functools.cached_property
182
+ def isMeeting(self) -> bool:
183
+ """
184
+ Attempts to determine if the object is a Meeting.
185
+
186
+ ``True`` if meeting, ``False`` if appointment.
187
+ """
188
+ return bool(self.appointmentStateFlags) and (AppointmentStateFlag.MEETING in self.appointmentStateFlags)
189
+
190
+ @functools.cached_property
191
+ def originalStoreEntryID(self) -> Optional[EntryID]:
192
+ """
193
+ The EntryID of the delegator's message store.
194
+ """
195
+ return self.getNamedAs('8237', ps.PSETID_APPOINTMENT, EntryID.autoCreate)
@@ -0,0 +1,92 @@
1
+ __all__ = [
2
+ 'Calendar',
3
+ ]
4
+
5
+
6
+ import datetime
7
+ import functools
8
+
9
+ from typing import Optional
10
+
11
+ from ..constants import ps
12
+ from .calendar_base import CalendarBase
13
+ from ..enums import ClientIntentFlag
14
+
15
+
16
+ class Calendar(CalendarBase):
17
+ """
18
+ A calendar object.
19
+ """
20
+
21
+ @functools.cached_property
22
+ def clientIntent(self) -> Optional[ClientIntentFlag]:
23
+ """
24
+ A set of the actions a user has taken on a Meeting object.
25
+ """
26
+ return self.getNamedAs('0015', ps.PSETID_CALENDAR_ASSISTANT, ClientIntentFlag)
27
+
28
+ @functools.cached_property
29
+ def fExceptionalAttendees(self) -> Optional[bool]:
30
+ """
31
+ Indicates that it is a Recurring Calendar object with one or more
32
+ excpetions and that at least one of the Exception Embedded Message
33
+ objects has at least one RecipientRow structure.
34
+
35
+ SHOULD NOT be set for any Calendar object other than that of the
36
+ organizer's.
37
+ """
38
+ return self.getNamedProp('822B', ps.PSETID_APPOINTMENT)
39
+
40
+ @functools.cached_property
41
+ def reminderDelta(self) -> Optional[int]:
42
+ """
43
+ The interval, in minutes, between the time at which the reminder first
44
+ becomes overdue and the start time of the Calendar object.
45
+ """
46
+ return self.getNamedProp('8501', ps.PSETID_COMMON)
47
+
48
+ @functools.cached_property
49
+ def reminderFileParameter(self) -> Optional[str]:
50
+ """
51
+ The full path (MAY only specify the file name) of the sound that a
52
+ client SHOULD play when the reminder for the Message Object becomes
53
+ overdue.
54
+ """
55
+ return self.getNamedProp('851F', ps.PSETID_COMMON)
56
+
57
+ @functools.cached_property
58
+ def reminderOverride(self) -> bool:
59
+ """
60
+ Specifies if clients SHOULD respect the value of the reminderPlaySound
61
+ property and the reminderFileParameter property.
62
+ """
63
+ return bool(self.getNamedProp('851C', ps.PSETID_COMMON))
64
+
65
+ @functools.cached_property
66
+ def reminderPlaySound(self) -> bool:
67
+ """
68
+ Specified that the cliebnt should play a sound when the reminder becomes
69
+ overdue.
70
+ """
71
+ return bool(self.getNamedProp('851E', ps.PSETID_COMMON))
72
+
73
+ @functools.cached_property
74
+ def reminderSet(self) -> bool:
75
+ """
76
+ Specifies whether a reminder is set on the object.
77
+ """
78
+ return bool(self.getNamedProp('8503', ps.PSETID_COMMON))
79
+
80
+ @functools.cached_property
81
+ def reminderSignalTime(self) -> Optional[datetime.datetime]:
82
+ """
83
+ The point in time when a reminder transitions from pending to overdue.
84
+ """
85
+ return self.getNamedProp('8560', ps.PSETID_COMMON)
86
+
87
+ @functools.cached_property
88
+ def reminderTime(self) -> Optional[datetime.datetime]:
89
+ """
90
+ The time after which the user would be late.
91
+ """
92
+ return self.getNamedProp('8502', ps.PSETID_COMMON)