1
+ /*
2
+ * Copyright 2019-2022 JetBrains s.r.o.
3
+ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
4
+ */
5
+
6
+ package kotlinx.datetime
7
+
8
+ import kotlinx.datetime.internal.clampToInt
9
+ import kotlinx.datetime.internal.safeAdd
10
+ import kotlinx.datetime.internal.safeMultiplyOrClamp
11
+ import kotlin.random.Random
12
+ import kotlin.random.nextLong
13
+
14
+ private class YearMonthProgressionIterator (private val iterator : LongIterator ) : Iterator<YearMonth> {
15
+ override fun hasNext (): Boolean = iterator.hasNext()
16
+ override fun next (): YearMonth = YearMonth .fromProlepticMonth(iterator.next())
17
+ }
18
+
19
+ /* *
20
+ * A progression of values of type [YearMonth].
21
+ *
22
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.progressionWithStep
23
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.reversedProgression
24
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.firstAndLast
25
+ */
26
+ public open class YearMonthProgression
27
+ internal constructor (internal val longProgression: LongProgression ) : Collection <YearMonth > {
28
+
29
+ internal constructor (
30
+ start: YearMonth ,
31
+ endInclusive: YearMonth ,
32
+ step: Long
33
+ ) : this (LongProgression .fromClosedRange(start.prolepticMonth, endInclusive.prolepticMonth, step))
34
+
35
+ /* *
36
+ * Returns the first [YearMonth] of the progression
37
+ */
38
+ public val first: YearMonth = YearMonth .fromProlepticMonth(longProgression.first)
39
+
40
+ /* *
41
+ * Returns the last [YearMonth] of the progression
42
+ */
43
+ public val last: YearMonth = YearMonth .fromProlepticMonth(longProgression.last)
44
+
45
+ /* *
46
+ * Returns an [Iterator] that traverses the progression from [first] to [last]
47
+ */
48
+ override fun iterator (): Iterator <YearMonth > = YearMonthProgressionIterator (longProgression.iterator())
49
+
50
+ /* *
51
+ * Returns true iff the progression contains no values.
52
+ * i.e. [first] < [last] if step is positive, or [first] > [last] if step is negative.
53
+ */
54
+ public override fun isEmpty (): Boolean = longProgression.isEmpty()
55
+
56
+ /* *
57
+ * Returns a string representation of the progression.
58
+ * Uses the range operator notation if the progression is increasing, and `downTo` if it is decreasing.
59
+ * The step is referenced in days.
60
+ */
61
+ override fun toString (): String =
62
+ if (longProgression.step > 0 ) " $first ..$last step ${longProgression.step} M"
63
+ else " $first downTo $last step ${longProgression.step} M"
64
+
65
+ /* *
66
+ * Returns the number of months in the progression.
67
+ * Returns [Int.MAX_VALUE] if the number of months overflows [Int]
68
+ */
69
+ override val size: Int
70
+ get() = longProgression.size
71
+
72
+ /* *
73
+ * Returns true iff every element in [elements] is a member of the progression.
74
+ */
75
+ override fun containsAll (elements : Collection <YearMonth >): Boolean =
76
+ (elements as Collection <* >).all { it is YearMonth && contains(it) }
77
+
78
+ /* *
79
+ * Returns true iff [value] is a member of the progression.
80
+ */
81
+ override fun contains (value : YearMonth ): Boolean {
82
+ @Suppress(" USELESS_CAST" )
83
+ if ((value as Any? ) !is YearMonth ) return false
84
+
85
+ return longProgression.contains(value.prolepticMonth)
86
+ }
87
+
88
+ override fun equals (other : Any? ): Boolean =
89
+ other is YearMonthProgression && longProgression == other.longProgression
90
+
91
+ override fun hashCode (): Int = longProgression.hashCode()
92
+
93
+ public companion object {
94
+ internal fun fromClosedRange (
95
+ rangeStart : YearMonth ,
96
+ rangeEnd : YearMonth ,
97
+ stepValue : Long ,
98
+ stepUnit : DateTimeUnit .MonthBased
99
+ ): YearMonthProgression =
100
+ YearMonthProgression (rangeStart, rangeEnd, safeMultiplyOrClamp(stepValue, stepUnit.months.toLong()))
101
+ }
102
+ }
103
+
104
+ /* *
105
+ * A range of values of type [YearMonth].
106
+ *
107
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.simpleRangeCreation
108
+ */
109
+ public class YearMonthRange (start : YearMonth , endInclusive : YearMonth ) : YearMonthProgression(start, endInclusive, 1 ),
110
+ ClosedRange <YearMonth >, OpenEndRange <YearMonth > {
111
+ /* *
112
+ * Returns the lower bound of the range, inclusive.
113
+ */
114
+ override val start: YearMonth get() = first
115
+
116
+ /* *
117
+ * Returns the upper bound of the range, inclusive.
118
+ */
119
+ override val endInclusive: YearMonth get() = last
120
+
121
+ /* *
122
+ * Returns the upper bound of the range, exclusive.
123
+ */
124
+ @Deprecated(
125
+ " This throws an exception if the exclusive end if not inside " +
126
+ " the platform-specific boundaries for YearMonth. " +
127
+ " The 'endInclusive' property does not throw and should be preferred." ,
128
+ level = DeprecationLevel .WARNING
129
+ )
130
+ override val endExclusive: YearMonth
131
+ get() {
132
+ if (last == YearMonth .MAX )
133
+ error(" Cannot return the exclusive upper bound of a range that includes YearMonth.MAX." )
134
+ return endInclusive.plus(1 , DateTimeUnit .MONTH )
135
+ }
136
+
137
+ /* *
138
+ * Returns true iff [value] is contained within the range.
139
+ * i.e. [value] is between [start] and [endInclusive].
140
+ */
141
+ @Suppress(" ConvertTwoComparisonsToRangeCheck" )
142
+ override fun contains (value : YearMonth ): Boolean {
143
+ @Suppress(" USELESS_CAST" )
144
+ if ((value as Any? ) !is YearMonth ) return false
145
+
146
+ return first <= value && value <= last
147
+ }
148
+
149
+ /* *
150
+ * Returns true iff there are no months contained within the range.
151
+ */
152
+ override fun isEmpty (): Boolean = first > last
153
+
154
+ /* *
155
+ * Returns a string representation of the range using the range operator notation.
156
+ */
157
+ override fun toString (): String = " $first ..$last "
158
+
159
+ public companion object {
160
+ /* * An empty range of values of type YearMonth. */
161
+ public val EMPTY : YearMonthRange = YearMonthRange (YearMonth (0 , 2 ), YearMonth (0 , 1 ))
162
+
163
+ internal fun fromRangeUntil (start : YearMonth , endExclusive : YearMonth ): YearMonthRange {
164
+ return if (endExclusive == YearMonth .MIN ) EMPTY else fromRangeTo(
165
+ start,
166
+ endExclusive.minus(1 , DateTimeUnit .MONTH )
167
+ )
168
+ }
169
+
170
+ internal fun fromRangeTo (start : YearMonth , endInclusive : YearMonth ): YearMonthRange {
171
+ return YearMonthRange (start, endInclusive)
172
+ }
173
+ }
174
+ }
175
+
176
+ /* *
177
+ * Returns the first [YearMonth] of the [YearMonthProgression].
178
+ *
179
+ * @throws NoSuchElementException if the progression is empty.
180
+ *
181
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.firstAndLast
182
+ */
183
+ public fun YearMonthProgression.first (): YearMonth {
184
+ if (isEmpty())
185
+ throw NoSuchElementException (" Progression $this is empty." )
186
+ return this .first
187
+ }
188
+
189
+ /* *
190
+ * Returns the last [YearMonth] of the [YearMonthProgression].
191
+ *
192
+ * @throws NoSuchElementException if the progression is empty.
193
+ *
194
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.firstAndLast
195
+ */
196
+ public fun YearMonthProgression.last (): YearMonth {
197
+ if (isEmpty())
198
+ throw NoSuchElementException (" Progression $this is empty." )
199
+ return this .last
200
+ }
201
+
202
+ /* *
203
+ * Returns the first [YearMonth] of the [YearMonthProgression], or null if the progression is empty.
204
+ *
205
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.firstAndLast
206
+ */
207
+ public fun YearMonthProgression.firstOrNull (): YearMonth ? = if (isEmpty()) null else this .first
208
+
209
+ /* *
210
+ * Returns the last [YearMonth] of the [YearMonthProgression], or null if the progression is empty.
211
+ *
212
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.firstAndLast
213
+ */
214
+ public fun YearMonthProgression.lastOrNull (): YearMonth ? = if (isEmpty()) null else this .last
215
+
216
+ /* *
217
+ * Returns a reversed [YearMonthProgression], i.e. one that goes from [last] to [first].
218
+ * The sign of the step is switched, in order to reverse the direction of the progression.
219
+ *
220
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.reversedProgression
221
+ */
222
+ public fun YearMonthProgression.reversed (): YearMonthProgression = YearMonthProgression (longProgression.reversed())
223
+
224
+ /* *
225
+ * Returns a [YearMonthProgression] with the same start and end, but a changed step value.
226
+ *
227
+ * **Pitfall**: the value parameter represents the magnitude of the step,
228
+ * not the direction, and therefore must be positive.
229
+ * Its sign will be matched to the sign of the existing step, in order to maintain the direction of the progression.
230
+ * If you wish to switch the direction of the progression, use [YearMonthProgression.reversed]
231
+ *
232
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.progressionWithStep
233
+ */
234
+ public fun YearMonthProgression.step (value : Int , unit : DateTimeUnit .MonthBased ): YearMonthProgression =
235
+ step(value.toLong(), unit)
236
+
237
+ /* *
238
+ * Returns a [YearMonthProgression] with the same start and end, but a changed step value.
239
+ *
240
+ * **Pitfall**: the value parameter represents the magnitude of the step,
241
+ * not the direction, and therefore must be positive.
242
+ * Its sign will be matched to the sign of the existing step, in order to maintain the direction of the progression.
243
+ * If you wish to switch the direction of the progression, use [YearMonthProgression.reversed]
244
+ *
245
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.progressionWithStep
246
+ */
247
+ public fun YearMonthProgression.step (value : Long , unit : DateTimeUnit .MonthBased ): YearMonthProgression =
248
+ YearMonthProgression (longProgression.step(safeMultiplyOrClamp(value, unit.months.toLong())))
249
+
250
+ /* *
251
+ * Creates a [YearMonthProgression] from `this` down to [that], inclusive.
252
+ *
253
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.simpleRangeCreation
254
+ */
255
+ public infix fun YearMonth.downTo (that : YearMonth ): YearMonthProgression =
256
+ YearMonthProgression .fromClosedRange(this , that, - 1 , DateTimeUnit .MONTH )
257
+
258
+ /* *
259
+ * Returns a random [YearMonth] within the bounds of the [YearMonthProgression].
260
+ *
261
+ * Takes the step into account;
262
+ * will not return any value within the range that would be skipped over by the progression.
263
+ *
264
+ * @throws IllegalArgumentException if the progression is empty.
265
+ *
266
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.random
267
+ */
268
+ public fun YearMonthProgression.random (random : Random = Random ): YearMonth =
269
+ if (isEmpty()) throw NoSuchElementException (" Cannot get random in empty range: $this " )
270
+ else longProgression.random(random).let (YearMonth .Companion ::fromProlepticMonth)
271
+
272
+ /* *
273
+ * Returns a random [YearMonth] within the bounds of the [YearMonthProgression] or null if the progression is empty.
274
+ *
275
+ * Takes the step into account;
276
+ * will not return any value within the range that would be skipped over by the progression.
277
+ *
278
+ * @sample kotlinx.datetime.test.samples.YearMonthRangeSamples.random
279
+ */
280
+ public fun YearMonthProgression.randomOrNull (random : Random = Random ): YearMonth ? = longProgression.randomOrNull(random)
281
+ ?.let (YearMonth .Companion ::fromProlepticMonth)
282
+
283
+ // this implementation is incorrect in general
284
+ // (for example, `(Long.MIN_VALUE..Long.MAX_VALUE).random()` throws an exception),
285
+ // but for the range of epoch days in YearMonth it's good enough
286
+ private fun LongProgression.random (random : Random = Random ): Long =
287
+ random.nextLong(0L .. (last - first) / step) * step + first
288
+
289
+ // incorrect in general; see `random` just above
290
+ private fun LongProgression.randomOrNull (random : Random = Random ): Long? = if (isEmpty()) null else random(random)
291
+
292
+ // this implementation is incorrect in general (for example, `(Long.MIN_VALUE..Long.MAX_VALUE).step(5).contains(2)`
293
+ // returns `false` incorrectly https://www.wolframalpha.com/input?i=-2%5E63+%2B+1844674407370955162+*+5),
294
+ // but for the range of epoch days in YearMonth it's good enough
295
+ private fun LongProgression.contains (value : Long ): Boolean =
296
+ value in (if (step > 0 ) first.. last else last.. first) && (value - first) % step == 0L
297
+
298
+ // this implementation is incorrect in general (for example, `Long.MIN_VALUE..Long.MAX_VALUE` has size == 0),
299
+ // but for the range of epoch days in YearMonth it's good enough
300
+ private val LongProgression .size: Int
301
+ get() = if (isEmpty()) 0 else try {
302
+ (safeAdd(last, - first) / step + 1 ).clampToInt()
303
+ } catch (e: ArithmeticException ) {
304
+ Int .MAX_VALUE
305
+ }
0 commit comments