or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# DateTimeRange

1

2

DateTimeRange is a Python library for comprehensive datetime range handling. It provides functionality for creating, validating, manipulating, and performing operations on datetime ranges, including containment checking, intersection calculation, range arithmetic, iteration, and time range transformations.

3

4

## Package Information

5

6

- **Package Name**: DateTimeRange

7

- **Language**: Python

8

- **Installation**: `pip install DateTimeRange`

9

- **Import Name**: `datetimerange`

10

- **Version**: 1.2.0

11

12

## Core Imports

13

14

```python

15

from datetimerange import DateTimeRange

16

```

17

18

Version and metadata information:

19

```python

20

from datetimerange import __version__, __author__, __license__

21

```

22

23

Type imports for advanced usage:

24

```python

25

from typing import List, Optional, Union

26

```

27

28

## Basic Usage

29

30

### Creating DateTime Ranges

31

32

```python

33

from datetimerange import DateTimeRange

34

import datetime

35

36

# From string representations

37

time_range = DateTimeRange(

38

"2015-03-22T10:00:00+0900",

39

"2015-03-22T10:10:00+0900"

40

)

41

print(time_range)

42

# Output: 2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900

43

44

# From datetime objects

45

start = datetime.datetime(2015, 3, 22, 10, 0, 0)

46

end = datetime.datetime(2015, 3, 22, 10, 10, 0)

47

time_range = DateTimeRange(start, end)

48

49

# From range text

50

time_range = DateTimeRange.from_range_text(

51

"2015-03-22T10:00:00+0900 - 2015-03-22T10:10:00+0900"

52

)

53

```

54

55

### Basic Range Operations

56

57

```python

58

# Check containment

59

print("2015-03-22T10:05:00+0900" in time_range) # True

60

print("2015-03-22T10:15:00+0900" in time_range) # False

61

62

# Get intersection with another range

63

other_range = DateTimeRange(

64

"2015-03-22T10:05:00+0900",

65

"2015-03-22T10:15:00+0900"

66

)

67

intersection = time_range.intersection(other_range)

68

print(intersection)

69

# Output: 2015-03-22T10:05:00+0900 - 2015-03-22T10:10:00+0900

70

71

# Iterate through range

72

import datetime

73

for dt in time_range.range(datetime.timedelta(minutes=2)):

74

print(dt)

75

```

76

77

## Architecture

78

79

The DateTimeRange library is built around a single main class that encapsulates all datetime range functionality:

80

81

- **DateTimeRange Class**: Central class providing all range operations with comprehensive validation and manipulation capabilities

82

- **Properties**: Immutable access to start/end datetimes and calculated timedelta

83

- **Validation**: Built-in time inversion checking and range validity validation

84

- **Operations**: Full suite of mathematical operations (intersection, subtraction, encompassing)

85

- **Iteration**: Generator-based range iteration with flexible step intervals

86

- **Conversion**: Flexible string formatting and parsing with timezone support

87

88

## Capabilities

89

90

### Range Creation and Configuration

91

92

Create and configure datetime ranges with flexible input formats and output customization.

93

94

```python { .api }

95

class DateTimeRange:

96

# Class constants

97

NOT_A_TIME_STR = "NaT" # String representation for invalid/null time values

98

99

def __init__(

100

self,

101

start_datetime=None,

102

end_datetime=None,

103

start_time_format="%Y-%m-%dT%H:%M:%S%z",

104

end_time_format="%Y-%m-%dT%H:%M:%S%z"

105

):

106

"""

107

Initialize a DateTimeRange instance.

108

109

Parameters:

110

- start_datetime: datetime/str, start time of the range (None for empty)

111

- end_datetime: datetime/str, end time of the range (None for empty)

112

- start_time_format: str, strftime format for start datetime string conversion

113

- end_time_format: str, strftime format for end datetime string conversion

114

"""

115

116

@classmethod

117

def from_range_text(

118

cls,

119

range_text: str,

120

separator: str = "-",

121

start_time_format: Optional[str] = None,

122

end_time_format: Optional[str] = None,

123

) -> "DateTimeRange":

124

"""

125

Create DateTimeRange from text representation.

126

127

Parameters:

128

- range_text: str, text containing datetime range (e.g., "2021-01-01T10:00:00 - 2021-01-01T11:00:00")

129

- separator: str, text separating start and end times (default "-")

130

- start_time_format: Optional[str], strftime format for start time

131

- end_time_format: Optional[str], strftime format for end time

132

133

Returns:

134

DateTimeRange instance created from the text

135

"""

136

```

137

138

Configuration attributes:

139

```python { .api }

140

# Instance attributes for customization

141

time_range.start_time_format = "%Y/%m/%d %H:%M:%S" # str: start datetime format

142

time_range.end_time_format = "%Y/%m/%d %H:%M:%S" # str: end datetime format

143

time_range.is_output_elapse = True # bool: show elapsed time in repr

144

time_range.separator = " to " # str: separator in string representation

145

```

146

147

### Range Properties and Access

148

149

Access range boundaries and calculated values.

150

151

```python { .api }

152

@property

153

def start_datetime(self) -> datetime.datetime:

154

"""

155

Start time of the range.

156

157

Returns:

158

datetime.datetime object representing the start time

159

"""

160

161

@property

162

def end_datetime(self) -> datetime.datetime:

163

"""

164

End time of the range.

165

166

Returns:

167

datetime.datetime object representing the end time

168

"""

169

170

@property

171

def timedelta(self) -> datetime.timedelta:

172

"""

173

Time difference between end and start.

174

175

Returns:

176

datetime.timedelta representing (end_datetime - start_datetime)

177

"""

178

```

179

180

### Range Validation and State Checking

181

182

Validate range integrity and check range state.

183

184

```python { .api }

185

def is_set(self) -> bool:

186

"""

187

Check if both start and end datetimes are set.

188

189

Returns:

190

bool: True if both start_datetime and end_datetime are not None

191

"""

192

193

def is_valid_timerange(self) -> bool:

194

"""

195

Check if the time range is valid.

196

197

Returns:

198

bool: True if range is set and has no time inversion

199

"""

200

201

def validate_time_inversion(self) -> None:

202

"""

203

Validate that start time is not greater than end time.

204

205

Raises:

206

ValueError: if start_datetime > end_datetime

207

TypeError: if either datetime is invalid

208

"""

209

```

210

211

### Range Manipulation and Modification

212

213

Modify range boundaries and transform ranges.

214

215

```python { .api }

216

def set_start_datetime(self, value, timezone=None) -> None:

217

"""

218

Set the start datetime of the range.

219

220

Parameters:

221

- value: datetime/str, new start datetime

222

- timezone: Optional[timezone], timezone for string conversion (used when value is string)

223

224

Raises:

225

ValueError: if value is invalid datetime

226

"""

227

228

def set_end_datetime(self, value, timezone=None) -> None:

229

"""

230

Set the end datetime of the range.

231

232

Parameters:

233

- value: datetime/str, new end datetime

234

- timezone: Optional[timezone], timezone for string conversion (used when value is string)

235

236

Raises:

237

ValueError: if value is invalid datetime

238

"""

239

240

def set_time_range(self, start, end) -> None:

241

"""

242

Set both start and end datetimes.

243

244

Parameters:

245

- start: datetime/str, start datetime

246

- end: datetime/str, end datetime

247

"""

248

249

def truncate(self, percentage: float) -> None:

250

"""

251

Truncate percentage/2 of time from both ends of range.

252

253

Parameters:

254

- percentage: float, percentage to truncate (0-100)

255

256

Raises:

257

ValueError: if percentage < 0

258

"""

259

```

260

261

### Range Operations and Set Theory

262

263

Perform mathematical operations between ranges.

264

265

```python { .api }

266

def intersection(self, x: "DateTimeRange") -> "DateTimeRange":

267

"""

268

Get intersection (overlap) of this range with another.

269

270

Parameters:

271

- x: DateTimeRange, range to intersect with

272

273

Returns:

274

DateTimeRange representing the overlapping time period

275

"""

276

277

def is_intersection(self, x: "DateTimeRange") -> bool:

278

"""

279

Check if this range intersects with another.

280

281

Parameters:

282

- x: DateTimeRange, range to check intersection with

283

284

Returns:

285

bool: True if ranges overlap

286

"""

287

288

def subtract(self, x: "DateTimeRange") -> List["DateTimeRange"]:

289

"""

290

Remove another range from this one.

291

292

Parameters:

293

- x: DateTimeRange, range to subtract from this range

294

295

Returns:

296

List[DateTimeRange]: remaining ranges after subtraction

297

- Empty list if x wholly encompasses this range

298

- [self.copy()] if no overlap

299

- [new_range] if partial overlap

300

- [range1, range2] if x creates a gap in the middle

301

"""

302

303

def encompass(self, x: "DateTimeRange") -> "DateTimeRange":

304

"""

305

Create range that encompasses both this range and another.

306

307

Parameters:

308

- x: DateTimeRange, range to encompass with this range

309

310

Returns:

311

DateTimeRange spanning from earliest start to latest end

312

"""

313

314

def split(self, separator: Union[str, datetime.datetime]) -> List["DateTimeRange"]:

315

"""

316

Split range into two ranges at specified datetime.

317

318

Parameters:

319

- separator: Union[str, datetime.datetime], datetime to split the range at

320

321

Returns:

322

List[DateTimeRange]:

323

- [original_copy] if separator not in range or at boundaries

324

- [range1, range2] if valid split (separator included in both)

325

"""

326

```

327

328

### Range Containment and Testing

329

330

Test containment relationships between ranges and datetimes.

331

332

```python { .api }

333

def __contains__(self, x) -> bool:

334

"""

335

Test if datetime or range is contained within this range.

336

337

Parameters:

338

- x: datetime/DateTimeRange/str, value to test containment

339

340

Returns:

341

bool: True if x is fully contained within this range

342

343

For DateTimeRange: x.start >= self.start and x.end <= self.end

344

For datetime: self.start <= x <= self.end

345

"""

346

```

347

348

### Range Iteration and Enumeration

349

350

Iterate through ranges with custom step intervals.

351

352

```python { .api }

353

def range(self, step) -> Iterator[datetime.datetime]:

354

"""

355

Generate datetime values from start to end by step interval.

356

357

Parameters:

358

- step: timedelta/relativedelta, interval between generated values

359

360

Returns:

361

Iterator[datetime.datetime]: sequence of datetime values

362

363

Raises:

364

ValueError: if step is zero or has wrong sign for range direction

365

"""

366

```

367

368

### String Conversion and Formatting

369

370

Convert ranges to formatted string representations.

371

372

```python { .api }

373

def get_start_time_str(self) -> str:

374

"""

375

Get formatted string representation of start datetime.

376

377

Returns:

378

str: start_datetime formatted with start_time_format, or "NaT" if invalid

379

"""

380

381

def get_end_time_str(self) -> str:

382

"""

383

Get formatted string representation of end datetime.

384

385

Returns:

386

str: end_datetime formatted with end_time_format, or "NaT" if invalid

387

"""

388

389

def get_timedelta_second(self) -> float:

390

"""

391

Get time difference in seconds.

392

393

Returns:

394

float: total seconds between start and end datetimes

395

"""

396

397

def __repr__(self) -> str:

398

"""

399

String representation of the range.

400

401

Returns:

402

str: formatted as "start_str - end_str" with optional elapsed time

403

"""

404

```

405

406

### Range Arithmetic Operations

407

408

Perform arithmetic operations with timedelta objects.

409

410

```python { .api }

411

def __add__(self, other) -> "DateTimeRange":

412

"""

413

Add timedelta to both start and end times.

414

415

Parameters:

416

- other: timedelta, time amount to add

417

418

Returns:

419

DateTimeRange: new range with both times shifted forward

420

"""

421

422

def __iadd__(self, other) -> "DateTimeRange":

423

"""

424

In-place addition of timedelta to both times.

425

426

Parameters:

427

- other: timedelta, time amount to add

428

429

Returns:

430

DateTimeRange: self after modification

431

"""

432

433

def __sub__(self, other) -> "DateTimeRange":

434

"""

435

Subtract timedelta from both start and end times.

436

437

Parameters:

438

- other: timedelta, time amount to subtract

439

440

Returns:

441

DateTimeRange: new range with both times shifted backward

442

"""

443

444

def __isub__(self, other) -> "DateTimeRange":

445

"""

446

In-place subtraction of timedelta from both times.

447

448

Parameters:

449

- other: timedelta, time amount to subtract

450

451

Returns:

452

DateTimeRange: self after modification

453

"""

454

```

455

456

### Range Comparison Operations

457

458

Compare ranges for equality and inequality.

459

460

```python { .api }

461

def __eq__(self, other) -> bool:

462

"""

463

Test equality with another DateTimeRange.

464

465

Parameters:

466

- other: DateTimeRange, range to compare with

467

468

Returns:

469

bool: True if both start and end datetimes are equal

470

"""

471

472

def __ne__(self, other) -> bool:

473

"""

474

Test inequality with another DateTimeRange.

475

476

Parameters:

477

- other: DateTimeRange, range to compare with

478

479

Returns:

480

bool: True if start or end datetimes differ

481

"""

482

```

483

484

## Type Definitions

485

486

```python { .api }

487

# Core types used throughout the API

488

datetime.datetime # Standard library datetime object

489

datetime.timedelta # Standard library timedelta object

490

str # String type for datetime representations

491

List[T] # Generic list type (from typing module)

492

Iterator[T] # Iterator protocol type (from typing module)

493

bool # Boolean type

494

float # Floating point number type

495

Optional[T] # Type that can be T or None (from typing module)

496

Union[T, U] # Type that can be T or U (from typing module)

497

498

# Time step types for iteration

499

from dateutil.relativedelta import relativedelta # Relative time deltas with months/years

500

501

# Import pattern for extended functionality

502

import datetime

503

from typing import List, Optional, Union, Iterator

504

from dateutil.relativedelta import relativedelta

505

```

506

507

## Usage Examples

508

509

### Time Range Validation and Error Handling

510

511

```python

512

from datetimerange import DateTimeRange

513

514

# Create ranges and validate

515

time_range = DateTimeRange("2015-01-01T10:00:00", "2015-01-01T09:00:00")

516

try:

517

time_range.validate_time_inversion()

518

except ValueError as e:

519

print(f"Invalid range: {e}")

520

# Fix the range

521

time_range.set_time_range("2015-01-01T09:00:00", "2015-01-01T10:00:00")

522

523

print(time_range.is_valid_timerange()) # True

524

```

525

526

### Complex Range Operations

527

528

```python

529

import datetime

530

from datetimerange import DateTimeRange

531

532

# Create overlapping ranges

533

range1 = DateTimeRange("2015-01-01T10:00:00", "2015-01-01T15:00:00")

534

range2 = DateTimeRange("2015-01-01T12:00:00", "2015-01-01T18:00:00")

535

536

# Find intersection

537

overlap = range1.intersection(range2)

538

print(f"Overlap: {overlap}") # 2015-01-01T12:00:00 - 2015-01-01T15:00:00

539

540

# Subtract one range from another

541

remaining = range1.subtract(range2)

542

print(f"Remaining: {remaining}") # [2015-01-01T10:00:00 - 2015-01-01T12:00:00]

543

544

# Create encompassing range

545

combined = range1.encompass(range2)

546

print(f"Combined: {combined}") # 2015-01-01T10:00:00 - 2015-01-01T18:00:00

547

```

548

549

### Range Iteration and Time Series

550

551

```python

552

import datetime

553

from dateutil.relativedelta import relativedelta

554

from datetimerange import DateTimeRange

555

556

# Daily iteration

557

daily_range = DateTimeRange("2015-01-01T00:00:00", "2015-01-07T00:00:00")

558

for day in daily_range.range(datetime.timedelta(days=1)):

559

print(day.strftime("%Y-%m-%d"))

560

561

# Monthly iteration with relativedelta

562

monthly_range = DateTimeRange("2015-01-01T00:00:00", "2015-12-01T00:00:00")

563

for month in monthly_range.range(relativedelta(months=1)):

564

print(month.strftime("%Y-%m"))

565

566

# Hourly iteration for a day

567

hourly_range = DateTimeRange("2015-01-01T00:00:00", "2015-01-02T00:00:00")

568

business_hours = []

569

for hour in hourly_range.range(datetime.timedelta(hours=1)):

570

if 9 <= hour.hour <= 17: # Business hours

571

business_hours.append(hour)

572

```

573

574

### Range Arithmetic and Shifting

575

576

```python

577

import datetime

578

from datetimerange import DateTimeRange

579

580

# Create base range

581

base_range = DateTimeRange("2015-01-01T10:00:00", "2015-01-01T12:00:00")

582

print(f"Original: {base_range}")

583

584

# Shift forward by 2 hours

585

shifted_forward = base_range + datetime.timedelta(hours=2)

586

print(f"Shifted forward: {shifted_forward}")

587

588

# Shift backward by 30 minutes

589

shifted_back = base_range - datetime.timedelta(minutes=30)

590

print(f"Shifted back: {shifted_back}")

591

592

# In-place modification

593

base_range += datetime.timedelta(days=1)

594

print(f"Next day: {base_range}")

595

```

596

597

### Custom Formatting and Display

598

599

```python

600

from datetimerange import DateTimeRange

601

602

# Create range with custom formatting

603

time_range = DateTimeRange("2015-03-22T10:00:00+0900", "2015-03-22T10:10:00+0900")

604

605

# Customize string format

606

time_range.start_time_format = "%Y/%m/%d %H:%M:%S"

607

time_range.end_time_format = "%Y/%m/%d %H:%M:%S"

608

time_range.separator = " to "

609

print(time_range) # 2015/03/22 10:00:00 to 2015/03/22 10:10:00

610

611

# Show elapsed time

612

time_range.is_output_elapse = True

613

print(time_range) # 2015/03/22 10:00:00 to 2015/03/22 10:10:00 (0:10:00)

614

615

# Get individual formatted strings

616

print(f"Start: {time_range.get_start_time_str()}")

617

print(f"End: {time_range.get_end_time_str()}")

618

print(f"Duration: {time_range.get_timedelta_second()} seconds")

619

```