0
# Date/Time Arithmetic
1
2
Date and time arithmetic operations using units, periods, and duration-based calculations. The library provides flexible arithmetic capabilities for both precise time-based operations and calendar-aware date operations.
3
4
## Capabilities
5
6
### DateTimeUnit
7
8
Sealed class hierarchy representing units for measuring time intervals, supporting both precise time-based units and calendar-based units.
9
10
```kotlin { .api }
11
/**
12
* Units for measuring time intervals
13
* Base sealed class for all time measurement units
14
*/
15
sealed class DateTimeUnit {
16
/**
17
* Multiply this unit by a scalar value
18
* @param scalar Multiplier for the unit
19
* @returns New DateTimeUnit representing the scaled unit
20
*/
21
abstract fun times(scalar: Int): DateTimeUnit
22
}
23
```
24
25
**Time-Based Units:**
26
27
```kotlin { .api }
28
/**
29
* Time-based unit with precise nanosecond duration
30
* Represents units that have a fixed duration in nanoseconds
31
*/
32
class TimeBased(val nanoseconds: Long) : DateTimeUnit() {
33
override fun times(scalar: Int): TimeBased
34
}
35
36
// Predefined time-based constants
37
object DateTimeUnit {
38
val NANOSECOND: TimeBased // 1 nanosecond
39
val MICROSECOND: TimeBased // 1,000 nanoseconds
40
val MILLISECOND: TimeBased // 1,000,000 nanoseconds
41
val SECOND: TimeBased // 1,000,000,000 nanoseconds
42
val MINUTE: TimeBased // 60 seconds
43
val HOUR: TimeBased // 60 minutes
44
}
45
```
46
47
**Date-Based Units:**
48
49
```kotlin { .api }
50
/**
51
* Date-based unit (abstract base for calendar units)
52
* Represents units that vary in duration based on calendar context
53
*/
54
sealed class DateBased : DateTimeUnit()
55
56
/**
57
* Day-based unit representing a number of calendar days
58
* Duration varies based on DST transitions and leap seconds
59
*/
60
class DayBased(val days: Int) : DateBased() {
61
override fun times(scalar: Int): DayBased
62
}
63
64
/**
65
* Month-based unit representing a number of months
66
* Duration varies significantly based on month length and year
67
*/
68
class MonthBased(val months: Int) : DateBased() {
69
override fun times(scalar: Int): MonthBased
70
}
71
72
// Predefined date-based constants
73
object DateTimeUnit {
74
val DAY: DayBased // 1 calendar day
75
val WEEK: DayBased // 7 calendar days
76
val MONTH: MonthBased // 1 calendar month
77
val QUARTER: MonthBased // 3 calendar months
78
val YEAR: MonthBased // 12 calendar months
79
val CENTURY: MonthBased // 1200 calendar months
80
}
81
```
82
83
**Usage Examples:**
84
85
```kotlin
86
import kotlinx.datetime.*
87
import kotlin.time.Clock
88
89
val now = Clock.System.now()
90
91
// Time-based arithmetic (precise)
92
val oneHourLater = now.plus(1, DateTimeUnit.HOUR, TimeZone.UTC)
93
val thirtyMinutes = now.plus(30, DateTimeUnit.MINUTE, TimeZone.UTC)
94
val fiveSeconds = now.plus(5, DateTimeUnit.SECOND, TimeZone.UTC)
95
96
// Custom time units
97
val twoHours = DateTimeUnit.HOUR.times(2)
98
val halfHour = DateTimeUnit.MINUTE.times(30)
99
100
// Date-based arithmetic (calendar-aware)
101
val tomorrow = now.plus(1, DateTimeUnit.DAY, TimeZone.of("America/New_York"))
102
val nextWeek = now.plus(1, DateTimeUnit.WEEK, TimeZone.UTC)
103
val nextMonth = now.plus(1, DateTimeUnit.MONTH, TimeZone.currentSystemDefault())
104
```
105
106
### DateTimePeriod
107
108
Represents a combination of date and time components for complex arithmetic operations.
109
110
```kotlin { .api }
111
/**
112
* Combination of date and time components
113
* Represents a period with separate year/month/day/hour/minute/second/nanosecond parts
114
*/
115
sealed class DateTimePeriod {
116
/** Whole years */
117
abstract val years: Int
118
119
/** Months not forming whole years (-11..11) */
120
abstract val months: Int
121
122
/** Calendar days (can exceed month boundaries) */
123
abstract val days: Int
124
125
/** Whole hours (can exceed day boundaries) */
126
abstract val hours: Int
127
128
/** Minutes not forming whole hours (-59..59) */
129
abstract val minutes: Int
130
131
/** Seconds not forming whole minutes (-59..59) */
132
abstract val seconds: Int
133
134
/** Nanoseconds not forming whole seconds */
135
abstract val nanoseconds: Int
136
}
137
```
138
139
**Factory Functions:**
140
141
```kotlin { .api }
142
/**
143
* Create a DateTimePeriod with specified components
144
* @param years Number of years (default 0)
145
* @param months Number of months (default 0)
146
* @param days Number of days (default 0)
147
* @param hours Number of hours (default 0)
148
* @param minutes Number of minutes (default 0)
149
* @param seconds Number of seconds (default 0)
150
* @param nanoseconds Number of nanoseconds (default 0)
151
* @returns DateTimePeriod with the specified components
152
*/
153
fun DateTimePeriod(
154
years: Int = 0,
155
months: Int = 0,
156
days: Int = 0,
157
hours: Int = 0,
158
minutes: Int = 0,
159
seconds: Int = 0,
160
nanoseconds: Long = 0
161
): DateTimePeriod
162
163
/**
164
* Parse DateTimePeriod from ISO 8601 duration string
165
* @param text ISO 8601 duration string (e.g., "P1Y2M3DT4H5M6.7S")
166
* @returns Parsed DateTimePeriod
167
*/
168
fun DateTimePeriod.Companion.parse(text: String): DateTimePeriod
169
170
/**
171
* Convert kotlin.time.Duration to DateTimePeriod
172
* @returns DateTimePeriod representing the duration
173
*/
174
fun Duration.toDateTimePeriod(): DateTimePeriod
175
```
176
177
**Formatting:**
178
179
```kotlin { .api }
180
/**
181
* Convert to ISO 8601 duration format
182
* @returns ISO 8601 duration string (e.g., "P1Y2M3DT4H5M6.789S")
183
*/
184
override fun DateTimePeriod.toString(): String
185
```
186
187
**Usage Examples:**
188
189
```kotlin
190
import kotlinx.datetime.*
191
import kotlin.time.*
192
193
// Create periods
194
val complexPeriod = DateTimePeriod(
195
years = 1,
196
months = 2,
197
days = 15,
198
hours = 8,
199
minutes = 30,
200
seconds = 45
201
)
202
203
val shortPeriod = DateTimePeriod(hours = 2, minutes = 30)
204
205
// From Duration
206
val duration = 2.hours + 30.minutes
207
val period = duration.toDateTimePeriod()
208
209
// Parse from ISO 8601
210
val parsed = DateTimePeriod.parse("P1Y2M15DT8H30M45S")
211
212
// Format as ISO 8601
213
println(complexPeriod.toString()) // "P1Y2M15DT8H30M45S"
214
215
// Use in arithmetic
216
val now = Clock.System.now()
217
val timeZone = TimeZone.of("Europe/Paris")
218
val future = now.plus(complexPeriod, timeZone)
219
```
220
221
### DatePeriod
222
223
Specialized period class for date-only arithmetic operations.
224
225
```kotlin { .api }
226
/**
227
* Date-only period (time components are zero)
228
* Extends DateTimePeriod with only date components
229
*/
230
class DatePeriod : DateTimePeriod {
231
/**
232
* Create a date period with specified components
233
* @param years Number of years (default 0)
234
* @param months Number of months (default 0)
235
* @param days Number of days (default 0)
236
*/
237
constructor(years: Int = 0, months: Int = 0, days: Int = 0)
238
239
companion object {
240
/**
241
* Parse DatePeriod from ISO 8601 date duration string
242
* @param text ISO 8601 date duration (e.g., "P1Y2M15D")
243
* @returns Parsed DatePeriod
244
*/
245
fun parse(text: String): DatePeriod
246
}
247
}
248
```
249
250
**Usage Examples:**
251
252
```kotlin
253
import kotlinx.datetime.*
254
255
// Create date periods
256
val oneYear = DatePeriod(years = 1)
257
val twoMonths = DatePeriod(months = 2)
258
val fifteenDays = DatePeriod(days = 15)
259
val combined = DatePeriod(years = 1, months = 6, days = 10)
260
261
// Parse from ISO 8601
262
val parsed = DatePeriod.parse("P1Y6M10D")
263
264
// Use with LocalDate
265
val today = LocalDate(2023, 12, 25)
266
val nextYear = today.plus(oneYear)
267
val futureDate = today.plus(combined)
268
269
// Use with Instant (requires time zone for calendar arithmetic)
270
val now = Clock.System.now()
271
val timeZone = TimeZone.of("America/New_York")
272
val futureInstant = now.plus(combined, timeZone)
273
```
274
275
## Instant Arithmetic Operations
276
277
Extension functions for performing arithmetic on Instant values.
278
279
### Basic Arithmetic
280
281
```kotlin { .api }
282
/**
283
* Add a DateTimePeriod to an Instant in the specified time zone
284
* @param period Period to add
285
* @param timeZone Time zone for calendar calculations
286
* @returns New Instant representing the result
287
*/
288
fun Instant.plus(period: DateTimePeriod, timeZone: TimeZone): Instant
289
290
/**
291
* Subtract a DateTimePeriod from an Instant in the specified time zone
292
* @param period Period to subtract
293
* @param timeZone Time zone for calendar calculations
294
* @returns New Instant representing the result
295
*/
296
fun Instant.minus(period: DateTimePeriod, timeZone: TimeZone): Instant
297
298
/**
299
* Add a value in the specified unit to an Instant
300
* @param value Amount to add
301
* @param unit Unit for the arithmetic
302
* @param timeZone Time zone for calendar-based units
303
* @returns New Instant representing the result
304
*/
305
fun Instant.plus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant
306
fun Instant.plus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant
307
308
/**
309
* Subtract a value in the specified unit from an Instant
310
* @param value Amount to subtract
311
* @param unit Unit for the arithmetic
312
* @param timeZone Time zone for calendar-based units
313
* @returns New Instant representing the result
314
*/
315
fun Instant.minus(value: Int, unit: DateTimeUnit, timeZone: TimeZone): Instant
316
fun Instant.minus(value: Long, unit: DateTimeUnit, timeZone: TimeZone): Instant
317
```
318
319
### Difference Calculations
320
321
```kotlin { .api }
322
/**
323
* Calculate the difference between two instants in the specified unit
324
* @param other The other instant
325
* @param unit Unit for the result
326
* @param timeZone Time zone for calendar-based calculations
327
* @returns Difference in the specified unit
328
*/
329
fun Instant.until(other: Instant, unit: DateTimeUnit, timeZone: TimeZone): Long
330
331
/**
332
* Calculate the number of whole days between two instants
333
* @param other The other instant
334
* @param timeZone Time zone for day calculations
335
* @returns Number of days
336
*/
337
fun Instant.daysUntil(other: Instant, timeZone: TimeZone): Int
338
339
/**
340
* Calculate the number of whole months between two instants
341
* @param other The other instant
342
* @param timeZone Time zone for month calculations
343
* @returns Number of months
344
*/
345
fun Instant.monthsUntil(other: Instant, timeZone: TimeZone): Int
346
347
/**
348
* Calculate the number of whole years between two instants
349
* @param other The other instant
350
* @param timeZone Time zone for year calculations
351
* @returns Number of years
352
*/
353
fun Instant.yearsUntil(other: Instant, timeZone: TimeZone): Int
354
355
/**
356
* Calculate the period between two instants
357
* @param other The other instant
358
* @param timeZone Time zone for calculations
359
* @returns DateTimePeriod representing the difference
360
*/
361
fun Instant.periodUntil(other: Instant, timeZone: TimeZone): DateTimePeriod
362
```
363
364
**Usage Examples:**
365
366
```kotlin
367
import kotlinx.datetime.*
368
import kotlin.time.Clock
369
370
val now = Clock.System.now()
371
val timeZone = TimeZone.of("Europe/London")
372
373
// Basic arithmetic with periods
374
val period = DateTimePeriod(years = 1, months = 6, days = 15, hours = 12)
375
val future = now.plus(period, timeZone)
376
val past = now.minus(period, timeZone)
377
378
// Arithmetic with units
379
val tomorrow = now.plus(1, DateTimeUnit.DAY, timeZone)
380
val nextHour = now.plus(1, DateTimeUnit.HOUR, timeZone)
381
val nextMonth = now.plus(1, DateTimeUnit.MONTH, timeZone)
382
383
// Calculate differences
384
val startOfYear = LocalDate(2023, 1, 1).atStartOfDayIn(timeZone)
385
val endOfYear = LocalDate(2023, 12, 31).atTime(23, 59, 59).toInstant(timeZone)
386
387
val daysInYear = startOfYear.daysUntil(endOfYear, timeZone)
388
val monthsInYear = startOfYear.monthsUntil(endOfYear, timeZone)
389
val fullPeriod = startOfYear.periodUntil(endOfYear, timeZone)
390
391
println("Days: $daysInYear") // 364
392
println("Months: $monthsInYear") // 11
393
println("Period: $fullPeriod") // P11M30D23H59M59S
394
```
395
396
## LocalDate Arithmetic Operations
397
398
Arithmetic operations specifically for LocalDate values.
399
400
```kotlin { .api }
401
/**
402
* Add a DatePeriod to a LocalDate
403
* @param period Date period to add
404
* @returns New LocalDate representing the result
405
*/
406
fun LocalDate.plus(period: DatePeriod): LocalDate
407
408
/**
409
* Subtract a DatePeriod from a LocalDate
410
* @param period Date period to subtract
411
* @returns New LocalDate representing the result
412
*/
413
fun LocalDate.minus(period: DatePeriod): LocalDate
414
415
/**
416
* Add a value in the specified date-based unit
417
* @param value Amount to add
418
* @param unit Date-based unit (DayBased or MonthBased)
419
* @returns New LocalDate representing the result
420
*/
421
fun LocalDate.plus(value: Int, unit: DateTimeUnit.DateBased): LocalDate
422
fun LocalDate.plus(value: Long, unit: DateTimeUnit.DateBased): LocalDate
423
424
/**
425
* Subtract a value in the specified date-based unit
426
* @param value Amount to subtract
427
* @param unit Date-based unit (DayBased or MonthBased)
428
* @returns New LocalDate representing the result
429
*/
430
fun LocalDate.minus(value: Int, unit: DateTimeUnit.DateBased): LocalDate
431
fun LocalDate.minus(value: Long, unit: DateTimeUnit.DateBased): LocalDate
432
433
/**
434
* Calculate difference between dates in the specified unit
435
* @param other The other date
436
* @param unit Date-based unit for the result
437
* @returns Difference in the specified unit
438
*/
439
fun LocalDate.until(other: LocalDate, unit: DateTimeUnit.DateBased): Long
440
441
/**
442
* Calculate whole days between dates
443
* @param other The other date
444
* @returns Number of days
445
*/
446
fun LocalDate.daysUntil(other: LocalDate): Int
447
448
/**
449
* Calculate whole months between dates
450
* @param other The other date
451
* @returns Number of months
452
*/
453
fun LocalDate.monthsUntil(other: LocalDate): Int
454
455
/**
456
* Calculate whole years between dates
457
* @param other The other date
458
* @returns Number of years
459
*/
460
fun LocalDate.yearsUntil(other: LocalDate): Int
461
462
/**
463
* Calculate the date period between two dates
464
* @param other The other date
465
* @returns DatePeriod representing the difference
466
*/
467
fun LocalDate.periodUntil(other: LocalDate): DatePeriod
468
```
469
470
**Usage Examples:**
471
472
```kotlin
473
import kotlinx.datetime.*
474
475
val startDate = LocalDate(2023, 1, 15)
476
477
// Add/subtract periods
478
val period = DatePeriod(years = 1, months = 6, days = 10)
479
val futureDate = startDate.plus(period) // 2024-07-25
480
val pastDate = startDate.minus(period) // 2021-07-05
481
482
// Add/subtract units
483
val tomorrow = startDate.plus(1, DateTimeUnit.DAY) // 2023-01-16
484
val nextWeek = startDate.plus(1, DateTimeUnit.WEEK) // 2023-01-22
485
val nextMonth = startDate.plus(1, DateTimeUnit.MONTH) // 2023-02-15
486
val nextYear = startDate.plus(1, DateTimeUnit.YEAR) // 2024-01-15
487
488
// Calculate differences
489
val endDate = LocalDate(2024, 7, 25)
490
val daysDiff = startDate.daysUntil(endDate) // 556
491
val monthsDiff = startDate.monthsUntil(endDate) // 18
492
val yearsDiff = startDate.yearsUntil(endDate) // 1
493
val fullPeriod = startDate.periodUntil(endDate) // P1Y6M10D
494
```
495
496
## YearMonth Arithmetic Operations
497
498
Specialized arithmetic for YearMonth values.
499
500
```kotlin { .api }
501
/**
502
* Add months to a YearMonth
503
* @param value Number of months to add
504
* @param unit Must be MonthBased unit
505
* @returns New YearMonth representing the result
506
*/
507
fun YearMonth.plus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth
508
fun YearMonth.plus(value: Long, unit: DateTimeUnit.MonthBased): YearMonth
509
510
/**
511
* Subtract months from a YearMonth
512
* @param value Number of months to subtract
513
* @param unit Must be MonthBased unit
514
* @returns New YearMonth representing the result
515
*/
516
fun YearMonth.minus(value: Int, unit: DateTimeUnit.MonthBased): YearMonth
517
fun YearMonth.minus(value: Long, unit: DateTimeUnit.MonthBased): YearMonth
518
519
/**
520
* Calculate difference in months
521
* @param other The other YearMonth
522
* @param unit MonthBased unit for the result
523
* @returns Difference in the specified unit
524
*/
525
fun YearMonth.until(other: YearMonth, unit: DateTimeUnit.MonthBased): Long
526
527
/**
528
* Calculate whole months between YearMonth values
529
* @param other The other YearMonth
530
* @returns Number of months
531
*/
532
fun YearMonth.monthsUntil(other: YearMonth): Int
533
534
/**
535
* Calculate whole years between YearMonth values
536
* @param other The other YearMonth
537
* @returns Number of years
538
*/
539
fun YearMonth.yearsUntil(other: YearMonth): Int
540
541
// Convenience functions
542
fun YearMonth.plusYear(): YearMonth
543
fun YearMonth.minusYear(): YearMonth
544
fun YearMonth.plusMonth(): YearMonth
545
fun YearMonth.minusMonth(): YearMonth
546
```
547
548
**Usage Examples:**
549
550
```kotlin
551
import kotlinx.datetime.*
552
553
val startMonth = YearMonth(2023, 6) // June 2023
554
555
// Add/subtract using units
556
val nextYear = startMonth.plus(1, DateTimeUnit.YEAR) // 2024-06
557
val nextQuarter = startMonth.plus(1, DateTimeUnit.QUARTER) // 2023-09
558
val nextMonth = startMonth.plus(1, DateTimeUnit.MONTH) // 2023-07
559
560
// Convenience functions
561
val plusYear = startMonth.plusYear() // 2024-06
562
val plusMonth = startMonth.plusMonth() // 2023-07
563
val minusYear = startMonth.minusYear() // 2022-06
564
565
// Calculate differences
566
val endMonth = YearMonth(2025, 12) // December 2025
567
val monthsDiff = startMonth.monthsUntil(endMonth) // 30
568
val yearsDiff = startMonth.yearsUntil(endMonth) // 2
569
```
570
571
## Error Handling
572
573
Arithmetic operations can throw DateTimeArithmeticException in certain scenarios.
574
575
```kotlin { .api }
576
/**
577
* Thrown when datetime arithmetic operations fail
578
* Typically occurs with invalid date calculations
579
*/
580
class DateTimeArithmeticException : RuntimeException {
581
constructor(message: String)
582
constructor(message: String, cause: Throwable?)
583
}
584
```
585
586
**Common Error Scenarios:**
587
588
```kotlin
589
import kotlinx.datetime.*
590
591
try {
592
// This can throw if the result would be invalid
593
val leap = LocalDate(2020, 2, 29) // Feb 29 in leap year
594
val nextYear = leap.plus(1, DateTimeUnit.YEAR) // Would be Feb 29, 2021 (invalid)
595
} catch (e: DateTimeArithmeticException) {
596
println("Arithmetic failed: ${e.message}")
597
}
598
599
// Safe alternatives - check before arithmetic
600
val date = LocalDate(2020, 2, 29)
601
val targetYear = 2021
602
603
// Check if the target date would be valid
604
val wouldBeValid = try {
605
LocalDate(targetYear, date.month, date.day)
606
true
607
} catch (e: Exception) {
608
false
609
}
610
611
if (wouldBeValid) {
612
val result = date.plus(1, DateTimeUnit.YEAR)
613
} else {
614
// Handle invalid date case (e.g., use last day of month)
615
val lastDayOfMonth = YearMonth(targetYear, date.month).lastDay
616
println("Using last day of month instead: $lastDayOfMonth")
617
}
618
```
619
620
## DST-Aware Arithmetic
621
622
When performing arithmetic with Instant values, the choice of time zone affects calendar-based calculations during DST transitions.
623
624
```kotlin
625
import kotlinx.datetime.*
626
627
val timeZone = TimeZone.of("America/New_York")
628
629
// Spring forward example (2023-03-12 02:00 -> 03:00)
630
val beforeSpring = LocalDateTime(2023, 3, 12, 1, 0).toInstant(timeZone)
631
632
// Adding 24 hours vs adding 1 day
633
val plus24Hours = beforeSpring.plus(24, DateTimeUnit.HOUR, timeZone)
634
val plus1Day = beforeSpring.plus(1, DateTimeUnit.DAY, timeZone)
635
636
val local24Hours = plus24Hours.toLocalDateTime(timeZone) // 2:00 AM (due to DST)
637
val local1Day = plus1Day.toLocalDateTime(timeZone) // 1:00 AM (same time next day)
638
639
println("24 hours later: $local24Hours") // 2023-03-13T02:00
640
println("1 day later: $local1Day") // 2023-03-13T01:00
641
642
// Fall back example (2023-11-05 02:00 -> 01:00)
643
val beforeFall = LocalDateTime(2023, 11, 5, 1, 0).toInstant(timeZone)
644
645
val fallPlus24Hours = beforeFall.plus(24, DateTimeUnit.HOUR, timeZone)
646
val fallPlus1Day = beforeFall.plus(1, DateTimeUnit.DAY, timeZone)
647
648
val fallLocal24Hours = fallPlus24Hours.toLocalDateTime(timeZone) // 12:00 AM (due to DST)
649
val fallLocal1Day = fallPlus1Day.toLocalDateTime(timeZone) // 1:00 AM (same time next day)
650
```