0
# DateTimeRange
1
2
DateTimeRange is a Python library to handle a time range. It provides comprehensive functionality for working with datetime intervals including checking containment, calculating intersections and unions, truncating ranges, splitting ranges, and iterating through time periods with custom intervals.
3
4
## Package Information
5
6
- **Package Name**: DateTimeRange
7
- **Language**: Python
8
- **Installation**: `pip install DateTimeRange`
9
- **Requirements**: Python 3.9+
10
11
## Core Imports
12
13
```python
14
from datetimerange import DateTimeRange
15
```
16
17
## Basic Usage
18
19
```python
20
import datetime
21
from datetimerange import DateTimeRange
22
23
# Create a time range from string representations
24
time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
25
print(time_range) # 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900
26
27
# Create from datetime objects
28
start = datetime.datetime(2015, 3, 22, 10, 0, 0)
29
end = datetime.datetime(2015, 3, 22, 10, 10, 0)
30
time_range = DateTimeRange(start, end)
31
32
# Check if a time is within the range
33
check_time = "2015-03-22T10:05:00+0900"
34
print(check_time in time_range) # True
35
36
# Get intersection of two ranges
37
range1 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
38
range2 = DateTimeRange("2015-03-22T10:05:00+0900", "2015-03-22T10:15:00+0900")
39
intersection = range1.intersection(range2)
40
print(intersection) # 2015-03-22T10:05:00+0900 - 2015-03-22T10:10:00+0900
41
42
# Iterate through time range with step intervals
43
import datetime
44
for time_point in time_range.range(datetime.timedelta(minutes=2)):
45
print(time_point)
46
```
47
48
## Capabilities
49
50
### Range Creation and Initialization
51
52
Create DateTimeRange instances from various input formats including datetime objects, ISO strings, and range text.
53
54
```python { .api }
55
class DateTimeRange:
56
def __init__(
57
self,
58
start_datetime: Union[datetime.datetime, str, None] = None,
59
end_datetime: Union[datetime.datetime, str, None] = None,
60
start_time_format: Optional[str] = None,
61
end_time_format: Optional[str] = None,
62
timezone: Optional[datetime.tzinfo] = None,
63
) -> None:
64
"""
65
Initialize a DateTimeRange instance.
66
67
Args:
68
start_datetime: Start time as datetime object or ISO string
69
end_datetime: End time as datetime object or ISO string
70
start_time_format: Custom format string for start time display
71
end_time_format: Custom format string for end time display
72
timezone: Timezone information for the range
73
"""
74
75
@classmethod
76
def from_range_text(
77
cls,
78
range_text: str,
79
separator: str = r"\s+\-\s+",
80
start_time_format: Optional[str] = None,
81
end_time_format: Optional[str] = None,
82
timezone: Optional[datetime.tzinfo] = None,
83
) -> "DateTimeRange":
84
"""
85
Create DateTimeRange from text representation.
86
87
Args:
88
range_text: Text containing datetime range (e.g., "2021-01-23T10:00:00+0400 - 2021-01-23T10:10:00+0400")
89
separator: Regex pattern separating start and end times
90
start_time_format: Custom format for start time
91
end_time_format: Custom format for end time
92
timezone: Timezone information
93
94
Returns:
95
DateTimeRange: New instance from parsed text
96
"""
97
```
98
99
### Properties and Attributes
100
101
Access datetime range properties and configure display formats.
102
103
```python { .api }
104
@property
105
def start_datetime(self) -> Optional[datetime.datetime]:
106
"""Start time of the range."""
107
108
@property
109
def end_datetime(self) -> Optional[datetime.datetime]:
110
"""End time of the range."""
111
112
@property
113
def timezone(self) -> Optional[datetime.tzinfo]:
114
"""Timezone information of the range."""
115
116
@property
117
def timedelta(self) -> datetime.timedelta:
118
"""Time difference between end and start as timedelta."""
119
120
# Instance attributes
121
start_time_format: str # Format string for start time display (default: "%Y-%m-%dT%H:%M:%S%z")
122
end_time_format: str # Format string for end time display (default: "%Y-%m-%dT%H:%M:%S%z")
123
is_output_elapse: bool # Whether to show elapsed time in string representation
124
separator: str # Separator string for display (default: " - ")
125
```
126
127
### Time Range Management
128
129
Set and modify datetime range boundaries with validation.
130
131
```python { .api }
132
def set_start_datetime(
133
self,
134
value: Union[datetime.datetime, str, None],
135
timezone: Optional[datetime.tzinfo] = None
136
) -> None:
137
"""
138
Set the start time of the range.
139
140
Args:
141
value: New start datetime or ISO string
142
timezone: Timezone for the datetime
143
144
Raises:
145
ValueError: If value is invalid datetime
146
"""
147
148
def set_end_datetime(
149
self,
150
value: Union[datetime.datetime, str, None],
151
timezone: Optional[datetime.tzinfo] = None
152
) -> None:
153
"""
154
Set the end time of the range.
155
156
Args:
157
value: New end datetime or ISO string
158
timezone: Timezone for the datetime
159
160
Raises:
161
ValueError: If value is invalid datetime
162
"""
163
164
def set_time_range(
165
self,
166
start: Union[datetime.datetime, str, None],
167
end: Union[datetime.datetime, str, None],
168
timezone: Optional[datetime.tzinfo] = None,
169
) -> None:
170
"""
171
Set both start and end times.
172
173
Args:
174
start: Start datetime or ISO string
175
end: End datetime or ISO string
176
timezone: Timezone for both times
177
"""
178
```
179
180
### Validation and Queries
181
182
Validate time ranges and query their properties.
183
184
```python { .api }
185
def is_set(self) -> bool:
186
"""
187
Check if both start and end times are set.
188
189
Returns:
190
bool: True if both times are not None
191
"""
192
193
def is_time_inversion(self, allow_timezone_mismatch: bool = True) -> bool:
194
"""
195
Check if start time is after end time.
196
197
Args:
198
allow_timezone_mismatch: Whether to ignore timezone differences
199
200
Returns:
201
bool: True if start > end
202
"""
203
204
def validate_time_inversion(self, allow_timezone_mismatch: bool = True) -> None:
205
"""
206
Validate time order, raising exception if inverted.
207
208
Args:
209
allow_timezone_mismatch: Whether to ignore timezone differences
210
211
Raises:
212
ValueError: If start time > end time
213
TypeError: If times are not set properly
214
"""
215
216
def is_valid_timerange(self) -> bool:
217
"""
218
Check if range is valid (set and not inverted).
219
220
Returns:
221
bool: True if valid non-null, non-inverted range
222
"""
223
```
224
225
### String Formatting
226
227
Convert datetime ranges to formatted string representations.
228
229
```python { .api }
230
def get_start_time_str(self) -> str:
231
"""
232
Get formatted start time string.
233
234
Returns:
235
str: Formatted start time or "NaT" if invalid
236
"""
237
238
def get_end_time_str(self) -> str:
239
"""
240
Get formatted end time string.
241
242
Returns:
243
str: Formatted end time or "NaT" if invalid
244
"""
245
246
def get_timedelta_second(self) -> float:
247
"""
248
Get time difference in seconds.
249
250
Returns:
251
float: Duration in seconds
252
"""
253
```
254
255
### Range Operations
256
257
Perform set-like operations on datetime ranges including intersection, union, and subtraction.
258
259
```python { .api }
260
def intersection(
261
self,
262
x: "DateTimeRange",
263
intersection_threshold: Union[datetime.timedelta, relativedelta, None] = None,
264
) -> "DateTimeRange":
265
"""
266
Create intersection of two time ranges.
267
268
Args:
269
x: Other DateTimeRange to intersect with
270
intersection_threshold: Minimum overlap duration required
271
272
Returns:
273
DateTimeRange: Overlapping portion or empty range if no overlap
274
"""
275
276
def is_intersection(
277
self,
278
x: "DateTimeRange",
279
intersection_threshold: Union[datetime.timedelta, relativedelta, None] = None,
280
) -> bool:
281
"""
282
Check if ranges intersect.
283
284
Args:
285
x: Other DateTimeRange to check
286
intersection_threshold: Minimum overlap duration required
287
288
Returns:
289
bool: True if ranges overlap
290
"""
291
292
def subtract(self, x: "DateTimeRange") -> list["DateTimeRange"]:
293
"""
294
Remove overlapping portion from this range.
295
296
Args:
297
x: DateTimeRange to subtract
298
299
Returns:
300
list[DateTimeRange]: List of remaining ranges after subtraction
301
"""
302
303
def encompass(self, x: "DateTimeRange") -> "DateTimeRange":
304
"""
305
Create range that encompasses both ranges.
306
307
Args:
308
x: Other DateTimeRange to encompass
309
310
Returns:
311
DateTimeRange: Range spanning from earliest start to latest end
312
"""
313
```
314
315
### Range Manipulation
316
317
Split, truncate, and modify datetime ranges.
318
319
```python { .api }
320
def split(self, separator: Union[str, datetime.datetime]) -> list["DateTimeRange"]:
321
"""
322
Split range at specific datetime.
323
324
Args:
325
separator: Datetime to split at (included in both resulting ranges)
326
327
Returns:
328
list[DateTimeRange]: List of split ranges (1 if separator outside range, 2 if inside)
329
"""
330
331
def truncate(self, percentage: float) -> None:
332
"""
333
Remove percentage/2 from each end of the range.
334
335
Args:
336
percentage: Percentage to truncate (distributed equally from both ends)
337
338
Raises:
339
ValueError: If percentage < 0
340
"""
341
```
342
343
### Range Iteration
344
345
Iterate through datetime ranges with custom step intervals.
346
347
```python { .api }
348
def range(self, step: Union[datetime.timedelta, relativedelta]) -> Iterator[datetime.datetime]:
349
"""
350
Iterate through range with specified step.
351
352
Args:
353
step: Time interval for iteration steps
354
355
Returns:
356
Iterator[datetime.datetime]: Iterator yielding datetime objects
357
358
Raises:
359
ValueError: If step is zero or invalid direction for inverted ranges
360
"""
361
```
362
363
### Membership Testing
364
365
Test if datetimes or ranges are contained within this range.
366
367
```python { .api }
368
def __contains__(self, x: Union[datetime.datetime, "DateTimeRange", str]) -> bool:
369
"""
370
Check if datetime or range is within this range.
371
372
Args:
373
x: Datetime, DateTimeRange, or ISO string to test
374
375
Returns:
376
bool: True if x is contained within this range
377
"""
378
```
379
380
### Arithmetic Operations
381
382
Add and subtract time intervals from datetime ranges.
383
384
```python { .api }
385
def __add__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
386
"""
387
Add time interval to both start and end times.
388
389
Args:
390
other: Time interval to add
391
392
Returns:
393
DateTimeRange: New range shifted forward by interval
394
"""
395
396
def __iadd__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
397
"""
398
Add time interval in-place.
399
400
Args:
401
other: Time interval to add
402
403
Returns:
404
DateTimeRange: Self with modified times
405
"""
406
407
def __sub__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
408
"""
409
Subtract time interval from both start and end times.
410
411
Args:
412
other: Time interval to subtract
413
414
Returns:
415
DateTimeRange: New range shifted backward by interval
416
"""
417
418
def __isub__(self, other: Union[datetime.timedelta, relativedelta]) -> "DateTimeRange":
419
"""
420
Subtract time interval in-place.
421
422
Args:
423
other: Time interval to subtract
424
425
Returns:
426
DateTimeRange: Self with modified times
427
"""
428
```
429
430
### String Representation and Comparison
431
432
Get string representation and compare datetime ranges.
433
434
```python { .api }
435
def __repr__(self) -> str:
436
"""
437
Get string representation of the datetime range.
438
439
Returns:
440
str: Formatted range with optional elapsed time if is_output_elapse is True
441
"""
442
443
def __eq__(self, other: object) -> bool:
444
"""
445
Check equality with another DateTimeRange.
446
447
Args:
448
other: Object to compare with
449
450
Returns:
451
bool: True if both ranges have same start and end times
452
"""
453
454
def __ne__(self, other: object) -> bool:
455
"""
456
Check inequality with another DateTimeRange.
457
458
Args:
459
other: Object to compare with
460
461
Returns:
462
bool: True if ranges differ in start or end times
463
"""
464
```
465
466
## Constants
467
468
```python { .api }
469
# Class constant for invalid time representation
470
NOT_A_TIME_STR: str = "NaT"
471
```
472
473
## Types
474
475
```python { .api }
476
from typing import Union, Optional, Iterator
477
import datetime
478
from dateutil.relativedelta import relativedelta
479
480
# Type aliases used throughout the API
481
TimeValue = Union[datetime.datetime, str, None]
482
TimeDelta = Union[datetime.timedelta, relativedelta]
483
```
484
485
## Common Usage Patterns
486
487
### Creating Ranges
488
489
```python
490
# From ISO strings
491
range1 = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")
492
493
# From datetime objects
494
start = datetime.datetime(2015, 3, 22, 10, 0, 0)
495
end = datetime.datetime(2015, 3, 22, 10, 10, 0)
496
range2 = DateTimeRange(start, end)
497
498
# From range text
499
range3 = DateTimeRange.from_range_text("2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900")
500
```
501
502
### Range Operations
503
504
```python
505
# Intersection
506
overlap = range1.intersection(range2)
507
508
# Union (encompass)
509
combined = range1.encompass(range2)
510
511
# Subtraction
512
remaining = range1.subtract(range2)
513
514
# Membership testing
515
if "2015-03-22T10:05:00+0900" in range1:
516
print("Time is in range")
517
```
518
519
### Iteration and Stepping
520
521
```python
522
# Daily iteration
523
for day in date_range.range(datetime.timedelta(days=1)):
524
print(day)
525
526
# Hourly iteration using relativedelta
527
from dateutil.relativedelta import relativedelta
528
for hour in date_range.range(relativedelta(hours=1)):
529
print(hour)
530
```
531
532
### Timezone Handling
533
534
```python
535
import pytz
536
537
# Create with timezone
538
tz = pytz.timezone('America/New_York')
539
range_tz = DateTimeRange(
540
"2015-03-22T10:00:00",
541
"2015-03-22T12:00:00",
542
timezone=tz
543
)
544
```