or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arithmetic.mdformatting.mdindex.mdinstant.mdlocal-types.mdplatform.mdranges.mdserialization.mdtimezones.md

ranges.mddocs/

0

# Ranges and Progressions

1

2

Range and progression support for date types, enabling iteration, membership testing, and range operations with step control. The library provides specialized range types for LocalDate and YearMonth with comprehensive progression capabilities.

3

4

## Capabilities

5

6

### LocalDateRange

7

8

Range implementation for LocalDate values, supporting inclusive ranges and iteration.

9

10

```kotlin { .api }

11

/**

12

* Range of LocalDate values with inclusive bounds

13

* Extends LocalDateProgression with range semantics

14

*/

15

class LocalDateRange : LocalDateProgression, ClosedRange<LocalDate> {

16

/** Lower bound of the range (inclusive) */

17

override val start: LocalDate

18

19

/** Upper bound of the range (inclusive) */

20

override val endInclusive: LocalDate

21

22

/**

23

* Check if the range contains no elements

24

* @returns true if start > endInclusive

25

*/

26

override fun isEmpty(): Boolean

27

28

companion object {

29

/** Empty range constant */

30

val EMPTY: LocalDateRange

31

}

32

}

33

```

34

35

**Creation:**

36

37

```kotlin { .api }

38

/**

39

* Create inclusive range from start to end

40

* @param that End date (inclusive)

41

* @returns LocalDateRange from this to that

42

*/

43

infix fun LocalDate.rangeTo(that: LocalDate): LocalDateRange

44

45

/**

46

* Create exclusive range from start to end

47

* @param that End date (exclusive)

48

* @returns LocalDateRange from this to that-1

49

*/

50

infix fun LocalDate.rangeUntil(that: LocalDate): LocalDateRange

51

```

52

53

**Usage Examples:**

54

55

```kotlin

56

import kotlinx.datetime.*

57

58

val start = LocalDate(2023, 12, 1)

59

val end = LocalDate(2023, 12, 31)

60

61

// Create ranges

62

val inclusiveRange = start..end // December 1-31, 2023

63

val exclusiveRange = start.rangeUntil(end) // December 1-30, 2023

64

val usingRangeTo = start.rangeTo(end) // Same as start..end

65

66

// Properties

67

println("Start: ${inclusiveRange.start}") // 2023-12-01

68

println("End: ${inclusiveRange.endInclusive}") // 2023-12-31

69

println("Empty: ${inclusiveRange.isEmpty()}") // false

70

71

// Empty range

72

val emptyRange = LocalDateRange.EMPTY

73

println("Empty: ${emptyRange.isEmpty()}") // true

74

75

// Membership testing

76

val christmas = LocalDate(2023, 12, 25)

77

val newYear = LocalDate(2024, 1, 1)

78

79

println("Christmas in range: ${christmas in inclusiveRange}") // true

80

println("New Year in range: ${newYear in inclusiveRange}") // false

81

82

// Iteration

83

println("First week of December:")

84

for (date in start..LocalDate(2023, 12, 7)) {

85

println(" $date (${date.dayOfWeek})")

86

}

87

```

88

89

### LocalDateProgression

90

91

Progression implementation that supports step-based iteration over LocalDate values.

92

93

```kotlin { .api }

94

/**

95

* Progression of LocalDate values with configurable step

96

* Supports forward and backward iteration with date-based steps

97

*/

98

open class LocalDateProgression : Iterable<LocalDate> {

99

/** First date in the progression */

100

val first: LocalDate

101

102

/** Last date that would be included (may not be reached if step doesn't align) */

103

val last: LocalDate

104

105

/** Number of elements in the progression */

106

val size: Int

107

108

/**

109

* Create reversed progression

110

* @returns LocalDateProgression in opposite direction

111

*/

112

fun reversed(): LocalDateProgression

113

114

/**

115

* Change the step of the progression

116

* @param value Step amount

117

* @param unit Date-based unit for the step

118

* @returns New progression with specified step

119

*/

120

fun step(value: Int, unit: DateTimeUnit.DayBased): LocalDateProgression

121

fun step(value: Long, unit: DateTimeUnit.DayBased): LocalDateProgression

122

123

/**

124

* Get a random element from the progression

125

* @param random Random number generator

126

* @returns Random LocalDate from the progression

127

*/

128

fun random(random: Random = Random.Default): LocalDate

129

130

/**

131

* Check if a date is contained in this progression

132

* @param value Date to check

133

* @returns true if the date is part of this progression

134

*/

135

operator fun contains(value: LocalDate): Boolean

136

137

/**

138

* Iterator for stepping through the progression

139

* @returns Iterator over LocalDate values

140

*/

141

override fun iterator(): Iterator<LocalDate>

142

}

143

```

144

145

**Creation Functions:**

146

147

```kotlin { .api }

148

/**

149

* Create descending progression from this date to that date

150

* @param that End date

151

* @returns LocalDateProgression stepping backward by days

152

*/

153

infix fun LocalDate.downTo(that: LocalDate): LocalDateProgression

154

```

155

156

**Usage Examples:**

157

158

```kotlin

159

import kotlinx.datetime.*

160

161

val start = LocalDate(2023, 12, 1)

162

val end = LocalDate(2023, 12, 31)

163

164

// Basic progression (daily steps)

165

val dailyProgression = start..end

166

println("Size: ${dailyProgression.size}") // 31 days

167

168

// Reverse progression

169

val reverseProgression = dailyProgression.reversed()

170

println("Reverse first: ${reverseProgression.first}") // 2023-12-31

171

println("Reverse last: ${reverseProgression.last}") // 2023-12-01

172

173

// Descending progression

174

val countdown = end.downTo(start)

175

println("Countdown from ${countdown.first} to ${countdown.last}")

176

177

// Weekly progression (every 7 days)

178

val weeklyProgression = (start..end).step(7, DateTimeUnit.DAY)

179

println("Weekly progression:")

180

for (date in weeklyProgression) {

181

println(" $date") // Every Sunday in December 2023

182

}

183

184

// Bi-weekly progression (every 14 days)

185

val biWeeklyProgression = (start..end).step(14, DateTimeUnit.DAY)

186

187

// Random selection

188

val randomDate = dailyProgression.random()

189

println("Random date in December: $randomDate")

190

191

// Membership testing

192

println("Christmas in progression: ${LocalDate(2023, 12, 25) in dailyProgression}") // true

193

194

// Custom step with different units (if extending to month-based)

195

val monthlyDates = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 1)

196

// Note: Monthly stepping would require DateTimeUnit.MonthBased support in actual implementation

197

```

198

199

### YearMonthRange

200

201

Range implementation for YearMonth values, supporting month-based ranges.

202

203

```kotlin { .api }

204

/**

205

* Range of YearMonth values with inclusive bounds

206

* Extends YearMonthProgression with range semantics

207

*/

208

class YearMonthRange : YearMonthProgression, ClosedRange<YearMonth> {

209

/** Lower bound of the range (inclusive) */

210

override val start: YearMonth

211

212

/** Upper bound of the range (inclusive) */

213

override val endInclusive: YearMonth

214

215

/**

216

* Check if the range contains no elements

217

* @returns true if start > endInclusive

218

*/

219

override fun isEmpty(): Boolean

220

221

companion object {

222

/** Empty range constant */

223

val EMPTY: YearMonthRange

224

}

225

}

226

```

227

228

**Creation:**

229

230

```kotlin { .api }

231

/**

232

* Create inclusive range from start to end

233

* @param that End YearMonth (inclusive)

234

* @returns YearMonthRange from this to that

235

*/

236

infix fun YearMonth.rangeTo(that: YearMonth): YearMonthRange

237

238

/**

239

* Create exclusive range from start to end

240

* @param that End YearMonth (exclusive)

241

* @returns YearMonthRange from this to that-1month

242

*/

243

infix fun YearMonth.rangeUntil(that: YearMonth): YearMonthRange

244

```

245

246

**Usage Examples:**

247

248

```kotlin

249

import kotlinx.datetime.*

250

251

val startMonth = YearMonth(2023, 1) // January 2023

252

val endMonth = YearMonth(2023, 12) // December 2023

253

254

// Create ranges

255

val yearRange = startMonth..endMonth // All of 2023

256

val firstHalf = startMonth.rangeUntil(YearMonth(2023, 7)) // Jan-June 2023

257

258

// Properties

259

println("Start: ${yearRange.start}") // 2023-01

260

println("End: ${yearRange.endInclusive}") // 2023-12

261

println("Size: ${yearRange.size}") // 12

262

263

// Membership testing

264

val summer = YearMonth(2023, 7)

265

val nextYear = YearMonth(2024, 1)

266

267

println("July in range: ${summer in yearRange}") // true

268

println("Next year in range: ${nextYear in yearRange}") // false

269

270

// Iteration

271

println("First quarter:")

272

for (month in YearMonth(2023, 1)..YearMonth(2023, 3)) {

273

println(" $month - ${month.numberOfDays} days")

274

}

275

```

276

277

### YearMonthProgression

278

279

Progression implementation for YearMonth values with month-based stepping.

280

281

```kotlin { .api }

282

/**

283

* Progression of YearMonth values with configurable step

284

* Supports forward and backward iteration with month-based steps

285

*/

286

open class YearMonthProgression : Iterable<YearMonth> {

287

/** First month in the progression */

288

val first: YearMonth

289

290

/** Last month that would be included */

291

val last: YearMonth

292

293

/** Number of elements in the progression */

294

val size: Int

295

296

/**

297

* Create reversed progression

298

* @returns YearMonthProgression in opposite direction

299

*/

300

fun reversed(): YearMonthProgression

301

302

/**

303

* Change the step of the progression

304

* @param value Step amount

305

* @param unit Month-based unit for the step

306

* @returns New progression with specified step

307

*/

308

fun step(value: Int, unit: DateTimeUnit.MonthBased): YearMonthProgression

309

fun step(value: Long, unit: DateTimeUnit.MonthBased): YearMonthProgression

310

311

/**

312

* Get a random element from the progression

313

* @param random Random number generator

314

* @returns Random YearMonth from the progression

315

*/

316

fun random(random: Random = Random.Default): YearMonth

317

318

/**

319

* Check if a YearMonth is contained in this progression

320

* @param value YearMonth to check

321

* @returns true if the YearMonth is part of this progression

322

*/

323

operator fun contains(value: YearMonth): Boolean

324

325

/**

326

* Iterator for stepping through the progression

327

* @returns Iterator over YearMonth values

328

*/

329

override fun iterator(): Iterator<YearMonth>

330

}

331

```

332

333

**Creation Functions:**

334

335

```kotlin { .api }

336

/**

337

* Create descending progression from this month to that month

338

* @param that End month

339

* @returns YearMonthProgression stepping backward by months

340

*/

341

infix fun YearMonth.downTo(that: YearMonth): YearMonthProgression

342

```

343

344

**Usage Examples:**

345

346

```kotlin

347

import kotlinx.datetime.*

348

349

val startMonth = YearMonth(2023, 1)

350

val endMonth = YearMonth(2025, 12)

351

352

// Basic progression (monthly steps)

353

val monthlyProgression = startMonth..endMonth

354

println("Total months: ${monthlyProgression.size}") // 36 months

355

356

// Reverse progression

357

val reverseMonths = monthlyProgression.reversed()

358

println("Reverse: ${reverseMonths.first} to ${reverseMonths.last}")

359

360

// Descending progression

361

val countdown = endMonth.downTo(startMonth)

362

363

// Quarterly progression (every 3 months)

364

val quarterlyProgression = (startMonth..endMonth).step(3, DateTimeUnit.MONTH)

365

println("Quarterly progression:")

366

for (month in quarterlyProgression) {

367

println(" $month (Q${(month.month.number - 1) / 3 + 1} ${month.year})")

368

}

369

370

// Yearly progression (every 12 months)

371

val yearlyProgression = (startMonth..endMonth).step(12, DateTimeUnit.MONTH)

372

println("Yearly progression:")

373

for (month in yearlyProgression) {

374

println(" $month") // January of each year

375

}

376

377

// Semi-annual progression (every 6 months)

378

val semiAnnualProgression = (startMonth..endMonth).step(6, DateTimeUnit.MONTH)

379

380

// Random selection

381

val randomMonth = monthlyProgression.random()

382

println("Random month: $randomMonth")

383

```

384

385

## Advanced Range Operations

386

387

### Range Extensions and Utilities

388

389

```kotlin

390

import kotlinx.datetime.*

391

392

// Utility functions for working with date ranges

393

fun LocalDateRange.weekends(): List<LocalDate> {

394

return this.filter { it.dayOfWeek == DayOfWeek.SATURDAY || it.dayOfWeek == DayOfWeek.SUNDAY }

395

}

396

397

fun LocalDateRange.weekdays(): List<LocalDate> {

398

return this.filter { it.dayOfWeek !in listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) }

399

}

400

401

fun LocalDateRange.monthBoundaries(): List<LocalDate> {

402

return this.filter { it.day == 1 || it == it.yearMonth.lastDay }

403

}

404

405

// Usage

406

val december = LocalDate(2023, 12, 1)..LocalDate(2023, 12, 31)

407

408

val weekendDays = december.weekends()

409

println("Weekend days in December: ${weekendDays.size}")

410

411

val workDays = december.weekdays()

412

println("Work days in December: ${workDays.size}")

413

414

// Month boundaries across a longer range

415

val yearRange = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 31)

416

val monthBounds = yearRange.monthBoundaries()

417

println("Month boundaries in 2023: ${monthBounds.size}") // 24 (first and last day of each month)

418

```

419

420

### Custom Step Functions

421

422

```kotlin

423

import kotlinx.datetime.*

424

425

// Extension function for business day progression (Mon-Fri only)

426

fun LocalDate.businessDaysTo(endDate: LocalDate): Sequence<LocalDate> {

427

return generateSequence(this) { current ->

428

val next = current.plus(1, DateTimeUnit.DAY)

429

if (next <= endDate) {

430

// Skip to next weekday if next is weekend

431

when (next.dayOfWeek) {

432

DayOfWeek.SATURDAY -> next.plus(2, DateTimeUnit.DAY)

433

DayOfWeek.SUNDAY -> next.plus(1, DateTimeUnit.DAY)

434

else -> next

435

}

436

} else null

437

}.takeWhile { it <= endDate }

438

}

439

440

// Usage

441

val startDate = LocalDate(2023, 12, 1) // Friday

442

val endDate = LocalDate(2023, 12, 15) // Friday

443

444

val businessDays = startDate.businessDaysTo(endDate).toList()

445

println("Business days:")

446

businessDays.forEach { date ->

447

println(" $date (${date.dayOfWeek})")

448

}

449

450

// Extension for nth day of month progression

451

fun YearMonth.nthDayProgression(dayOfMonth: Int, months: Int): Sequence<LocalDate> {

452

return generateSequence(0) { it + 1 }

453

.take(months)

454

.map { this.plus(it, DateTimeUnit.MONTH) }

455

.mapNotNull { yearMonth ->

456

try {

457

LocalDate(yearMonth.year, yearMonth.month, dayOfMonth)

458

} catch (e: Exception) {

459

// Handle cases where day doesn't exist (e.g., Feb 30)

460

null

461

}

462

}

463

}

464

465

// Usage: Every 15th of the month for a year

466

val base = YearMonth(2023, 1)

467

val fifteenthOfMonths = base.nthDayProgression(15, 12).toList()

468

println("15th of each month in 2023:")

469

fifteenthOfMonths.forEach { println(" $it") }

470

```

471

472

### Range Set Operations

473

474

```kotlin

475

import kotlinx.datetime.*

476

477

// Extension functions for range operations

478

fun LocalDateRange.intersect(other: LocalDateRange): LocalDateRange? {

479

val start = maxOf(this.start, other.start)

480

val end = minOf(this.endInclusive, other.endInclusive)

481

return if (start <= end) start..end else null

482

}

483

484

fun LocalDateRange.union(other: LocalDateRange): LocalDateRange? {

485

// Only works for overlapping or adjacent ranges

486

val gap = when {

487

this.endInclusive < other.start -> other.start.minus(this.endInclusive.plus(1, DateTimeUnit.DAY))

488

other.endInclusive < this.start -> this.start.minus(other.endInclusive.plus(1, DateTimeUnit.DAY))

489

else -> DatePeriod() // Overlapping or adjacent

490

}

491

492

return if (gap.days <= 1) { // Adjacent or overlapping

493

minOf(this.start, other.start)..maxOf(this.endInclusive, other.endInclusive)

494

} else null

495

}

496

497

fun LocalDateRange.subtract(other: LocalDateRange): List<LocalDateRange> {

498

val intersection = this.intersect(other) ?: return listOf(this)

499

500

val ranges = mutableListOf<LocalDateRange>()

501

502

// Before intersection

503

if (this.start < intersection.start) {

504

ranges.add(this.start.rangeUntil(intersection.start))

505

}

506

507

// After intersection

508

if (intersection.endInclusive < this.endInclusive) {

509

ranges.add(intersection.endInclusive.plus(1, DateTimeUnit.DAY)..this.endInclusive)

510

}

511

512

return ranges

513

}

514

515

// Usage

516

val range1 = LocalDate(2023, 12, 1)..LocalDate(2023, 12, 15) // Dec 1-15

517

val range2 = LocalDate(2023, 12, 10)..LocalDate(2023, 12, 25) // Dec 10-25

518

519

val intersection = range1.intersect(range2)

520

println("Intersection: $intersection") // Dec 10-15

521

522

val union = range1.union(range2)

523

println("Union: $union") // Dec 1-25

524

525

val subtraction = range1.subtract(range2)

526

println("Range1 - Range2: $subtraction") // [Dec 1-9]

527

```

528

529

### Performance Considerations

530

531

```kotlin

532

import kotlinx.datetime.*

533

534

// For large ranges, prefer sequences over lists to avoid memory issues

535

fun LocalDateRange.asSequence(): Sequence<LocalDate> {

536

return generateSequence(this.start) { current ->

537

val next = current.plus(1, DateTimeUnit.DAY)

538

if (next <= this.endInclusive) next else null

539

}

540

}

541

542

// Efficient counting without iteration

543

fun LocalDateRange.count(): Int {

544

return if (isEmpty()) 0 else this.start.daysUntil(this.endInclusive) + 1

545

}

546

547

// Efficient nth element access

548

fun LocalDateRange.elementAt(index: Int): LocalDate? {

549

return if (index in 0 until count()) {

550

this.start.plus(index, DateTimeUnit.DAY)

551

} else null

552

}

553

554

// Usage for large ranges

555

val largeRange = LocalDate(2020, 1, 1)..LocalDate(2030, 12, 31) // 11 years

556

println("Large range size: ${largeRange.count()}") // ~4000 days

557

558

// Use sequence for memory-efficient processing

559

val weekendsCount = largeRange.asSequence()

560

.filter { it.dayOfWeek in listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY) }

561

.count()

562

563

println("Weekends in large range: $weekendsCount")

564

565

// Direct access without iteration

566

val midpoint = largeRange.elementAt(largeRange.count() / 2)

567

println("Midpoint date: $midpoint")

568

```

569

570

## Range Validation and Error Handling

571

572

```kotlin

573

import kotlinx.datetime.*

574

575

// Safe range creation with validation

576

fun createSafeRange(start: LocalDate, end: LocalDate): LocalDateRange? {

577

return if (start <= end) {

578

start..end

579

} else {

580

null // Invalid range

581

}

582

}

583

584

// Range bounds checking

585

fun LocalDateRange.isValidRange(): Boolean {

586

return !isEmpty()

587

}

588

589

fun LocalDateRange.clampToRange(bounds: LocalDateRange): LocalDateRange? {

590

val clampedStart = maxOf(this.start, bounds.start)

591

val clampedEnd = minOf(this.endInclusive, bounds.endInclusive)

592

593

return if (clampedStart <= clampedEnd) {

594

clampedStart..clampedEnd

595

} else {

596

null // No overlap

597

}

598

}

599

600

// Usage

601

val validRange = createSafeRange(

602

LocalDate(2023, 12, 1),

603

LocalDate(2023, 12, 31)

604

) // Valid range

605

606

val invalidRange = createSafeRange(

607

LocalDate(2023, 12, 31),

608

LocalDate(2023, 12, 1)

609

) // null - invalid

610

611

// Bounds checking

612

val bounds = LocalDate(2023, 1, 1)..LocalDate(2023, 12, 31) // Year 2023

613

val testRange = LocalDate(2022, 6, 1)..LocalDate(2024, 6, 1) // Spans multiple years

614

615

val clamped = testRange.clampToRange(bounds)

616

println("Clamped range: $clamped") // 2023-01-01..2023-12-31

617

```