Skip to content

Commit b5236ed

Browse files
committed
Update README to include parsing and formatting
1 parent 1ce9d14 commit b5236ed

File tree

2 files changed

+248
-11
lines changed

2 files changed

+248
-11
lines changed

README.md

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -172,34 +172,100 @@ val hourMinute = LocalTime(hour = 12, minute = 13)
172172
An `Instant` can be converted to a number of milliseconds since the Unix/POSIX epoch with the `toEpochMilliseconds()` function.
173173
To convert back, use the companion object function `Instant.fromEpochMilliseconds(Long)`.
174174

175-
### Converting instant and local date/time to and from string
175+
### Converting instant and local date/time to and from the ISO 8601 string
176176

177-
Currently, `Instant`, `LocalDateTime`, `LocalDate` and `LocalTime` only support ISO-8601 format.
177+
`Instant`, `LocalDateTime`, `LocalDate` and `LocalTime` provide shortcuts for
178+
parsing and formatting them using the extended ISO-8601 format.
178179
The `toString()` function is used to convert the value to a string in that format, and
179180
the `parse` function in companion object is used to parse a string representation back.
180181

181-
182182
```kotlin
183183
val instantNow = Clock.System.now()
184184
instantNow.toString() // returns something like 2015-12-31T12:30:00Z
185185
val instantBefore = Instant.parse("2010-06-01T22:19:44.475Z")
186186
```
187187

188-
Alternatively, the `String.to...()` extension functions can be used instead of `parse`,
189-
where it feels more convenient:
190-
191188
`LocalDateTime` uses a similar format, but without `Z` UTC time zone designator in the end.
192189

193190
`LocalDate` uses a format with just year, month, and date components, e.g. `2010-06-01`.
194191

195192
`LocalTime` uses a format with just hour, minute, second and (if non-zero) nanosecond components, e.g. `12:01:03`.
196193

197194
```kotlin
198-
"2010-06-01T22:19:44.475Z".toInstant()
199-
"2010-06-01T22:19:44".toLocalDateTime()
200-
"2010-06-01".toLocalDate()
201-
"12:01:03".toLocalTime()
202-
"12:0:03.999".toLocalTime()
195+
LocalDateTime.parse("2010-06-01T22:19:44")
196+
LocalDate.parse("2010-06-01")
197+
LocalTime.parse("12:01:03")
198+
LocalTime.parse("12:00:03.999")
199+
LocalTime.parse("12:0:03.999") // fails with an IllegalArgumentException
200+
```
201+
202+
### Working with other string formats
203+
204+
When some data needs to be formatted in some format other than ISO-8601, one
205+
can define their own format or use some of the predefined ones:
206+
207+
```kotlin
208+
// import kotlinx.datetime.format.*
209+
210+
val dateFormat = LocalDate.Format {
211+
monthNumber(padding = Padding.SPACE)
212+
char('/')
213+
dayOfMonth()
214+
char(' ')
215+
year()
216+
}
217+
218+
val date = dateFormat.parse("12/24 2023")
219+
println(date.format(LocalDate.Formats.ISO_BASIC)) // "20231224"
220+
```
221+
222+
### Parsing and formatting partial, compound or out-of-bounds data
223+
224+
Sometimes, the required string format doesn't fully correspond to any of the
225+
classes `kotlinx-datetime` provides. In these cases, `DateTimeComponents`, a
226+
collection of all date-time fields, can be used instead.
227+
228+
```kotlin
229+
// import kotlinx.datetime.format.*
230+
231+
val yearMonth = DateTimeComponents.Format { year(); char('-'); monthNumber() }
232+
.parse("2024-01")
233+
println(yearMonth.year)
234+
println(yearMonth.monthNumber)
235+
236+
val dateTimeOffset = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET
237+
.parse("2023-01-07T23:16:15.53+02:00")
238+
println(dateTimeOffset.toUtcOffset()) // +02:00
239+
println(dateTimeOffset.toLocalDateTime()) // 2023-01-07T23:16:15.53
240+
```
241+
242+
Occasionally, one can encounter strings where the values are slightly off:
243+
for example, `23:59:60`, where `60` is an invalid value for the second.
244+
`DateTimeComponents` allows parsing such values as well and then mutating them
245+
before conversion.
246+
247+
```kotlin
248+
val time = DateTimeComponents.Format { time(LocalTime.Formats.ISO) }
249+
.parse("23:59:60").apply {
250+
if (second == 60) second = 59
251+
}.toLocalTime()
252+
println(time) // 23:59:59
253+
```
254+
255+
Because `DateTimeComponents` is provided specifically for parsing and
256+
formatting, there is no way to construct it normally. If one needs to format
257+
partial, complex or out-of-bounds data, the `format` function allows building
258+
`DateTimeComponents` specifically for formatting it:
259+
260+
```kotlin
261+
DateTimeComponents.Formats.RFC_1123.format {
262+
// the receiver of this lambda is DateTimeComponents
263+
setDate(LocalDate(2023, 1, 7))
264+
hour = 23
265+
minute = 59
266+
second = 60
267+
setOffset(UtcOffset(hours = 2))
268+
} // Sat, 7 Jan 2023 23:59:60 +0200
203269
```
204270

205271
### Instant arithmetic

core/common/test/ReadmeTest.kt

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright 2019-2024 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.test
7+
8+
import kotlinx.datetime.*
9+
import kotlinx.datetime.format.*
10+
import kotlin.test.*
11+
import kotlin.time.*
12+
13+
/**
14+
* Tests the code snippets in the README.md file.
15+
*/
16+
@Suppress("UNUSED_VARIABLE")
17+
class ReadmeTest {
18+
@Test
19+
fun testGettingCurrentMoment() {
20+
val currentMoment = Clock.System.now()
21+
}
22+
23+
@Test
24+
fun testConvertingAnInstantToLocalDateAndTimeComponents() {
25+
val currentMoment: Instant = Clock.System.now()
26+
val datetimeInUtc: LocalDateTime = currentMoment.toLocalDateTime(TimeZone.UTC)
27+
val datetimeInSystemZone: LocalDateTime = currentMoment.toLocalDateTime(TimeZone.currentSystemDefault())
28+
29+
val tzBerlin = TimeZone.of("Europe/Berlin")
30+
val datetimeInBerlin = currentMoment.toLocalDateTime(tzBerlin)
31+
32+
val kotlinReleaseDateTime = LocalDateTime(2016, 2, 15, 16, 57, 0, 0)
33+
34+
val kotlinReleaseInstant = kotlinReleaseDateTime.toInstant(TimeZone.of("UTC+3"))
35+
}
36+
37+
@Test
38+
fun testGettingLocalDateComponents() {
39+
val now: Instant = Clock.System.now()
40+
val today: LocalDate = now.toLocalDateTime(TimeZone.currentSystemDefault()).date
41+
// or shorter
42+
val today2: LocalDate = Clock.System.todayIn(TimeZone.currentSystemDefault())
43+
44+
val knownDate = LocalDate(2020, 2, 21)
45+
}
46+
47+
@Test
48+
fun testGettingLocalTimeComponents() {
49+
val now: Instant = Clock.System.now()
50+
val thisTime: LocalTime = now.toLocalDateTime(TimeZone.currentSystemDefault()).time
51+
52+
val knownTime = LocalTime(hour = 23, minute = 59, second = 12)
53+
val timeWithNanos = LocalTime(hour = 23, minute = 59, second = 12, nanosecond = 999)
54+
val hourMinute = LocalTime(hour = 12, minute = 13)
55+
}
56+
57+
@Test
58+
fun testConvertingInstantToAndFromUnixTime() {
59+
Instant.fromEpochMilliseconds(Clock.System.now().toEpochMilliseconds())
60+
}
61+
62+
@Test
63+
fun testConvertingInstantAndLocalDateTimeToAndFromIso8601String() {
64+
val instantNow = Clock.System.now()
65+
instantNow.toString() // returns something like 2015-12-31T12:30:00Z
66+
val instantBefore = Instant.parse("2010-06-01T22:19:44.475Z")
67+
68+
LocalDateTime.parse("2010-06-01T22:19:44")
69+
LocalDate.parse("2010-06-01")
70+
LocalTime.parse("12:01:03")
71+
LocalTime.parse("12:00:03.999")
72+
assertFailsWith<IllegalArgumentException> { LocalTime.parse("12:0:03.999") }
73+
}
74+
75+
@Test
76+
fun testWorkingWithOtherStringFormats() {
77+
val dateFormat = LocalDate.Format {
78+
monthNumber(padding = Padding.SPACE)
79+
char('/')
80+
dayOfMonth()
81+
char(' ')
82+
year()
83+
}
84+
85+
val date = dateFormat.parse("12/24 2023")
86+
assertEquals("20231224", date.format(LocalDate.Formats.ISO_BASIC))
87+
}
88+
89+
@Test
90+
fun testParsingAndFormattingPartialCompoundOrOutOfBoundsData() {
91+
val yearMonth = DateTimeComponents.Format { year(); char('-'); monthNumber() }
92+
.parse("2024-01")
93+
assertEquals(2024, yearMonth.year)
94+
assertEquals(1, yearMonth.monthNumber)
95+
96+
val dateTimeOffset = DateTimeComponents.Formats.ISO_DATE_TIME_OFFSET
97+
.parse("2023-01-07T23:16:15.53+02:00")
98+
assertEquals("+02:00", dateTimeOffset.toUtcOffset().toString())
99+
assertEquals("2023-01-07T23:16:15.530", dateTimeOffset.toLocalDateTime().toString())
100+
101+
val time = DateTimeComponents.Format { time(LocalTime.Formats.ISO) }
102+
.parse("23:59:60").apply {
103+
if (second == 60) second = 59
104+
}.toLocalTime()
105+
assertEquals(LocalTime(23, 59, 59), time)
106+
107+
assertEquals("Sat, 7 Jan 2023 23:59:60 +0200", DateTimeComponents.Formats.RFC_1123.format {
108+
// the receiver of this lambda is DateTimeComponents
109+
setDate(LocalDate(2023, 1, 7))
110+
hour = 23
111+
minute = 59
112+
second = 60
113+
setOffset(UtcOffset(hours = 2))
114+
})
115+
}
116+
117+
@Test
118+
fun testInstantArithmetic() {
119+
run {
120+
val now = Clock.System.now()
121+
val instantInThePast: Instant = Instant.parse("2020-01-01T00:00:00Z")
122+
val durationSinceThen: Duration = now - instantInThePast
123+
val equidistantInstantInTheFuture: Instant = now + durationSinceThen
124+
125+
val period: DateTimePeriod = instantInThePast.periodUntil(Clock.System.now(), TimeZone.UTC)
126+
127+
instantInThePast.yearsUntil(Clock.System.now(), TimeZone.UTC)
128+
instantInThePast.monthsUntil(Clock.System.now(), TimeZone.UTC)
129+
instantInThePast.daysUntil(Clock.System.now(), TimeZone.UTC)
130+
131+
val diffInMonths = instantInThePast.until(Clock.System.now(), DateTimeUnit.MONTH, TimeZone.UTC)
132+
}
133+
134+
run {
135+
val now = Clock.System.now()
136+
val systemTZ = TimeZone.currentSystemDefault()
137+
val tomorrow = now.plus(2, DateTimeUnit.DAY, systemTZ)
138+
val threeYearsAndAMonthLater = now.plus(DateTimePeriod(years = 3, months = 1), systemTZ)
139+
}
140+
}
141+
142+
@Test
143+
fun testDateArithmetic() {
144+
val date = LocalDate(2023, 1, 7)
145+
val date2 = date.plus(1, DateTimeUnit.DAY)
146+
date.plus(DatePeriod(days = 1))
147+
date.until(date2, DateTimeUnit.DAY)
148+
date.yearsUntil(date2)
149+
date.monthsUntil(date2)
150+
date.daysUntil(date2)
151+
date.periodUntil(date2)
152+
date2 - date
153+
}
154+
155+
@Test
156+
fun testDateTimeArithmetic() {
157+
val timeZone = TimeZone.of("Europe/Berlin")
158+
val localDateTime = LocalDateTime.parse("2021-03-27T02:16:20")
159+
val instant = localDateTime.toInstant(timeZone)
160+
161+
val instantOneDayLater = instant.plus(1, DateTimeUnit.DAY, timeZone)
162+
val localDateTimeOneDayLater = instantOneDayLater.toLocalDateTime(timeZone)
163+
assertEquals(LocalDateTime(2021, 3, 28, 3, 16, 20), localDateTimeOneDayLater)
164+
// 2021-03-28T03:16:20, as 02:16:20 that day is in a time gap
165+
166+
val instantTwoDaysLater = instant.plus(2, DateTimeUnit.DAY, timeZone)
167+
val localDateTimeTwoDaysLater = instantTwoDaysLater.toLocalDateTime(timeZone)
168+
assertEquals(LocalDateTime(2021, 3, 29, 2, 16, 20), localDateTimeTwoDaysLater)
169+
// 2021-03-29T02:16:20
170+
}
171+
}

0 commit comments

Comments
 (0)