5
5
6
6
package kotlinx.datetime
7
7
8
+ import kotlin.math.absoluteValue
9
+ import kotlin.time.Duration.Companion.seconds
8
10
import kotlin.test.*
9
11
10
12
class InstantIsoStringsTest {
11
13
12
14
@Test
13
- fun parseAndFormatCanonicalStrings () {
14
- for (canonicalString in arrayOf(
15
- " 1970-01-01T00:00:00Z" ,
16
- " 1970-01-01T00:00:00.100Z" ,
17
- " 1970-01-01T00:00:00.010Z" ,
18
- " 1970-01-01T00:00:00.001Z" ,
19
- " 1970-01-01T00:00:00.000100Z" ,
20
- " 1970-01-01T00:00:00.000010Z" ,
21
- " 1970-01-01T00:00:00.000001Z" ,
22
- " 1970-01-01T00:00:00.000000100Z" ,
23
- " 1970-01-01T00:00:00.000000010Z" ,
24
- " 1970-01-01T00:00:00.000000001Z" ,
15
+ fun parseDates () {
16
+ fun Int.zeroPadded (digits : Int ): String = when {
17
+ this >= 0 -> " ${toString().padStart(digits, ' 0' )} "
18
+ else -> " -${absoluteValue.toString().padStart(digits, ' 0' )} "
19
+ }
20
+ fun localDateToString (year : Int , month : Int , day : Int ) =
21
+ " ${year.zeroPadded(4 )} -${month.zeroPadded(2 )} -${day.zeroPadded(2 )} "
22
+ // only works for 1-4-digit years
23
+ fun assertMonthBoundariesAreCorrect (year : Int , month : Int , lastDayOfMonth : Int ) {
24
+ val validString = " ${localDateToString(year, month, lastDayOfMonth)} T23:59:59Z"
25
+ val invalidString = " ${localDateToString(year, month, lastDayOfMonth + 1 )} T23:59:59Z"
26
+ parseInstant(validString) // shouldn't throw
27
+ assertInvalidFormat(invalidString) { parseInstant(invalidString) }
28
+ }
29
+ val nonLeapYears = listOf (
30
+ 1970 , 1971 , 1973 , 1974 , 1975 , 2021 , 2022 , 2023 , 2100 , 1100 , 1 , 2 , 3 , 5 , - 1 , - 2 , - 1971 , 100 , - 100
31
+ )
32
+ val leapYears = listOf (
33
+ 0 , 1972 , 1976 , 1980 , 2000 , 1200 , 400 , - 400 , - 4 , - 8 ,
34
+ )
35
+ for ((month, lastDayOfMonth) in arrayOf(
36
+ 1 to 31 , 3 to 31 , 4 to 30 , 5 to 31 , 6 to 30 ,
37
+ 7 to 31 , 8 to 31 , 9 to 30 , 10 to 31 , 11 to 30 , 12 to 31 ,
25
38
)) {
26
- val instant = parseInstant(canonicalString)
27
- assertEquals(canonicalString, instant.toString())
39
+ for (year in nonLeapYears + leapYears) {
40
+ assertMonthBoundariesAreCorrect(year, month, lastDayOfMonth)
41
+ }
42
+ }
43
+ for (leapYear in leapYears) {
44
+ assertMonthBoundariesAreCorrect(leapYear, 2 , 29 )
45
+ }
46
+ for (nonLeapYear in nonLeapYears) {
47
+ assertMonthBoundariesAreCorrect(nonLeapYear, 2 , 28 )
28
48
}
29
49
}
30
50
31
51
@Test
32
52
fun parseIsoString () {
33
53
for ((str, seconds, nanos) in arrayOf(
34
- // both upper and lower case is supported
35
- Triple (" 1970-01-01T00:00:00Z" , 0 , 0 ),
36
- Triple (" 1970-01-01t00:00:00Z" , 0 , 0 ),
37
- Triple (" 1970-01-01T00:00:00z" , 0 , 0 ),
38
- Triple (" 1970-01-01T00:00:00.0Z" , 0 , 0 ),
39
54
// all components are taken into accout
40
- Triple (" 1970-01-01T00:00:00.000000000Z " , 0 , 0 ),
55
+ Triple (" 1970-01-01T00:00:00Z " , 0 , 0 ),
41
56
Triple (" 1970-01-01T00:00:00.000000001Z" , 0 , 1 ),
42
- Triple (" 1970-01-01T00:00:00.100000000Z " , 0 , 100000000 ),
57
+ Triple (" 1970-01-01T00:00:00.100Z " , 0 , 100000000 ),
43
58
Triple (" 1970-01-01T00:00:01Z" , 1 , 0 ),
44
59
Triple (" 1970-01-01T00:01:00Z" , 60 , 0 ),
45
60
Triple (" 1970-01-01T00:01:01Z" , 61 , 0 ),
46
61
Triple (" 1970-01-01T00:01:01.000000001Z" , 61 , 1 ),
47
- Triple (" 1970-01-01T01:00:00.000000000Z " , 3600 , 0 ),
62
+ Triple (" 1970-01-01T01:00:00Z " , 3600 , 0 ),
48
63
Triple (" 1970-01-01T01:01:01.000000001Z" , 3661 , 1 ),
49
- Triple (" 1970-01-02T01:01:01.100000000Z" , 90061 , 100000000 ),
50
- Triple (" 1970-02-02T01:01:01.100000000Z" , 31 * 86400 + 90061 , 100000000 ),
51
- Triple (" 1971-02-02T01:01:01.100000000Z" , (365 + 31 ) * 86400 + 90061 , 100000000 ),
64
+ Triple (" 1970-01-02T01:01:01.100Z" , 90061 , 100000000 ),
65
+ Triple (" 1970-02-02T01:01:01.100Z" , 31 * 86400 + 90061 , 100000000 ),
66
+ Triple (" 1971-02-02T01:01:01.100Z" , (365 + 31 ) * 86400 + 90061 , 100000000 ),
67
+ // how many digits get output for various precision of the sub-second portion
68
+ Triple (" 1970-01-01T00:00:00.100Z" , 0 , 100_000_000 ),
69
+ Triple (" 1970-01-01T00:00:00.010Z" , 0 , 10_000_000 ),
70
+ Triple (" 1970-01-01T00:00:00.001Z" , 0 , 1_000_000 ),
71
+ Triple (" 1970-01-01T00:00:00.000100Z" , 0 , 100_000 ),
72
+ Triple (" 1970-01-01T00:00:00.000010Z" , 0 , 10_000 ),
73
+ Triple (" 1970-01-01T00:00:00.000001Z" , 0 , 1_000 ),
74
+ Triple (" 1970-01-01T00:00:00.000000100Z" , 0 , 100 ),
75
+ Triple (" 1970-01-01T00:00:00.000000010Z" , 0 , 10 ),
76
+ Triple (" 1970-01-01T00:00:00.000000001Z" , 0 , 1 ),
77
+ // random data queried from java.time
78
+ Triple (" +51861-09-21T11:07:43.782719883Z" , 1574430692863 , 782719883 ),
79
+ Triple (" +395069-04-30T01:28:37.454777349Z" , 12405016603717 , 454777349 ),
80
+ Triple (" -551259-03-05T08:01:36.195722269Z" , - 17458215523104 , 195722269 ),
81
+ Triple (" +498403-02-11T17:47:05.156642423Z" , 15665915958425 , 156642423 ),
82
+ Triple (" +283686-10-14T23:00:25.666521845Z" , 8890123158025 , 666521845 ),
83
+ Triple (" -910329-04-04T09:27:54.456784744Z" , - 28789367639526 , 456784744 ),
84
+ Triple (" -37222-03-21T18:04:37.006055123Z" , - 1236773166923 , 6055123 ),
85
+ Triple (" -189377-03-30T01:37:14.288808090Z" , - 6038320515766 , 288808090 ),
86
+ Triple (" -67394-03-24T03:19:41.794404047Z" , - 2188909341619 , 794404047 ),
87
+ Triple (" -870649-05-27T13:47:39.925150102Z" , - 27537183223941 , 925150102 ),
88
+ Triple (" +94020-04-10T14:51:21.569206089Z" , 2904826114281 , 569206089 ),
89
+ Triple (" -945485-07-11T23:28:58.240153828Z" , - 29898775384262 , 240153828 ),
90
+ Triple (" -73722-02-22T11:19:54.364548772Z" , - 2388604250406 , 364548772 ),
91
+ Triple (" -645899-05-17T16:44:21.522135477Z" , - 20444759104539 , 522135477 ),
92
+ Triple (" -702594-10-20T10:13:53.212104714Z" , - 22233867083167 , 212104714 ),
93
+ Triple (" -442579-11-22T01:35:44.591216727Z" , - 14028583357456 , 591216727 ),
94
+ Triple (" -849915-06-25T01:28:27.625015449Z" , - 26882878833093 , 625015449 ),
95
+ Triple (" -481897-08-13T05:44:47.077814711Z" , - 15269348340913 , 77814711 ),
96
+ Triple (" +295919-02-07T15:47:37.850981753Z" , 9276137682457 , 850981753 ),
97
+ Triple (" +967334-01-15T15:08:10.235167075Z" , 30463946694490 , 235167075 ),
98
+ Triple (" +774237-04-30T16:00:32.810606451Z" , 24370403011232 , 810606451 ),
99
+ Triple (" +792959-05-03T08:18:31.616194572Z" , 24961212490711 , 616194572 ),
100
+ Triple (" -261823-02-16T03:17:35.085815500Z" , - 8324498983345 , 85815500 ),
101
+ Triple (" +931062-03-22T17:04:54.135075640Z" , 29319318637494 , 135075640 ),
102
+ Triple (" +623320-01-26T03:08:05.121769356Z" , 19607914264085 , 121769356 ),
103
+ Triple (" +322804-03-06T11:31:24.788006817Z" , 10124548774284 , 788006817 ),
104
+ Triple (" -784322-04-03T21:25:19.666588404Z" , - 24812970806081 , 666588404 ),
105
+ Triple (" +403293-01-07T05:59:41.601460200Z" , 12664531288781 , 601460200 ),
106
+ Triple (" -835821-06-01T00:52:15.782852248Z" , - 26438117296065 , 782852248 ),
107
+ Triple (" +222483-07-15T08:29:55.019931345Z" , 6958735086595 , 19931345 ),
108
+ Triple (" -663595-09-05T04:36:24.110433196Z" , - 21003181356216 , 110433196 ),
109
+ Triple (" +166626-02-15T22:16:34.070665743Z" , 5196045449794 , 70665743 ),
110
+ Triple (" -517158-01-02T22:52:24.155574933Z" , - 16382097162456 , 155574933 ),
111
+ Triple (" +850155-01-02T10:25:31.349473798Z" , 26766133467931 , 349473798 ),
112
+ Triple (" -967697-04-25T20:43:33.328060156Z" , - 30599725115787 , 328060156 ),
113
+ Triple (" +437131-04-26T07:32:58.134219875Z" , 13732364705578 , 134219875 ),
114
+ Triple (" +372920-11-25T13:38:22.852562723Z" , 11706079786702 , 852562723 ),
115
+ Triple (" +169255-09-07T11:28:18.481625778Z" , 5279026303698 , 481625778 ),
116
+ Triple (" -980786-08-18T17:05:22.581779094Z" , - 31012764044078 , 581779094 ),
117
+ Triple (" +182945-05-25T20:39:24.545585221Z" , 5711031952764 , 545585221 ),
118
+ Triple (" +300811-12-15T02:53:38.676752671Z" , 9430541175218 , 676752671 ),
119
+ Triple (" -807816-01-18T18:04:26.291749218Z" , - 25554376389334 , 291749218 ),
120
+ Triple (" -53033-12-30T22:02:01.398533618Z" , - 1735695568679 , 398533618 ),
121
+ Triple (" -354903-06-14T10:08:46.111648055Z" , - 11261809864274 , 111648055 ),
122
+ Triple (" +842009-03-11T23:58:06.537554993Z" , 26509076495886 , 537554993 ),
123
+ Triple (" -391976-11-09T04:16:17.862484469Z" , - 12431707962223 , 862484469 ),
124
+ Triple (" -733019-10-28T17:07:13.450343935Z" , - 23193986539967 , 450343935 ),
125
+ Triple (" +595280-03-05T23:36:27.765851400Z" , 18723060833787 , 765851400 ),
126
+ Triple (" -930296-07-17T03:33:33.094509320Z" , - 29419456335987 , 94509320 ),
127
+ Triple (" +609508-02-29T10:58:02.703241053Z" , 19172052557882 , 703241053 ),
128
+ Triple (" +996233-06-25T06:01:55.647461964Z" , 31375924927315 , 647461964 ),
129
+ Triple (" -93200-12-06T21:29:56.140938343Z" , - 3003245692204 , 140938343 ),
130
+ Triple (" +794143-07-02T09:49:35.585085194Z" , 24998581100975 , 585085194 ),
131
+ Triple (" -783550-12-31T17:10:16.577723428Z" , - 24788585371784 , 577723428 ),
132
+ Triple (" -240168-11-03T17:22:09.108424624Z" , - 7641110702271 , 108424624 ),
133
+ Triple (" +613419-02-15T12:00:07.012460989Z" , 19295470641607 , 12460989 ),
134
+ Triple (" -521405-03-25T02:03:46.552711998Z" , - 16516112536574 , 552711998 ),
135
+ Triple (" -938829-01-22T16:48:43.582709371Z" , - 29688747030677 , 582709371 ),
136
+ Triple (" +916785-05-16T21:54:45.983221956Z" , 28868784818085 , 983221956 ),
137
+ Triple (" +482425-06-09T04:24:32.683186155Z" , 15161709183872 , 683186155 ),
138
+ Triple (" +622585-08-20T05:45:52.555088343Z" , 19584737819152 , 555088343 ),
139
+ Triple (" -451048-11-02T01:49:29.076392891Z" , - 14295840847831 , 76392891 ),
140
+ Triple (" +721083-09-17T00:31:34.648020241Z" , 22693036811494 , 648020241 ),
141
+ Triple (" +235979-10-28T12:07:33.706273641Z" , 7384636728453 , 706273641 ),
142
+ Triple (" +285234-04-12T18:30:25.215363003Z" , 8938957285825 , 215363003 ),
143
+ Triple (" -917176-03-10T10:03:25.943265324Z" , - 29005440213395 , 943265324 ),
144
+ Triple (" -381932-09-05T02:47:17.004960541Z" , - 12114755529163 , 4960541 ),
145
+ Triple (" -52158-11-11T09:38:45.489915403Z" , - 1708087530075 , 489915403 ),
146
+ Triple (" -584290-11-15T20:15:24.377620606Z" , - 18500551127076 , 377620606 ),
147
+ Triple (" -645616-05-05T17:36:59.941608628Z" , - 20435829488581 , 941608628 ),
148
+ Triple (" +794405-06-22T21:08:20.853641989Z" , 25006848239300 , 853641989 ),
149
+ Triple (" +986590-08-01T05:15:25.827177433Z" , 31071624470125 , 827177433 ),
150
+ Triple (" +527158-02-06T12:34:35.088546391Z" , 16573335654875 , 88546391 ),
151
+ Triple (" -513116-05-01T07:28:44.448204123Z" , - 16254533665876 , 448204123 ),
152
+ Triple (" +397065-10-19T21:59:05.831855226Z" , 12468019211945 , 831855226 ),
153
+ Triple (" +312769-04-26T11:33:07.802217284Z" , 9807879123187 , 802217284 ),
154
+ Triple (" +682473-04-14T01:00:38.067076018Z" , 21474609498038 , 67076018 ),
155
+ Triple (" +731560-02-15T02:15:06.599802467Z" , 23023640456106 , 599802467 ),
156
+ Triple (" -877354-10-27T22:55:02.723751549Z" , - 27748759338298 , 723751549 ),
157
+ Triple (" -746193-01-02T07:19:56.258497483Z" , - 23609743807204 , 258497483 ),
158
+ Triple (" -822112-07-28T08:55:19.319285417Z" , - 26005498038281 , 319285417 ),
159
+ Triple (" -400365-04-30T00:05:51.210582736Z" , - 12696455980449 , 210582736 ),
160
+ Triple (" +436254-07-11T18:08:06.937065549Z" , 13704695921286 , 937065549 ),
161
+ Triple (" -340854-01-07T03:17:32.367173472Z" , - 10818479997748 , 367173472 ),
162
+ Triple (" -985221-04-25T22:57:01.511559459Z" , - 31152729085379 , 511559459 ),
163
+ Triple (" +859861-09-01T02:21:20.289341591Z" , 27072446149280 , 289341591 ),
164
+ Triple (" -0131-07-16T10:47:54.756333457Z" , - 66284140326 , 756333457 ),
165
+ Triple (" -327041-11-18T22:55:21.885337272Z" , - 10382556503079 , 885337272 ),
166
+ Triple (" -268616-05-06T10:27:54.420166505Z" , - 8538858480726 , 420166505 ),
167
+ Triple (" -228012-05-16T15:26:54.680432991Z" , - 7257519160386 , 680432991 ),
168
+ Triple (" +857168-09-12T13:29:36.945689251Z" , 26987464272576 , 945689251 ),
169
+ Triple (" -974181-04-12T08:47:35.627678735Z" , - 30804341526745 , 627678735 ),
170
+ Triple (" -435700-10-20T22:33:13.897477229Z" , - 13811505874007 , 897477229 ),
171
+ Triple (" -507467-01-19T23:06:05.156792267Z" , - 16076277276835 , 156792267 ),
172
+ Triple (" -382257-11-19T08:00:10.407963305Z" , - 12125005142390 , 407963305 ),
173
+ Triple (" +83082-01-04T20:18:56.409867424Z" , 2559647852336 , 409867424 ),
174
+ Triple (" -916839-09-12T22:45:39.091941363Z" , - 28994789466861 , 91941363 ),
175
+ Triple (" -147771-05-07T08:31:34.950238979Z" , - 4725358615706 , 950238979 ),
176
+ )) {
177
+ val instant = parseInstant(str)
178
+ assertEquals(
179
+ seconds.toLong() * 1000 + nanos / 1000000 , instant.toEpochMilliseconds(),
180
+ " Parsed $instant from $str , with Unix time = `$seconds + 10^-9 * $nanos `"
181
+ )
182
+ assertEquals(str, formatIso(instant))
183
+ }
184
+ // non-canonical strings are parsed as well, but formatted differently
185
+ for ((str, seconds, nanos) in arrayOf(
186
+ // upper, lower case, trailing zeros
187
+ Triple (" 2024-07-15T14:06:29.461245000z" , 1721052389 , 461245000 ),
188
+ Triple (" 2024-07-15t14:06:29.4612450z" , 1721052389 , 461245000 ),
52
189
// current time
53
190
Triple (" 2024-07-15T16:06:29.461245691+02:00" , 1721052389 , 461245691 ),
54
191
)) {
@@ -213,9 +350,12 @@ class InstantIsoStringsTest {
213
350
val instants = listOf (
214
351
Instant .DISTANT_FUTURE ,
215
352
Instant .DISTANT_PAST ,
216
- Instant .fromEpochSeconds(0 , 0 ))
353
+ Instant .fromEpochSeconds(0 , 0 ),
354
+ Instant .parse(" 2020-01-02T03:04:05.6789Z" ),
355
+ Instant .MAX ,
356
+ Instant .MIN ,
357
+ )
217
358
218
- /*
219
359
val offsets = listOf (
220
360
0 to " Z" ,
221
361
3 * 3600 + 12 * 60 + 14 to " +03:12:14" ,
@@ -228,15 +368,13 @@ class InstantIsoStringsTest {
228
368
229
369
for (instant in instants) {
230
370
for ((offsetSeconds, offsetString) in offsets) {
231
- val str = instant.format(DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET, offsets[offsetIx])
232
- val offsetString = offsets[offsetIx].toString()
233
- assertEquals(offsetString, offsetString.commonSuffixWith(str))
234
- assertEquals(instant, Instant.parse(str, DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET) )
235
- assertEquals(instant, Instant.parse(str ))
371
+ if (instant == Instant . MAX && offsetSeconds < 0 ||
372
+ instant == Instant . MIN && offsetSeconds > 0
373
+ ) continue
374
+ val newInstant = Instant .parse(" ${instant.toString().dropLast( 1 )}$offsetString " )
375
+ assertEquals(newInstant, instant.minus(offsetSeconds.seconds ))
236
376
}
237
377
}
238
-
239
- */
240
378
}
241
379
242
380
private fun parseInstant (isoString : String ): Instant {
0 commit comments