plain 0.24.0__py3-none-any.whl → 0.25.0__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.
plain/utils/dateformat.py DELETED
@@ -1,330 +0,0 @@
1
- """
2
- PHP date() style date formatting
3
- See https://www.php.net/date for format strings
4
-
5
- Usage:
6
- >>> from datetime import datetime
7
- >>> d = datetime.now()
8
- >>> df = DateFormat(d)
9
- >>> print(df.format('jS F Y H:i'))
10
- 7th October 2003 11:39
11
- >>>
12
- """
13
-
14
- import calendar
15
- from datetime import date, datetime, time
16
- from email.utils import format_datetime as format_datetime_rfc5322
17
-
18
- from plain.utils.dates import (
19
- MONTHS,
20
- MONTHS_3,
21
- MONTHS_ALT,
22
- MONTHS_AP,
23
- WEEKDAYS,
24
- WEEKDAYS_ABBR,
25
- )
26
- from plain.utils.regex_helper import _lazy_re_compile
27
- from plain.utils.timezone import (
28
- _datetime_ambiguous_or_imaginary,
29
- get_default_timezone,
30
- is_naive,
31
- make_aware,
32
- )
33
-
34
- re_formatchars = _lazy_re_compile(r"(?<!\\)([aAbcdDeEfFgGhHiIjlLmMnNoOPrsStTUuwWyYzZ])")
35
- re_escaped = _lazy_re_compile(r"\\(.)")
36
-
37
-
38
- class Formatter:
39
- def format(self, formatstr):
40
- pieces = []
41
- for i, piece in enumerate(re_formatchars.split(str(formatstr))):
42
- if i % 2:
43
- if type(self.data) is date and hasattr(TimeFormat, piece):
44
- raise TypeError(
45
- "The format for date objects may not contain "
46
- f"time-related format specifiers (found '{piece}')."
47
- )
48
- pieces.append(str(getattr(self, piece)()))
49
- elif piece:
50
- pieces.append(re_escaped.sub(r"\1", piece))
51
- return "".join(pieces)
52
-
53
-
54
- class TimeFormat(Formatter):
55
- def __init__(self, obj):
56
- self.data = obj
57
- self.timezone = None
58
-
59
- if isinstance(obj, datetime):
60
- # Timezone is only supported when formatting datetime objects, not
61
- # date objects (timezone information not appropriate), or time
62
- # objects (against established Plain policy).
63
- if is_naive(obj):
64
- timezone = get_default_timezone()
65
- else:
66
- timezone = obj.tzinfo
67
- if not _datetime_ambiguous_or_imaginary(obj, timezone):
68
- self.timezone = timezone
69
-
70
- def a(self):
71
- "'a.m.' or 'p.m.'"
72
- if self.data.hour > 11:
73
- return "p.m."
74
- return "a.m."
75
-
76
- def A(self):
77
- "'AM' or 'PM'"
78
- if self.data.hour > 11:
79
- return "PM"
80
- return "AM"
81
-
82
- def e(self):
83
- """
84
- Timezone name.
85
-
86
- If timezone information is not available, return an empty string.
87
- """
88
- if not self.timezone:
89
- return ""
90
-
91
- try:
92
- if getattr(self.data, "tzinfo", None):
93
- return self.data.tzname() or ""
94
- except NotImplementedError:
95
- pass
96
- return ""
97
-
98
- def f(self):
99
- """
100
- Time, in 12-hour hours and minutes, with minutes left off if they're
101
- zero.
102
- Examples: '1', '1:30', '2:05', '2'
103
- Proprietary extension.
104
- """
105
- hour = self.data.hour % 12 or 12
106
- minute = self.data.minute
107
- return "%d:%02d" % (hour, minute) if minute else hour # noqa: UP031
108
-
109
- def g(self):
110
- "Hour, 12-hour format without leading zeros; i.e. '1' to '12'"
111
- return self.data.hour % 12 or 12
112
-
113
- def G(self):
114
- "Hour, 24-hour format without leading zeros; i.e. '0' to '23'"
115
- return self.data.hour
116
-
117
- def h(self):
118
- "Hour, 12-hour format; i.e. '01' to '12'"
119
- return "%02d" % (self.data.hour % 12 or 12) # noqa: UP031
120
-
121
- def H(self):
122
- "Hour, 24-hour format; i.e. '00' to '23'"
123
- return "%02d" % self.data.hour # noqa: UP031
124
-
125
- def i(self):
126
- "Minutes; i.e. '00' to '59'"
127
- return "%02d" % self.data.minute # noqa: UP031
128
-
129
- def O(self): # NOQA: E743, E741
130
- """
131
- Difference to Greenwich time in hours; e.g. '+0200', '-0430'.
132
-
133
- If timezone information is not available, return an empty string.
134
- """
135
- if self.timezone is None:
136
- return ""
137
-
138
- offset = self.timezone.utcoffset(self.data)
139
- seconds = offset.days * 86400 + offset.seconds
140
- sign = "-" if seconds < 0 else "+"
141
- seconds = abs(seconds)
142
- return "%s%02d%02d" % (sign, seconds // 3600, (seconds // 60) % 60) # noqa: UP031
143
-
144
- def P(self):
145
- """
146
- Time, in 12-hour hours, minutes and 'a.m.'/'p.m.', with minutes left off
147
- if they're zero and the strings 'midnight' and 'noon' if appropriate.
148
- Examples: '1 a.m.', '1:30 p.m.', 'midnight', 'noon', '12:30 p.m.'
149
- Proprietary extension.
150
- """
151
- if self.data.minute == 0 and self.data.hour == 0:
152
- return "midnight"
153
- if self.data.minute == 0 and self.data.hour == 12:
154
- return "noon"
155
- return f"{self.f()} {self.a()}"
156
-
157
- def s(self):
158
- "Seconds; i.e. '00' to '59'"
159
- return "%02d" % self.data.second # noqa: UP031
160
-
161
- def T(self):
162
- """
163
- Time zone of this machine; e.g. 'EST' or 'MDT'.
164
-
165
- If timezone information is not available, return an empty string.
166
- """
167
- if self.timezone is None:
168
- return ""
169
-
170
- return str(self.timezone.tzname(self.data))
171
-
172
- def u(self):
173
- "Microseconds; i.e. '000000' to '999999'"
174
- return "%06d" % self.data.microsecond # noqa: UP031
175
-
176
- def Z(self):
177
- """
178
- Time zone offset in seconds (i.e. '-43200' to '43200'). The offset for
179
- timezones west of UTC is always negative, and for those east of UTC is
180
- always positive.
181
-
182
- If timezone information is not available, return an empty string.
183
- """
184
- if self.timezone is None:
185
- return ""
186
-
187
- offset = self.timezone.utcoffset(self.data)
188
-
189
- # `offset` is a datetime.timedelta. For negative values (to the west of
190
- # UTC) only days can be negative (days=-1) and seconds are always
191
- # positive. e.g. UTC-1 -> timedelta(days=-1, seconds=82800, microseconds=0)
192
- # Positive offsets have days=0
193
- return offset.days * 86400 + offset.seconds
194
-
195
-
196
- class DateFormat(TimeFormat):
197
- def b(self):
198
- "Month, textual, 3 letters, lowercase; e.g. 'jan'"
199
- return MONTHS_3[self.data.month]
200
-
201
- def c(self):
202
- """
203
- ISO 8601 Format
204
- Example : '2008-01-02T10:30:00.000123'
205
- """
206
- return self.data.isoformat()
207
-
208
- def d(self):
209
- "Day of the month, 2 digits with leading zeros; i.e. '01' to '31'"
210
- return "%02d" % self.data.day # noqa: UP031
211
-
212
- def D(self):
213
- "Day of the week, textual, 3 letters; e.g. 'Fri'"
214
- return WEEKDAYS_ABBR[self.data.weekday()]
215
-
216
- def E(self):
217
- "Alternative month names as required by some locales. Proprietary extension."
218
- return MONTHS_ALT[self.data.month]
219
-
220
- def F(self):
221
- "Month, textual, long; e.g. 'January'"
222
- return MONTHS[self.data.month]
223
-
224
- def I(self): # NOQA: E743, E741
225
- "'1' if daylight saving time, '0' otherwise."
226
- if self.timezone is None:
227
- return ""
228
- return "1" if self.timezone.dst(self.data) else "0"
229
-
230
- def j(self):
231
- "Day of the month without leading zeros; i.e. '1' to '31'"
232
- return self.data.day
233
-
234
- def l(self): # NOQA: E743, E741
235
- "Day of the week, textual, long; e.g. 'Friday'"
236
- return WEEKDAYS[self.data.weekday()]
237
-
238
- def L(self):
239
- "Boolean for whether it is a leap year; i.e. True or False"
240
- return calendar.isleap(self.data.year)
241
-
242
- def m(self):
243
- "Month; i.e. '01' to '12'"
244
- return "%02d" % self.data.month # noqa: UP031
245
-
246
- def M(self):
247
- "Month, textual, 3 letters; e.g. 'Jan'"
248
- return MONTHS_3[self.data.month].title()
249
-
250
- def n(self):
251
- "Month without leading zeros; i.e. '1' to '12'"
252
- return self.data.month
253
-
254
- def N(self):
255
- "Month abbreviation in Associated Press style. Proprietary extension."
256
- return MONTHS_AP[self.data.month]
257
-
258
- def o(self):
259
- "ISO 8601 year number matching the ISO week number (W)"
260
- return self.data.isocalendar().year
261
-
262
- def r(self):
263
- "RFC 5322 formatted date; e.g. 'Thu, 21 Dec 2000 16:01:07 +0200'"
264
- value = self.data
265
- if not isinstance(value, datetime):
266
- # Assume midnight in default timezone if datetime.date provided.
267
- default_timezone = get_default_timezone()
268
- value = datetime.combine(value, time.min).replace(tzinfo=default_timezone)
269
- elif is_naive(value):
270
- value = make_aware(value, timezone=self.timezone)
271
- return format_datetime_rfc5322(value)
272
-
273
- def S(self):
274
- """
275
- English ordinal suffix for the day of the month, 2 characters; i.e.
276
- 'st', 'nd', 'rd' or 'th'.
277
- """
278
- if self.data.day in (11, 12, 13): # Special case
279
- return "th"
280
- last = self.data.day % 10
281
- if last == 1:
282
- return "st"
283
- if last == 2:
284
- return "nd"
285
- if last == 3:
286
- return "rd"
287
- return "th"
288
-
289
- def t(self):
290
- "Number of days in the given month; i.e. '28' to '31'"
291
- return calendar.monthrange(self.data.year, self.data.month)[1]
292
-
293
- def U(self):
294
- "Seconds since the Unix epoch (January 1 1970 00:00:00 GMT)"
295
- value = self.data
296
- if not isinstance(value, datetime):
297
- value = datetime.combine(value, time.min)
298
- return int(value.timestamp())
299
-
300
- def w(self):
301
- "Day of the week, numeric, i.e. '0' (Sunday) to '6' (Saturday)"
302
- return (self.data.weekday() + 1) % 7
303
-
304
- def W(self):
305
- "ISO-8601 week number of year, weeks starting on Monday"
306
- return self.data.isocalendar().week
307
-
308
- def y(self):
309
- """Year, 2 digits with leading zeros; e.g. '99'."""
310
- return "%02d" % (self.data.year % 100) # noqa: UP031
311
-
312
- def Y(self):
313
- """Year, 4 digits with leading zeros; e.g. '1999'."""
314
- return "%04d" % self.data.year # noqa: UP031
315
-
316
- def z(self):
317
- """Day of the year, i.e. 1 to 366."""
318
- return self.data.timetuple().tm_yday
319
-
320
-
321
- def format(value, format_string):
322
- "Convenience function"
323
- df = DateFormat(value)
324
- return df.format(format_string)
325
-
326
-
327
- def time_format(value, format_string):
328
- "Convenience function"
329
- tf = TimeFormat(value)
330
- return tf.format(format_string)
plain/utils/dates.py DELETED
@@ -1,76 +0,0 @@
1
- "Commonly-used date structures"
2
-
3
- WEEKDAYS = {
4
- 0: "Monday",
5
- 1: "Tuesday",
6
- 2: "Wednesday",
7
- 3: "Thursday",
8
- 4: "Friday",
9
- 5: "Saturday",
10
- 6: "Sunday",
11
- }
12
- WEEKDAYS_ABBR = {
13
- 0: "Mon",
14
- 1: "Tue",
15
- 2: "Wed",
16
- 3: "Thu",
17
- 4: "Fri",
18
- 5: "Sat",
19
- 6: "Sun",
20
- }
21
- MONTHS = {
22
- 1: "January",
23
- 2: "February",
24
- 3: "March",
25
- 4: "April",
26
- 5: "May",
27
- 6: "June",
28
- 7: "July",
29
- 8: "August",
30
- 9: "September",
31
- 10: "October",
32
- 11: "November",
33
- 12: "December",
34
- }
35
- MONTHS_3 = {
36
- 1: "jan",
37
- 2: "feb",
38
- 3: "mar",
39
- 4: "apr",
40
- 5: "may",
41
- 6: "jun",
42
- 7: "jul",
43
- 8: "aug",
44
- 9: "sep",
45
- 10: "oct",
46
- 11: "nov",
47
- 12: "dec",
48
- }
49
- MONTHS_AP = { # month names in Associated Press style
50
- 1: "Jan.",
51
- 2: "Feb.",
52
- 3: "March",
53
- 4: "April",
54
- 5: "May",
55
- 6: "June",
56
- 7: "July",
57
- 8: "Aug.",
58
- 9: "Sept.",
59
- 10: "Oct.",
60
- 11: "Nov.",
61
- 12: "Dec.",
62
- }
63
- MONTHS_ALT = { # required for long date representation by some locales
64
- 1: "January",
65
- 2: "February",
66
- 3: "March",
67
- 4: "April",
68
- 5: "May",
69
- 6: "June",
70
- 7: "July",
71
- 8: "August",
72
- 9: "September",
73
- 10: "October",
74
- 11: "November",
75
- 12: "December",
76
- }
plain/utils/email.py DELETED
@@ -1,12 +0,0 @@
1
- def normalize_email(email):
2
- """
3
- Normalize the email address by lowercasing the domain part of it.
4
- """
5
- email = email or ""
6
- try:
7
- email_name, domain_part = email.strip().rsplit("@", 1)
8
- except ValueError:
9
- pass
10
- else:
11
- email = email_name + "@" + domain_part.lower()
12
- return email
File without changes