5
5
6
6
package kotlinx.datetime
7
7
8
+ import kotlin.math.*
8
9
import kotlin.time.Duration
9
10
import kotlin.time.ExperimentalTime
10
11
11
12
12
13
// TODO: could be error-prone without explicitly named params
13
14
sealed class DateTimePeriod {
14
- abstract val years: Int
15
- abstract val months: Int
15
+ internal abstract val totalMonths: Int
16
16
abstract val days: Int
17
- abstract val hours: Int
18
- abstract val minutes: Int
19
- abstract val seconds: Long
20
- abstract val nanoseconds: Long
17
+ internal abstract val totalNanoseconds: Long
21
18
22
- private fun allNotPositive () =
23
- years <= 0 && months <= 0 && days <= 0 && hours <= 0 && minutes <= 0 && seconds <= 0 && nanoseconds <= 0 &&
24
- (years or months or days or hours or minutes != 0 || seconds or nanoseconds != 0L )
19
+ val years: Int get() = totalMonths / 12
20
+ val months: Int get() = totalMonths % 12
21
+ open val hours: Int get() = (totalNanoseconds / 3_600_000_000_000 ).toInt()
22
+ open val minutes: Int get() = ((totalNanoseconds % 3_600_000_000_000 ) / 60_000_000_000 ).toInt()
23
+ open val seconds: Int get() = ((totalNanoseconds % 60_000_000_000 ) / NANOS_PER_ONE ).toInt()
24
+ open val nanoseconds: Int get() = (totalNanoseconds % NANOS_PER_ONE ).toInt()
25
+
26
+ private fun allNonpositive () =
27
+ totalMonths <= 0 && days <= 0 && totalNanoseconds <= 0 && (totalMonths or days != 0 || totalNanoseconds != 0L )
25
28
26
29
override fun toString (): String = buildString {
27
- val sign = if (allNotPositive ()) { append(' -' ); - 1 } else 1
30
+ val sign = if (allNonpositive ()) { append(' -' ); - 1 } else 1
28
31
append(' P' )
29
32
if (years != 0 ) append(years * sign).append(' Y' )
30
33
if (months != 0 ) append(months * sign).append(' M' )
31
34
if (days != 0 ) append(days * sign).append(' D' )
32
35
var t = " T"
33
36
if (hours != 0 ) append(t).append(hours * sign).append(' H' ).also { t = " " }
34
37
if (minutes != 0 ) append(t).append(minutes * sign).append(' M' ).also { t = " " }
35
- if (seconds != 0L || nanoseconds != 0L ) {
36
- append(t).append(seconds * sign)
37
- if (nanoseconds != 0L ) append(' .' ).append((nanoseconds * sign).toString().padStart(9 , ' 0' ))
38
+ if (seconds or nanoseconds != 0 ) {
39
+ append(t)
40
+ append(when {
41
+ seconds != 0 -> seconds * sign
42
+ nanoseconds * sign < 0 -> " -0"
43
+ else -> " 0"
44
+ })
45
+ if (nanoseconds != 0 ) append(' .' ).append((nanoseconds.absoluteValue).toString().padStart(9 , ' 0' ))
38
46
append(' S' )
39
47
}
40
48
@@ -45,83 +53,95 @@ sealed class DateTimePeriod {
45
53
if (this == = other) return true
46
54
if (other !is DateTimePeriod ) return false
47
55
48
- if (years != other.years) return false
49
- if (months != other.months) return false
56
+ if (totalMonths != other.totalMonths) return false
50
57
if (days != other.days) return false
51
- if (hours != other.hours) return false
52
- if (minutes != other.minutes) return false
53
- if (seconds != other.seconds) return false
54
- if (nanoseconds != other.nanoseconds) return false
58
+ if (totalNanoseconds != other.totalNanoseconds) return false
55
59
56
60
return true
57
61
}
58
62
59
63
override fun hashCode (): Int {
60
- var result = years
61
- result = 31 * result + months
64
+ var result = totalMonths
62
65
result = 31 * result + days
63
- result = 31 * result + hours
64
- result = 31 * result + minutes
65
- result = 31 * result + seconds.hashCode()
66
- result = 31 * result + nanoseconds.hashCode()
66
+ result = 31 * result + totalNanoseconds.hashCode()
67
67
return result
68
68
}
69
69
70
70
// TODO: parsing from iso string
71
71
}
72
72
73
- class DatePeriod (
74
- override val years : Int = 0 ,
75
- override val months : Int = 0 ,
76
- override val days : Int = 0
73
+ class DatePeriod internal constructor(
74
+ internal override val totalMonths : Int ,
75
+ override val days : Int = 0 ,
77
76
) : DateTimePeriod() {
77
+ constructor (years: Int = 0 , months: Int = 0 , days: Int = 0 ): this (totalMonths(years, months), days)
78
+ // avoiding excessive computations
78
79
override val hours: Int get() = 0
79
80
override val minutes: Int get() = 0
80
- override val seconds: Long get() = 0
81
- override val nanoseconds: Long get() = 0
81
+ override val seconds: Int get() = 0
82
+ override val nanoseconds: Int get() = 0
83
+ override val totalNanoseconds: Long get() = 0
82
84
}
83
85
84
86
private class DateTimePeriodImpl (
85
- override val years : Int = 0 ,
86
- override val months : Int = 0 ,
87
- override val days : Int = 0 ,
88
- override val hours : Int = 0 ,
89
- override val minutes : Int = 0 ,
90
- override val seconds : Long = 0 ,
91
- override val nanoseconds : Long = 0
87
+ internal override val totalMonths : Int = 0 ,
88
+ override val days : Int = 0 ,
89
+ override val totalNanoseconds : Long = 0 ,
92
90
) : DateTimePeriod()
93
91
92
+ // TODO: these calculations fit in a JS Number. Possible to do an expect/actual here.
93
+ private fun totalMonths (years : Int , months : Int ): Int =
94
+ when (val totalMonths = years.toLong() * 12 + months.toLong()) {
95
+ in Int .MIN_VALUE .. Int .MAX_VALUE -> totalMonths.toInt()
96
+ else -> throw IllegalArgumentException (" The total number of months in $years years and $months months overflows an Int" )
97
+ }
98
+
99
+ private fun totalNanoseconds (hours : Int , minutes : Int , seconds : Int , nanoseconds : Long ): Long {
100
+ val totalMinutes: Long = hours.toLong() * 60 + minutes
101
+ // absolute value at most 61 * Int.MAX_VALUE
102
+ val totalMinutesAsSeconds: Long = totalMinutes * 60
103
+ // absolute value at most 61 * 60 * Int.MAX_VALUE < 64 * 64 * 2^31 = 2^43
104
+ val minutesAndNanosecondsAsSeconds: Long = totalMinutesAsSeconds + nanoseconds / NANOS_PER_ONE
105
+ // absolute value at most 2^43 + 2^63 / 10^9 < 2^43 + 2^34 < 2^44
106
+ val totalSeconds = minutesAndNanosecondsAsSeconds + seconds
107
+ // absolute value at most 2^44 + 2^31 < 2^45
108
+ return try {
109
+ multiplyAndAdd(totalSeconds, 1_000_000_000 , nanoseconds % NANOS_PER_ONE )
110
+ } catch (e: ArithmeticException ) {
111
+ throw IllegalArgumentException (" The total number of nanoseconds in $hours hours, $minutes minutes, $seconds seconds, and $nanoseconds nanoseconds overflows a Long" )
112
+ }
113
+ }
114
+
115
+ internal fun buildDateTimePeriod (totalMonths : Int = 0, days : Int = 0, totalNanoseconds : Long ): DateTimePeriod =
116
+ if (totalNanoseconds != 0L )
117
+ DateTimePeriodImpl (totalMonths, days, totalNanoseconds)
118
+ else
119
+ DatePeriod (totalMonths, days)
120
+
94
121
fun DateTimePeriod (
95
122
years : Int = 0,
96
123
months : Int = 0,
97
124
days : Int = 0,
98
125
hours : Int = 0,
99
126
minutes : Int = 0,
100
- seconds : Long = 0,
127
+ seconds : Int = 0,
101
128
nanoseconds : Long = 0
102
- ): DateTimePeriod = if (hours or minutes != 0 || seconds or nanoseconds != 0L )
103
- DateTimePeriodImpl (years, months, days, hours, minutes, seconds, nanoseconds)
104
- else
105
- DatePeriod (years, months, days)
129
+ ): DateTimePeriod = buildDateTimePeriod(totalMonths(years, months), days,
130
+ totalNanoseconds(hours, minutes, seconds, nanoseconds))
106
131
107
132
@OptIn(ExperimentalTime ::class )
108
133
fun Duration.toDateTimePeriod (): DateTimePeriod = toComponents { hours, minutes, seconds, nanoseconds ->
109
- DateTimePeriod (hours = hours, minutes = minutes , seconds = seconds.toLong() , nanoseconds = nanoseconds .toLong())
134
+ buildDateTimePeriod(totalNanoseconds = totalNanoseconds( hours, minutes, seconds, nanoseconds.toLong() ))
110
135
}
111
136
112
- operator fun DateTimePeriod.plus (other : DateTimePeriod ): DateTimePeriod = DateTimePeriod (
113
- safeAdd(this .years, other.years),
114
- safeAdd(this .months, other.months),
115
- safeAdd(this .days, other.days),
116
- safeAdd(this .hours, other.hours),
117
- safeAdd(this .minutes, other.minutes),
118
- safeAdd(this .seconds, other.seconds),
119
- safeAdd(this .nanoseconds, other.nanoseconds)
137
+ operator fun DateTimePeriod.plus (other : DateTimePeriod ): DateTimePeriod = buildDateTimePeriod(
138
+ safeAdd(totalMonths, other.totalMonths),
139
+ safeAdd(days, other.days),
140
+ safeAdd(totalNanoseconds, other.totalNanoseconds),
120
141
)
121
142
122
143
operator fun DatePeriod.plus (other : DatePeriod ): DatePeriod = DatePeriod (
123
- safeAdd(this .years, other.years),
124
- safeAdd(this .months, other.months),
125
- safeAdd(this .days, other.days)
144
+ safeAdd(totalMonths, other.totalMonths),
145
+ safeAdd(days, other.days),
126
146
)
127
147
0 commit comments