Skip to content

Commit 7c0a62b

Browse files
committed
Move some of the classes to separate files
1 parent abc4ceb commit 7c0a62b

File tree

7 files changed

+241
-230
lines changed

7 files changed

+241
-230
lines changed

core/linux/src/TimeZoneNative.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual ove
3030
actual override fun atStartOfDay(date: LocalDate): Instant = memScoped {
3131
val ldt = LocalDateTime(date, LocalTime.MIN)
3232
when (val info = tzid.infoAtDatetime(ldt)) {
33-
is Regular -> ldt.toInstant(info.offset)
34-
is Gap -> info.start
35-
is Overlap -> ldt.toInstant(info.offsetBefore)
33+
is OffsetInfo.Regular -> ldt.toInstant(info.offset)
34+
is OffsetInfo.Gap -> info.start
35+
is OffsetInfo.Overlap -> ldt.toInstant(info.offsetBefore)
3636
}
3737
}
3838

3939
actual override fun atZone(dateTime: LocalDateTime, preferred: UtcOffset?): ZonedDateTime =
4040
when (val info = tzid.infoAtDatetime(dateTime)) {
41-
is Regular -> ZonedDateTime(dateTime, this, info.offset)
42-
is Gap -> {
41+
is OffsetInfo.Regular -> ZonedDateTime(dateTime, this, info.offset)
42+
is OffsetInfo.Gap -> {
4343
try {
4444
ZonedDateTime(dateTime.plusSeconds(info.transitionDurationSeconds), this, info.offsetAfter)
4545
} catch (e: IllegalArgumentException) {
@@ -50,7 +50,7 @@ internal actual class RegionTimeZone(private val tzid: TimeZoneRules, actual ove
5050
}
5151
}
5252

53-
is Overlap -> ZonedDateTime(dateTime, this,
53+
is OffsetInfo.Overlap -> ZonedDateTime(dateTime, this,
5454
if (info.offsetAfter == preferred) info.offsetAfter else info.offsetBefore)
5555
}
5656

core/linux/test/TimeZoneRulesCompleteTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,18 @@ class TimeZoneRulesCompleteTest {
3434
val infoAt1 = rules.infoAtDatetime(beforeTransition.localDateTime)
3535
val infoAt2 = rules.infoAtDatetime(afterTransition.localDateTime)
3636
assertEquals(infoAt1, infoAt2)
37-
assertIs<Regular>(infoAt1)
37+
assertIs<OffsetInfo.Regular>(infoAt1)
3838
} else if (afterTransition.localDateTime < beforeTransition.localDateTime) {
3939
// Overlap
4040
val infoAt1 = rules.infoAtDatetime(beforeTransition.localDateTime.plusSeconds(-1))
4141
val infoAt2 = rules.infoAtDatetime(afterTransition.localDateTime.plusSeconds(1))
4242
assertEquals(infoAt1, infoAt2)
43-
assertIs<Overlap>(infoAt1)
43+
assertIs<OffsetInfo.Overlap>(infoAt1)
4444
} else if (afterTransition.localDateTime > beforeTransition.localDateTime) {
4545
// Gap
4646
val infoAt1 = rules.infoAtDatetime(afterTransition.localDateTime.plusSeconds(-1))
4747
val infoAt2 = rules.infoAtDatetime(beforeTransition.localDateTime.plusSeconds(1))
48-
assertIs<Gap>(infoAt1)
48+
assertIs<OffsetInfo.Gap>(infoAt1)
4949
assertEquals(infoAt1, infoAt2)
5050
}
5151
} catch (e: Throwable) {
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
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.internal
7+
8+
import kotlinx.datetime.*
9+
10+
/**
11+
* A rule expressing how to create a date in a given year.
12+
*
13+
* Some examples of expressible dates:
14+
* * the 16th March
15+
* * the Sunday on or after the 16th March
16+
* * the Sunday on or before the 16th March
17+
* * the last Sunday in February
18+
* * the 300th day of the year
19+
* * the last day of February
20+
*/
21+
internal interface DateOfYear {
22+
/**
23+
* Converts this date-time to an [Instant] in the given [year],
24+
* using the knowledge of the offset that's in effect at the resulting date-time.
25+
*/
26+
fun toLocalDate(year: Int): LocalDate
27+
}
28+
29+
/**
30+
* The day of year, in the 1..365 range. During leap years, 29th February is counted as the 60th day of the year.
31+
* The number 366 is not supported, as outside the leap years, there are only 365 days in a year.
32+
*/
33+
internal class JulianDayOfYear(val dayOfYear: Int) : DateOfYear {
34+
init {
35+
require(dayOfYear in 1..365)
36+
}
37+
override fun toLocalDate(year: Int): LocalDate =
38+
LocalDate(year, 1, 1).plusDays(dayOfYear - 1)
39+
40+
override fun toString(): String = "JulianDayOfYear($dayOfYear)"
41+
}
42+
43+
/**
44+
* The day of year, in the 1..365 range. During leap years, 29th February is skipped.
45+
*/
46+
internal fun JulianDayOfYearSkippingLeapDate(dayOfYear: Int) : DateOfYear {
47+
require(dayOfYear in 1..365)
48+
// In this form, the `dayOfYear` corresponds exactly to a specific month and day.
49+
// For example, `dayOfYear = 60` is always 1st March, even in leap years.
50+
// We take a non-leap year, as in that case, this is the same as JulianDayOfYear, so regular addition works.
51+
val date = LocalDate(2011, 1, 1).plusDays(dayOfYear - 1)
52+
return MonthDayOfYear(date.month, MonthDayOfYear.TransitionDay.ExactlyDayOfMonth(date.dayOfMonth))
53+
}
54+
55+
internal class MonthDayOfYear(val month: Month, val day: TransitionDay) : DateOfYear {
56+
override fun toLocalDate(year: Int): LocalDate = day.resolve(year, month)
57+
58+
/**
59+
* The day of month when the transition occurs.
60+
*/
61+
sealed interface TransitionDay {
62+
/**
63+
* The first given [dayOfWeek] of the month that is not earlier than [atLeastDayOfMonth].
64+
*/
65+
class First(val dayOfWeek: DayOfWeek, val atLeastDayOfMonth: Int = 1) : TransitionDay {
66+
override fun resolve(year: Int, month: Month): LocalDate =
67+
LocalDate(year, month, atLeastDayOfMonth).nextOrSame(dayOfWeek)
68+
69+
override fun toString(): String = "the first $dayOfWeek" +
70+
(if (atLeastDayOfMonth > 1) " on or after $atLeastDayOfMonth" else "")
71+
}
72+
73+
companion object {
74+
/**
75+
* The [n]th given [dayOfWeek] in the month.
76+
*/
77+
fun Nth(dayOfWeek: DayOfWeek, n: Int): TransitionDay =
78+
First(dayOfWeek, (n-1) * 7 + 1)
79+
}
80+
81+
/**
82+
* The last given [dayOfWeek] of the month that is not later than [atMostDayOfMonth].
83+
*/
84+
class Last(val dayOfWeek: DayOfWeek, val atMostDayOfMonth: Int?) : TransitionDay {
85+
override fun resolve(year: Int, month: Month): LocalDate {
86+
val dayOfMonth = atMostDayOfMonth ?: month.number.monthLength(isLeapYear(year))
87+
return LocalDate(year, month, dayOfMonth).previousOrSame(dayOfWeek)
88+
}
89+
90+
override fun toString(): String = "the last $dayOfWeek" +
91+
(atMostDayOfMonth?.let { " on or before $it" } ?: "")
92+
}
93+
94+
/**
95+
* Exactly the given [dayOfMonth].
96+
*/
97+
class ExactlyDayOfMonth(val dayOfMonth: Int) : TransitionDay {
98+
override fun resolve(year: Int, month: Month): LocalDate = LocalDate(year, month, dayOfMonth)
99+
100+
override fun toString(): String = "$dayOfMonth"
101+
}
102+
103+
fun resolve(year: Int, month: Month): LocalDate
104+
}
105+
106+
override fun toString(): String = "$month, $day"
107+
}
108+
109+
internal class MonthDayTime(
110+
/**
111+
* The date.
112+
*/
113+
val date: DateOfYear,
114+
/**
115+
* The procedure to calculate the local time.
116+
*/
117+
val time: TransitionLocaltime,
118+
/**
119+
* The definition of how the offset in which the local date-time is expressed.
120+
*/
121+
val offset: OffsetResolver,
122+
) {
123+
124+
/**
125+
* Converts this [MonthDayTime] to an [Instant] in the given [year],
126+
* using the knowledge of the offset that's in effect at the resulting date-time.
127+
*/
128+
fun toInstant(year: Int, effectiveOffset: UtcOffset): Instant {
129+
val localDateTime = time.resolve(date.toLocalDate(year))
130+
return when (this.offset) {
131+
is OffsetResolver.WallClockOffset -> localDateTime.toInstant(effectiveOffset)
132+
is OffsetResolver.FixedOffset -> localDateTime.toInstant(this.offset.offset)
133+
}
134+
}
135+
136+
/**
137+
* Describes how the offset in which the local date-time is expressed is defined.
138+
*/
139+
sealed interface OffsetResolver {
140+
/**
141+
* The offset is the one currently used by the wall clock.
142+
*/
143+
object WallClockOffset : OffsetResolver {
144+
override fun toString(): String = "wall clock offset"
145+
}
146+
147+
/**
148+
* The offset is fixed to a specific value.
149+
*/
150+
class FixedOffset(val offset: UtcOffset) : OffsetResolver {
151+
override fun toString(): String = offset.toString()
152+
}
153+
}
154+
155+
/**
156+
* The local time of day at which the transition occurs.
157+
*/
158+
class TransitionLocaltime(val seconds: Int) {
159+
constructor(time: LocalTime) : this(time.toSecondOfDay())
160+
161+
constructor(hour: Int, minute: Int, second: Int) : this(hour * 3600 + minute * 60 + second)
162+
163+
fun resolve(date: LocalDate): LocalDateTime = date.atTime(LocalTime(0, 0)).plusSeconds(seconds)
164+
165+
override fun toString(): String = if (seconds < 86400)
166+
LocalTime.ofSecondOfDay(seconds, 0).toString() else "$seconds seconds since the day start"
167+
}
168+
169+
override fun toString(): String = "$date, $time, $offset"
170+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2019-2023 JetBrains s.r.o. and contributors.
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.internal
7+
8+
import kotlinx.datetime.*
9+
10+
internal sealed interface OffsetInfo {
11+
data class Gap(
12+
val start: Instant,
13+
val offsetBefore: UtcOffset,
14+
val offsetAfter: UtcOffset
15+
): OffsetInfo {
16+
init {
17+
check(offsetBefore.totalSeconds < offsetAfter.totalSeconds)
18+
}
19+
20+
val transitionDurationSeconds: Int get() = offsetAfter.totalSeconds - offsetBefore.totalSeconds
21+
}
22+
23+
data class Overlap(
24+
val start: Instant,
25+
val offsetBefore: UtcOffset,
26+
val offsetAfter: UtcOffset
27+
): OffsetInfo {
28+
init {
29+
check(offsetBefore.totalSeconds > offsetAfter.totalSeconds)
30+
}
31+
}
32+
33+
data class Regular(
34+
val offset: UtcOffset
35+
) : OffsetInfo
36+
}
37+
38+
internal fun OffsetInfo(transitionInstant: Instant, offsetBefore: UtcOffset, offsetAfter: UtcOffset): OffsetInfo =
39+
if (offsetBefore == offsetAfter) {
40+
OffsetInfo.Regular(offsetBefore)
41+
} else if (offsetBefore.totalSeconds < offsetAfter.totalSeconds) {
42+
OffsetInfo.Gap(transitionInstant, offsetBefore, offsetAfter)
43+
} else {
44+
OffsetInfo.Overlap(transitionInstant, offsetBefore, offsetAfter)
45+
}
46+

0 commit comments

Comments
 (0)