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