@@ -6,40 +6,44 @@ package kotlinx.datetime.test
6
6
7
7
import kotlinx.datetime.*
8
8
import kotlinx.cinterop.*
9
+ import kotlinx.datetime.internal.NANOS_PER_ONE
9
10
import platform.Foundation.*
10
11
import kotlin.math.*
11
12
import kotlin.random.*
12
13
import kotlin.test.*
13
14
14
15
class ConvertersTest {
15
16
16
- private val dateFormatter = NSDateFormatter ()
17
- private val locale = NSLocale .localeWithLocaleIdentifier(" en_US_POSIX" )
18
- private val gregorian = NSCalendar .calendarWithIdentifier(NSCalendarIdentifierGregorian )!!
17
+ private val isoCalendar = NSCalendar .calendarWithIdentifier(NSCalendarIdentifierISO8601 )!!
18
+ @OptIn(UnsafeNumber ::class )
19
19
private val utc = NSTimeZone .timeZoneForSecondsFromGMT(0 )
20
20
21
- init {
22
- dateFormatter.locale = locale
23
- dateFormatter.dateFormat = " yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
24
- dateFormatter.calendar = gregorian
25
- dateFormatter.timeZone = utc
21
+ @Test
22
+ fun testToFromNSDateNow () {
23
+ // as of writing, the max difference in such round-trips across 10^8 iterations was 120 nanoseconds,
24
+ // so we allow for four times that
25
+ repeat(10 ) {
26
+ val now = Clock .System .now()
27
+ assertEqualUpToHalfMicrosecond(now, now.toNSDate().toKotlinInstant())
28
+ }
29
+ repeat(10 ) {
30
+ val nowInSwift = NSDate ()
31
+ assertEqualUpToHalfMicrosecond(nowInSwift, nowInSwift.toKotlinInstant().toNSDate())
32
+ }
26
33
}
27
34
28
35
@Test
29
36
fun testToFromNSDate () {
30
- // The first day on the Gregorian calendar. The day before it is 1582-10-04 in the Julian calendar.
31
- val gregorianCalendarStart = Instant .parse(" 1582-10-15T00:00:00Z" ).toEpochMilliseconds()
32
- val minBoundMillis = (NSDate .distantPast.timeIntervalSince1970 * 1000 + 0.5 ).toLong()
33
- val maxBoundMillis = (NSDate .distantFuture.timeIntervalSince1970 * 1000 - 0.5 ).toLong()
37
+ val secondsBound = NSDate .distantPast.timeIntervalSince1970.toLong() until
38
+ NSDate .distantFuture.timeIntervalSince1970.toLong()
34
39
repeat(STRESS_TEST_ITERATIONS ) {
35
- val millis = Random .nextLong(minBoundMillis, maxBoundMillis)
36
- val instant = Instant .fromEpochMilliseconds(millis)
37
- val date = instant.toNSDate()
38
- // Darwin's date printer dynamically adjusts to switching between calendars, while our Instant does not.
39
- if (millis >= gregorianCalendarStart) {
40
- assertEquals(instant, Instant .parse(dateFormatter.stringFromDate(date)))
41
- }
42
- assertEquals(instant, date.toKotlinInstant())
40
+ val seconds = Random .nextLong(secondsBound)
41
+ val nanos = Random .nextInt(0 , NANOS_PER_ONE )
42
+ val instant = Instant .fromEpochSeconds(seconds, nanos)
43
+ // at most 6 microseconds difference was observed in 10^8 iterations
44
+ assertEqualUpToTenMicroseconds(instant, instant.toNSDate().toKotlinInstant())
45
+ // while here, no difference at all was observed
46
+ assertEqualUpToOneNanosecond(instant.toNSDate(), instant.toNSDate().toKotlinInstant().toNSDate())
43
47
}
44
48
}
45
49
@@ -79,32 +83,60 @@ class ConvertersTest {
79
83
80
84
@Test
81
85
fun localDateToNSDateComponentsTest () {
82
- val date = LocalDate .parse( " 2019-02-04 " )
86
+ val date = LocalDate ( 2019 , 2 , 4 )
83
87
val components = date.toNSDateComponents()
84
88
components.timeZone = utc
85
- val nsDate = gregorian.dateFromComponents(components)!!
86
- val formatter = NSDateFormatter ()
87
- formatter.timeZone = utc
88
- formatter.dateFormat = " yyyy-MM-dd"
89
+ val nsDate = isoCalendar.dateFromComponents(components)!!
90
+ val formatter = NSDateFormatter ().apply {
91
+ timeZone = utc
92
+ dateFormat = " yyyy-MM-dd"
93
+ }
89
94
assertEquals(" 2019-02-04" , formatter.stringFromDate(nsDate))
90
95
}
91
96
92
97
@Test
93
98
fun localDateTimeToNSDateComponentsTest () {
94
- val str = " 2019-02-04T23:59:30.543"
95
- val dateTime = LocalDateTime .parse(str)
99
+ val dateTime = LocalDate (2019 , 2 , 4 ).atTime(23 , 59 , 30 , 123456000 )
96
100
val components = dateTime.toNSDateComponents()
97
101
components.timeZone = utc
98
- val nsDate = gregorian .dateFromComponents(components)!!
99
- assertEquals(str + " Z " , dateFormatter.stringFromDate(nsDate ))
102
+ val nsDate = isoCalendar .dateFromComponents(components)!!
103
+ assertEqualUpToHalfMicrosecond(dateTime.toInstant( TimeZone . UTC ), nsDate.toKotlinInstant( ))
100
104
}
101
105
102
- @OptIn(kotlinx.cinterop. ExperimentalForeignApi ::class )
106
+ @OptIn(ExperimentalForeignApi :: class , UnsafeNumber ::class )
103
107
private fun zoneOffsetCheck (timeZone : FixedOffsetTimeZone , hours : Int , minutes : Int ) {
104
108
val nsTimeZone = timeZone.toNSTimeZone()
105
109
val kotlinTimeZone = nsTimeZone.toKotlinTimeZone()
106
110
assertEquals(hours * 3600 + minutes * 60 , nsTimeZone.secondsFromGMT.convert())
107
111
assertIs<FixedOffsetTimeZone >(kotlinTimeZone)
108
112
assertEquals(timeZone.offset, kotlinTimeZone.offset)
109
113
}
114
+
115
+ private fun assertEqualUpToTenMicroseconds (instant1 : Instant , instant2 : Instant ) {
116
+ if ((instant1 - instant2).inWholeMicroseconds.absoluteValue > 10 ) {
117
+ throw AssertionError (" Expected $instant1 to be equal to $instant2 up to 10 microseconds" )
118
+ }
119
+ }
120
+
121
+ private fun assertEqualUpToHalfMicrosecond (instant1 : Instant , instant2 : Instant ) {
122
+ if ((instant1 - instant2).inWholeNanoseconds.absoluteValue > 500 ) {
123
+ throw AssertionError (" Expected $instant1 to be equal to $instant2 up to 0.5 microseconds" )
124
+ }
125
+ }
126
+
127
+ private fun assertEqualUpToHalfMicrosecond (date1 : NSDate , date2 : NSDate ) {
128
+ val difference = abs(date1.timeIntervalSinceDate(date2) * 1000000 )
129
+ if (difference > 0.5 ) {
130
+ throw AssertionError (" Expected $date1 to be equal to $date2 up to 0.5 microseconds, " +
131
+ " but the difference was $difference microseconds" )
132
+ }
133
+ }
134
+
135
+ private fun assertEqualUpToOneNanosecond (date1 : NSDate , date2 : NSDate ) {
136
+ val difference = abs(date1.timeIntervalSinceDate(date2) * 1000000000 )
137
+ if (difference > 1 ) {
138
+ throw AssertionError (" Expected $date1 to be equal to $date2 up to 1 nanosecond, " +
139
+ " but the difference was $difference microseconds" )
140
+ }
141
+ }
110
142
}
0 commit comments