@@ -17,9 +17,22 @@ import kotlinx.serialization.Serializable
17
17
/* *
18
18
* A difference between two [instants][Instant], decomposed into date and time components.
19
19
*
20
- * The date components are: [years], [months], [days].
20
+ * The date components are: [years] ([DateTimeUnit.YEAR]) , [months] ([DateTimeUnit.MONTH]) , [days] ([DateTimeUnit.DAY]) .
21
21
*
22
- * The time components are: [hours], [minutes], [seconds], [nanoseconds].
22
+ * The time components are: [hours] ([DateTimeUnit.HOUR]), [minutes] ([DateTimeUnit.MINUTE]),
23
+ * [seconds] ([DateTimeUnit.SECOND]), [nanoseconds] ([DateTimeUnit.NANOSECOND]).
24
+ *
25
+ * The time components are not independent and always overflow into one another.
26
+ * Likewise, months overflow into years.
27
+ * For example, there is no difference between `DateTimePeriod(months = 24, hours = 2, minutes = 63)` and
28
+ * `DateTimePeriod(years = 2, hours = 3, minutes = 3)`.
29
+ *
30
+ * All components can also be negative: for example, `DateTimePeriod(months = -5, days = 6, hours = -3)`.
31
+ * Whereas `months = 5` means "5 months after," `months = -5` means "5 months earlier."
32
+ *
33
+ * Since, semantically, a [DateTimePeriod] is a combination of [DateTimeUnit] values, in cases when the period is a
34
+ * fixed time interval (like "yearly" or "quarterly"), please consider using [DateTimeUnit] directly instead:
35
+ * for example, instead of `DateTimePeriod(months = 6)`, one could use `DateTimeUnit.MONTH * 6`.
23
36
*
24
37
* ### Interaction with other entities
25
38
*
@@ -29,6 +42,10 @@ import kotlinx.serialization.Serializable
29
42
* [DatePeriod] is a subtype of [DateTimePeriod] that only stores the date components and has all time components equal
30
43
* to zero.
31
44
*
45
+ * [DateTimePeriod] can be thought of as a combination of a [Duration] and a [DatePeriod], as it contains both the
46
+ * time components of [Duration] and the date components of [DatePeriod].
47
+ * [Duration.toDateTimePeriod] can be used to convert a [Duration] to the corresponding [DateTimePeriod].
48
+ *
32
49
* ### Construction, serialization, and deserialization
33
50
*
34
51
* When a [DateTimePeriod] is constructed in any way, a [DatePeriod] value, which is a subtype of [DateTimePeriod],
@@ -98,7 +115,17 @@ public sealed class DateTimePeriod {
98
115
/* *
99
116
* Converts this period to the ISO 8601 string representation for durations, for example, `P2M1DT3H`.
100
117
*
101
- * @see DateTimePeriod.parse
118
+ * Note that the ISO 8601 duration is not the same as [Duration],
119
+ * but instead includes the date components, like [DateTimePeriod] does.
120
+ *
121
+ * Examples of the output:
122
+ * - `P2Y4M-1D`: two years, four months, minus one day;
123
+ * - `-P2Y4M1D`: minus two years, minus four months, minus one day;
124
+ * - `P1DT3H2M4.123456789S`: one day, three hours, two minutes, four seconds, 123456789 nanoseconds;
125
+ * - `P1DT-3H-2M-4.123456789S`: one day, minus three hours, minus two minutes,
126
+ * minus four seconds, minus 123456789 nanoseconds;
127
+ *
128
+ * @see DateTimePeriod.parse for the detailed description of the format.
102
129
*/
103
130
override fun toString (): String = buildString {
104
131
val sign = if (allNonpositive()) { append(' -' ); - 1 } else 1
@@ -144,16 +171,38 @@ public sealed class DateTimePeriod {
144
171
public companion object {
145
172
/* *
146
173
* Parses a ISO 8601 duration string as a [DateTimePeriod].
174
+ *
147
175
* If the time components are absent or equal to zero, returns a [DatePeriod].
148
176
*
149
- * Additionally, we support the `W` signifier to represent weeks.
177
+ * Note that the ISO 8601 duration is not the same as [Duration],
178
+ * but instead includes the date components, like [DateTimePeriod] does.
150
179
*
151
180
* Examples of durations in the ISO 8601 format:
152
181
* - `P1Y40D` is one year and 40 days
153
182
* - `-P1DT1H` is minus (one day and one hour)
154
183
* - `P1DT-1H` is one day minus one hour
155
184
* - `-PT0.000000001S` is minus one nanosecond
156
185
*
186
+ * The format is defined as follows:
187
+ * - First, optionally, a `-` or `+`.
188
+ * If `-` is present, the whole period after the `-` is negated: `-P-2M1D` is the same as `P2M-1D`.
189
+ * - Then, the letter `P`.
190
+ * - Optionally, the number of years, followed by `Y`.
191
+ * - Optionally, the number of months, followed by `M`.
192
+ * - Optionally, the number of weeks, followed by `W`.
193
+ * This is not a part of the ISO 8601 format but an extension.
194
+ * - Optionally, the number of days, followed by `D`.
195
+ * - The string can end here if there are no more time components.
196
+ * If there are time components, the letter `T` is required.
197
+ * - Optionally, the number of hours, followed by `H`.
198
+ * - Optionally, the number of minutes, followed by `M`.
199
+ * - Optionally, the number of seconds, followed by `S`.
200
+ * Seconds can optionally have a fractional part with up to nine digits.
201
+ * The fractional part is separated with a `.`.
202
+ *
203
+ * All numbers can be negative, in which case, `-` is prepended to them.
204
+ * Otherwise, a number can have `+` prepended to it, which does not have an effect.
205
+ *
157
206
* @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [DateTimePeriod] are
158
207
* exceeded.
159
208
*/
@@ -348,10 +397,14 @@ public class DatePeriod internal constructor(
348
397
* Constructs a new [DatePeriod].
349
398
*
350
399
* It is recommended to always explicitly name the arguments when constructing this manually,
351
- * like `DatePeriod(years = 1, months = 12)`.
400
+ * like `DatePeriod(years = 1, months = 12, days = 16 )`.
352
401
*
353
402
* The passed numbers are not stored as is but are normalized instead for human readability, so, for example,
354
- * `DateTimePeriod(months = 24)` becomes `DateTimePeriod(years = 2)`.
403
+ * `DateTimePeriod(months = 24, days = 41)` becomes `DateTimePeriod(years = 2, days = 41)`.
404
+ *
405
+ * If only a single component is set and is always non-zero and is semantically a fixed time interval
406
+ * (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit.DateBased] instead.
407
+ * For example, instead of `DatePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`.
355
408
*
356
409
* @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int].
357
410
*/
@@ -435,10 +488,14 @@ internal fun buildDateTimePeriod(totalMonths: Int = 0, days: Int = 0, totalNanos
435
488
* Constructs a new [DateTimePeriod]. If all the time components are zero, returns a [DatePeriod].
436
489
*
437
490
* It is recommended to always explicitly name the arguments when constructing this manually,
438
- * like `DateTimePeriod(years = 1, months = 12)`.
491
+ * like `DateTimePeriod(years = 1, months = 12, days = 16 )`.
439
492
*
440
493
* The passed numbers are not stored as is but are normalized instead for human readability, so, for example,
441
- * `DateTimePeriod(months = 24)` becomes `DateTimePeriod(years = 2)`.
494
+ * `DateTimePeriod(months = 24, days = 41)` becomes `DateTimePeriod(years = 2, days = 41)`.
495
+ *
496
+ * If only a single component is set and is always non-zero and is semantically a fixed time interval
497
+ * (like "yearly" or "quarterly"), please consider using a multiple of [DateTimeUnit] instead.
498
+ * For example, instead of `DateTimePeriod(months = 6)`, one can use `DateTimeUnit.MONTH * 6`.
442
499
*
443
500
* @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int].
444
501
* @throws IllegalArgumentException if the total number of months in [hours], [minutes], [seconds] and [nanoseconds]
@@ -465,6 +522,10 @@ public fun DateTimePeriod(
465
522
* The reason is that even a [Duration] obtained via [Duration.Companion.days] just means a multiple of 24 hours,
466
523
* whereas in `kotlinx-datetime`, a day is a calendar day, which can be different from 24 hours.
467
524
* See [DateTimeUnit.DayBased] for details.
525
+ *
526
+ * ```
527
+ * 2.days.toDateTimePeriod() // 0 days, 48 hours
528
+ * ```
468
529
*/
469
530
// TODO: maybe it's more consistent to throw here on overflow?
470
531
public fun Duration.toDateTimePeriod (): DateTimePeriod = buildDateTimePeriod(totalNanoseconds = inWholeNanoseconds)
0 commit comments