Skip to content

Commit 5a920ea

Browse files
dkhalanskyjbilya-g
andauthored
Fill the gaps in the documentation (#157)
Document time scale, leap second handling in ISO-8601 <-> Instant conversions Co-authored-by: Ilya Gorbunov <[email protected]>
1 parent e3642e6 commit 5a920ea

20 files changed

+602
-37
lines changed

core/common/src/Clock.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,20 @@ package kotlinx.datetime
77

88
import kotlin.time.*
99

10+
/**
11+
* A source of [Instant] values.
12+
*
13+
* See [Clock.System][Clock.System] for the clock instance that queries the operating system.
14+
*/
1015
public interface Clock {
16+
/**
17+
* Returns the [Instant] corresponding to the current time, according to this clock.
18+
*/
1119
public fun now(): Instant
1220

21+
/**
22+
* The [Clock] instance that queries the operating system as its source of knowledge of time.
23+
*/
1324
public object System : Clock {
1425
override fun now(): Instant = @Suppress("DEPRECATION_ERROR") Instant.now()
1526
}
@@ -19,9 +30,15 @@ public interface Clock {
1930
}
2031
}
2132

33+
/**
34+
* Returns the current date at the given [time zone][timeZone], according to [this Clock][this].
35+
*/
2236
public fun Clock.todayAt(timeZone: TimeZone): LocalDate =
2337
now().toLocalDateTime(timeZone).date
2438

39+
/**
40+
* Returns a [TimeSource] that uses this [Clock] to mark a time instant and to find the amount of time elapsed since that mark.
41+
*/
2542
@ExperimentalTime
2643
public fun Clock.asTimeSource(): TimeSource = object : TimeSource {
2744
override fun markNow(): TimeMark = InstantTimeMark(now(), this@asTimeSource)

core/common/src/DateTimePeriod.kt

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,70 @@ import kotlin.math.*
1111
import kotlin.time.Duration
1212
import kotlinx.serialization.Serializable
1313

14+
/**
15+
* A difference between two [instants][Instant], decomposed into date and time components.
16+
*
17+
* The date components are: [years], [months], [days].
18+
*
19+
* The time components are: [hours], [minutes], [seconds], [nanoseconds].
20+
*
21+
* A `DateTimePeriod` can be constructed using the same-named constructor function,
22+
* [parsed][DateTimePeriod.parse] from a string, or returned as the result of instant arithmetic operations (see [Instant.periodUntil]).
23+
* All these functions can return a [DatePeriod] value, which is a subtype of `DateTimePeriod`,
24+
* a special case that only stores date components, if all time components of the result happen to be zero.
25+
*/
1426
@Serializable(with = DateTimePeriodIso8601Serializer::class)
1527
// TODO: could be error-prone without explicitly named params
1628
public sealed class DateTimePeriod {
1729
internal abstract val totalMonths: Int
30+
31+
/**
32+
* The number of calendar days.
33+
*
34+
* Note that a calendar day is not identical to 24 hours, see [DateTimeUnit.DayBased] for details.
35+
*/
1836
public abstract val days: Int
1937
internal abstract val totalNanoseconds: Long
2038

39+
/**
40+
* The number of whole years.
41+
*/
2142
public val years: Int get() = totalMonths / 12
43+
44+
/**
45+
* The number of months in this period that don't form a whole year, so this value is always in `(-11..11)`.
46+
*/
2247
public val months: Int get() = totalMonths % 12
48+
49+
/**
50+
* The number of whole hours in this period.
51+
*/
2352
public open val hours: Int get() = (totalNanoseconds / 3_600_000_000_000).toInt()
53+
54+
/**
55+
* The number of whole minutes in this period that don't form a whole hour, so this value is always in `(-59..59)`.
56+
*/
2457
public open val minutes: Int get() = ((totalNanoseconds % 3_600_000_000_000) / 60_000_000_000).toInt()
58+
59+
/**
60+
* The number of whole seconds in this period that don't form a whole minute, so this value is always in `(-59..59)`.
61+
*/
2562
public open val seconds: Int get() = ((totalNanoseconds % 60_000_000_000) / NANOS_PER_ONE).toInt()
63+
64+
/**
65+
* The number of whole nanoseconds in this period that don't form a whole second, so this value is always in
66+
* `(-999_999_999..999_999_999)`.
67+
*/
2668
public open val nanoseconds: Int get() = (totalNanoseconds % NANOS_PER_ONE).toInt()
2769

2870
private fun allNonpositive() =
2971
totalMonths <= 0 && days <= 0 && totalNanoseconds <= 0 && (totalMonths or days != 0 || totalNanoseconds != 0L)
3072

73+
/**
74+
* Converts this period to the ISO-8601 string representation for durations.
75+
*
76+
* @see DateTimePeriod.parse
77+
*/
3178
override fun toString(): String = buildString {
3279
val sign = if (allNonpositive()) { append('-'); -1 } else 1
3380
append('P')
@@ -70,6 +117,21 @@ public sealed class DateTimePeriod {
70117
}
71118

72119
public companion object {
120+
/**
121+
* Parses a ISO-8601 duration string as a [DateTimePeriod].
122+
* If the time components are absent or equal to zero, returns a [DatePeriod].
123+
*
124+
* Additionally, we support the `W` signifier to represent weeks.
125+
*
126+
* Examples of durations in the ISO-8601 format:
127+
* - `P1Y40D` is one year and 40 days
128+
* - `-P1DT1H` is minus (one day and one hour)
129+
* - `P1DT-1H` is one day minus one hour
130+
* - `-PT0.000000001S` is minus one nanosecond
131+
*
132+
* @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [DateTimePeriod] are
133+
* exceeded.
134+
*/
73135
public fun parse(text: String): DateTimePeriod {
74136
fun parseException(message: String, position: Int): Nothing =
75137
throw DateTimeFormatException("Parse error at char $position: $message")
@@ -234,22 +296,58 @@ public sealed class DateTimePeriod {
234296
}
235297
}
236298

299+
/**
300+
* Parses the ISO-8601 duration representation as a [DateTimePeriod].
301+
*
302+
* See [DateTimePeriod.parse] for examples.
303+
*
304+
* @throws IllegalArgumentException if the text cannot be parsed or the boundaries of [DateTimePeriod] are exceeded.
305+
*
306+
* @see DateTimePeriod.parse
307+
*/
237308
public fun String.toDateTimePeriod(): DateTimePeriod = DateTimePeriod.parse(this)
238309

310+
/**
311+
* A special case of [DateTimePeriod] that only stores date components and has all time components equal to zero.
312+
*
313+
* A `DatePeriod` is automatically returned from all constructor functions for [DateTimePeriod] if it turns out that
314+
* the time components are zero.
315+
*
316+
* `DatePeriod` values are used in operations on [LocalDates][LocalDate] and are returned from operations on [LocalDates][LocalDate],
317+
* but they also can be passed anywhere a [DateTimePeriod] is expected.
318+
*/
239319
@Serializable(with = DatePeriodIso8601Serializer::class)
240320
public class DatePeriod internal constructor(
241321
internal override val totalMonths: Int,
242322
override val days: Int,
243323
) : DateTimePeriod() {
244324
public constructor(years: Int = 0, months: Int = 0, days: Int = 0): this(totalMonths(years, months), days)
245325
// avoiding excessive computations
326+
/** The number of whole hours in this period. Always equal to zero. */
246327
override val hours: Int get() = 0
328+
329+
/** The number of whole minutes in this period. Always equal to zero. */
247330
override val minutes: Int get() = 0
331+
332+
/** The number of whole seconds in this period. Always equal to zero. */
248333
override val seconds: Int get() = 0
334+
335+
/** The number of nanoseconds in this period. Always equal to zero. */
249336
override val nanoseconds: Int get() = 0
250337
internal override val totalNanoseconds: Long get() = 0
251338

252339
public companion object {
340+
/**
341+
* Parses the ISO-8601 duration representation as a [DatePeriod].
342+
*
343+
* This function is equivalent to [DateTimePeriod.parse], but will fail if any of the time components are not
344+
* zero.
345+
*
346+
* @throws IllegalArgumentException if the text cannot be parsed, the boundaries of [DatePeriod] are exceeded,
347+
* or any time components are not zero.
348+
*
349+
* @see DateTimePeriod.parse
350+
*/
253351
public fun parse(text: String): DatePeriod =
254352
when (val period = DateTimePeriod.parse(text)) {
255353
is DatePeriod -> period
@@ -258,6 +356,17 @@ public class DatePeriod internal constructor(
258356
}
259357
}
260358

359+
/**
360+
* Parses the ISO-8601 duration representation as a [DatePeriod].
361+
*
362+
* This function is equivalent to [DateTimePeriod.parse], but will fail if any of the time components are not
363+
* zero.
364+
*
365+
* @throws IllegalArgumentException if the text cannot be parsed, the boundaries of [DatePeriod] are exceeded,
366+
* or any time components are not zero.
367+
*
368+
* @see DateTimePeriod.parse
369+
*/
261370
public fun String.toDatePeriod(): DatePeriod = DatePeriod.parse(this)
262371

263372
private class DateTimePeriodImpl(
@@ -295,6 +404,19 @@ internal fun buildDateTimePeriod(totalMonths: Int = 0, days: Int = 0, totalNanos
295404
else
296405
DatePeriod(totalMonths, days)
297406

407+
/**
408+
* Constructs a new [DateTimePeriod]. If all the time components are zero, returns a [DatePeriod].
409+
*
410+
* It is recommended to always explicitly name the arguments when constructing this manually,
411+
* like `DateTimePeriod(years = 1, months = 12)`.
412+
*
413+
* The passed numbers are not stored as is but are normalized instead for human readability, so, for example,
414+
* `DateTimePeriod(months = 24)` becomes `DateTimePeriod(years = 2)`.
415+
*
416+
* @throws IllegalArgumentException if the total number of months in [years] and [months] overflows an [Int].
417+
* @throws IllegalArgumentException if the total number of months in [hours], [minutes], [seconds] and [nanoseconds]
418+
* overflows a [Long].
419+
*/
298420
public fun DateTimePeriod(
299421
years: Int = 0,
300422
months: Int = 0,
@@ -306,14 +428,31 @@ public fun DateTimePeriod(
306428
): DateTimePeriod = buildDateTimePeriod(totalMonths(years, months), days,
307429
totalNanoseconds(hours, minutes, seconds, nanoseconds))
308430

431+
/**
432+
* Constructs a [DateTimePeriod] from a [Duration].
433+
*
434+
* If the duration value is too big to be represented as a [Long] number of nanoseconds,
435+
* the result will be [Long.MAX_VALUE] nanoseconds.
436+
*/
437+
// TODO: maybe it's more consistent to throw here on overflow?
309438
public fun Duration.toDateTimePeriod(): DateTimePeriod = buildDateTimePeriod(totalNanoseconds = inWholeNanoseconds)
310439

440+
/**
441+
* Adds two [DateTimePeriod] instances.
442+
*
443+
* @throws DateTimeArithmeticException if arithmetic overflow happens.
444+
*/
311445
public operator fun DateTimePeriod.plus(other: DateTimePeriod): DateTimePeriod = buildDateTimePeriod(
312446
safeAdd(totalMonths, other.totalMonths),
313447
safeAdd(days, other.days),
314448
safeAdd(totalNanoseconds, other.totalNanoseconds),
315449
)
316450

451+
/**
452+
* Adds two [DatePeriod] instances.
453+
*
454+
* @throws DateTimeArithmeticException if arithmetic overflow happens.
455+
*/
317456
public operator fun DatePeriod.plus(other: DatePeriod): DatePeriod = DatePeriod(
318457
safeAdd(totalMonths, other.totalMonths),
319458
safeAdd(days, other.days),

0 commit comments

Comments
 (0)