55
66package kotlinx.datetime
77
8+ import kotlin.math.absoluteValue
9+ import kotlin.time.Duration.Companion.seconds
810import kotlin.test.*
911
1012class InstantIsoStringsTest {
1113
1214 @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 ,
2538 )) {
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 )
2848 }
2949 }
3050
3151 @Test
3252 fun parseIsoString () {
3353 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 ),
3954 // 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 ),
4156 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 ),
4358 Triple (" 1970-01-01T00:00:01Z" , 1 , 0 ),
4459 Triple (" 1970-01-01T00:01:00Z" , 60 , 0 ),
4560 Triple (" 1970-01-01T00:01:01Z" , 61 , 0 ),
4661 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 ),
4863 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 ),
52189 // current time
53190 Triple (" 2024-07-15T16:06:29.461245691+02:00" , 1721052389 , 461245691 ),
54191 )) {
@@ -213,9 +350,12 @@ class InstantIsoStringsTest {
213350 val instants = listOf (
214351 Instant .DISTANT_FUTURE ,
215352 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+ )
217358
218- /*
219359 val offsets = listOf (
220360 0 to " Z" ,
221361 3 * 3600 + 12 * 60 + 14 to " +03:12:14" ,
@@ -228,15 +368,13 @@ class InstantIsoStringsTest {
228368
229369 for (instant in instants) {
230370 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 ))
236376 }
237377 }
238-
239- */
240378 }
241379
242380 private fun parseInstant (isoString : String ): Instant {
0 commit comments