0
# Formatting and Parsing System
1
2
Comprehensive formatting and parsing system with DSL builders for creating custom date/time formats. The library supports ISO 8601 formats out of the box and provides flexible builders for custom formatting needs.
3
4
## Capabilities
5
6
### DateTimeFormat<T>
7
8
Core formatting interface that handles both parsing and formatting of date/time values.
9
10
```kotlin { .api }
11
/**
12
* Format for parsing and formatting date/time values
13
* Sealed interface providing type-safe formatting operations
14
*/
15
sealed interface DateTimeFormat<T> {
16
/**
17
* Format a value to string
18
* @param value The value to format
19
* @returns Formatted string representation
20
*/
21
fun format(value: T): String
22
23
/**
24
* Format a value to an appendable
25
* @param appendable Target to append the formatted value to
26
* @param value The value to format
27
* @returns The appendable for chaining
28
*/
29
fun formatTo(appendable: Appendable, value: T): Appendable
30
31
/**
32
* Parse a string to the target type
33
* @param input String to parse
34
* @returns Parsed value
35
* @throws DateTimeParseException if parsing fails
36
*/
37
fun parse(input: CharSequence): T
38
39
/**
40
* Parse a string to the target type, returning null on failure
41
* @param input String to parse
42
* @returns Parsed value or null if parsing fails
43
*/
44
fun parseOrNull(input: CharSequence): T?
45
46
companion object {
47
/**
48
* Generate Kotlin DSL code for recreating a format
49
* @param format The format to analyze
50
* @returns Kotlin code string
51
*/
52
fun formatAsKotlinBuilderDsl(format: DateTimeFormat<*>): String
53
}
54
}
55
```
56
57
**Usage Examples:**
58
59
```kotlin
60
import kotlinx.datetime.*
61
62
// Using predefined formats
63
val date = LocalDate(2023, 12, 25)
64
val formatted = date.format(LocalDate.Formats.ISO) // "2023-12-25"
65
66
// Parse from string
67
val parsed = LocalDate.parse("2023-12-25", LocalDate.Formats.ISO)
68
69
// Safe parsing
70
val maybeParsed = LocalDate.Formats.ISO.parseOrNull("invalid-date") // null
71
72
// Format to StringBuilder
73
val buffer = StringBuilder()
74
LocalDate.Formats.ISO.formatTo(buffer, date)
75
println(buffer.toString()) // "2023-12-25"
76
```
77
78
### Predefined Formats
79
80
Each date/time type provides predefined format constants for common use cases.
81
82
#### LocalDate Formats
83
84
```kotlin { .api }
85
object LocalDate.Formats {
86
/** ISO 8601 extended format: YYYY-MM-DD */
87
val ISO: DateTimeFormat<LocalDate>
88
89
/** ISO 8601 basic format: YYYYMMDD */
90
val ISO_BASIC: DateTimeFormat<LocalDate>
91
}
92
```
93
94
#### LocalTime Formats
95
96
```kotlin { .api }
97
object LocalTime.Formats {
98
/** ISO 8601 extended format: HH:MM:SS[.fff] */
99
val ISO: DateTimeFormat<LocalTime>
100
}
101
```
102
103
#### LocalDateTime Formats
104
105
```kotlin { .api }
106
object LocalDateTime.Formats {
107
/** ISO 8601 extended format: YYYY-MM-DDTHH:MM:SS[.fff] */
108
val ISO: DateTimeFormat<LocalDateTime>
109
}
110
```
111
112
#### YearMonth Formats
113
114
```kotlin { .api }
115
object YearMonth.Formats {
116
/** ISO 8601 extended format: YYYY-MM */
117
val ISO: DateTimeFormat<YearMonth>
118
}
119
```
120
121
#### UtcOffset Formats
122
123
```kotlin { .api }
124
object UtcOffset.Formats {
125
/** ISO 8601 extended format: +HH:MM, +HH:MM:SS, or Z */
126
val ISO: DateTimeFormat<UtcOffset>
127
128
/** ISO 8601 basic format: +HHMM, +HHMMSS, or Z */
129
val ISO_BASIC: DateTimeFormat<UtcOffset>
130
131
/** Four digits format: always ±HHMM, never Z */
132
val FOUR_DIGITS: DateTimeFormat<UtcOffset>
133
}
134
```
135
136
**Usage Examples:**
137
138
```kotlin
139
import kotlinx.datetime.*
140
141
val date = LocalDate(2023, 12, 25)
142
val time = LocalTime(15, 30, 45, 123456789)
143
val dateTime = LocalDateTime(date, time)
144
val yearMonth = YearMonth(2023, 12)
145
val offset = UtcOffset(hours = 5, minutes = 30)
146
147
// Format using predefined formats
148
println(date.format(LocalDate.Formats.ISO)) // "2023-12-25"
149
println(date.format(LocalDate.Formats.ISO_BASIC)) // "20231225"
150
println(time.format(LocalTime.Formats.ISO)) // "15:30:45.123456789"
151
println(dateTime.format(LocalDateTime.Formats.ISO)) // "2023-12-25T15:30:45.123456789"
152
println(yearMonth.format(YearMonth.Formats.ISO)) // "2023-12"
153
println(offset.format(UtcOffset.Formats.ISO)) // "+05:30"
154
println(offset.format(UtcOffset.Formats.ISO_BASIC)) // "+0530"
155
println(offset.format(UtcOffset.Formats.FOUR_DIGITS)) // "+0530"
156
157
// Parse using predefined formats
158
val parsedDate = LocalDate.parse("2023-12-25", LocalDate.Formats.ISO)
159
val parsedTime = LocalTime.parse("15:30:45", LocalTime.Formats.ISO)
160
val parsedOffset = UtcOffset.parse("+05:30", UtcOffset.Formats.ISO)
161
```
162
163
### DateTimeFormatBuilder DSL
164
165
Flexible DSL for creating custom date/time formats with fine-grained control over each component.
166
167
#### Base Builder Interface
168
169
```kotlin { .api }
170
/**
171
* Base interface for all format builders
172
* Provides common formatting operations
173
*/
174
interface DateTimeFormatBuilder {
175
/**
176
* Add literal text to the format
177
* @param value Literal string to include
178
*/
179
fun chars(value: String)
180
181
/**
182
* Add a single literal character
183
* @param value Character to include
184
*/
185
fun char(value: Char)
186
}
187
```
188
189
#### Date Component Builders
190
191
```kotlin { .api }
192
/**
193
* Builder for formats that include date components
194
*/
195
interface DateTimeFormatBuilder.WithDate : DateTimeFormatBuilder {
196
/**
197
* Year component
198
* @param padding Padding style for the year
199
*/
200
fun year(padding: Padding = Padding.ZERO)
201
202
/**
203
* Month as number
204
* @param padding Padding style (NONE, ZERO, SPACE)
205
*/
206
fun monthNumber(padding: Padding = Padding.ZERO)
207
208
/**
209
* Day of month
210
* @param padding Padding style
211
*/
212
fun dayOfMonth(padding: Padding = Padding.ZERO)
213
214
/**
215
* Day of year (1-366)
216
* @param padding Padding style
217
*/
218
fun dayOfYear(padding: Padding = Padding.ZERO)
219
220
/**
221
* Day of week as number (1=Monday, 7=Sunday)
222
* @param padding Padding style
223
*/
224
fun dayOfWeek(padding: Padding = Padding.ZERO)
225
}
226
```
227
228
#### Time Component Builders
229
230
```kotlin { .api }
231
/**
232
* Builder for formats that include time components
233
*/
234
interface DateTimeFormatBuilder.WithTime : DateTimeFormatBuilder {
235
/**
236
* Hour component (0-23)
237
* @param padding Padding style
238
*/
239
fun hour(padding: Padding = Padding.ZERO)
240
241
/**
242
* Minute component (0-59)
243
* @param padding Padding style
244
*/
245
fun minute(padding: Padding = Padding.ZERO)
246
247
/**
248
* Second component (0-59)
249
* @param padding Padding style
250
*/
251
fun second(padding: Padding = Padding.ZERO)
252
253
/**
254
* Fractional second component
255
* @param minLength Minimum number of digits
256
* @param maxLength Maximum number of digits
257
*/
258
fun secondFraction(minLength: Int = 0, maxLength: Int = 9)
259
260
/**
261
* AM/PM marker
262
* @param am String for AM (default "AM")
263
* @param pm String for PM (default "PM")
264
*/
265
fun amPmMarker(am: String = "AM", pm: String = "PM")
266
}
267
```
268
269
#### Combined Builders
270
271
```kotlin { .api }
272
/**
273
* Builder for formats with both date and time components
274
*/
275
interface DateTimeFormatBuilder.WithDateTime :
276
DateTimeFormatBuilder.WithDate,
277
DateTimeFormatBuilder.WithTime
278
279
/**
280
* Builder for formats with UTC offset
281
*/
282
interface DateTimeFormatBuilder.WithUtcOffset : DateTimeFormatBuilder {
283
/**
284
* UTC offset component
285
* @param format Offset format style
286
*/
287
fun offset(format: UtcOffsetFormat)
288
}
289
290
/**
291
* Builder for YearMonth formats
292
*/
293
interface DateTimeFormatBuilder.WithYearMonth : DateTimeFormatBuilder {
294
fun year(padding: Padding = Padding.ZERO)
295
fun monthNumber(padding: Padding = Padding.ZERO)
296
}
297
```
298
299
#### Padding Enumeration
300
301
```kotlin { .api }
302
/**
303
* Padding style for formatting numeric components
304
*/
305
enum class Padding {
306
/** No padding */
307
NONE,
308
309
/** Pad with zeros */
310
ZERO,
311
312
/** Pad with spaces */
313
SPACE
314
}
315
```
316
317
### Format Builder Functions
318
319
Factory functions for creating custom formats using the DSL.
320
321
```kotlin { .api }
322
/**
323
* Create a LocalDate format using the DSL
324
* @param builder DSL builder function
325
* @returns DateTimeFormat for LocalDate
326
*/
327
fun LocalDate.Format(
328
builder: DateTimeFormatBuilder.WithDate.() -> Unit
329
): DateTimeFormat<LocalDate>
330
331
/**
332
* Create a LocalTime format using the DSL
333
* @param builder DSL builder function
334
* @returns DateTimeFormat for LocalTime
335
*/
336
fun LocalTime.Format(
337
builder: DateTimeFormatBuilder.WithTime.() -> Unit
338
): DateTimeFormat<LocalTime>
339
340
/**
341
* Create a LocalDateTime format using the DSL
342
* @param builder DSL builder function
343
* @returns DateTimeFormat for LocalDateTime
344
*/
345
fun LocalDateTime.Format(
346
builder: DateTimeFormatBuilder.WithDateTime.() -> Unit
347
): DateTimeFormat<LocalDateTime>
348
349
/**
350
* Create a YearMonth format using the DSL
351
* @param builder DSL builder function
352
* @returns DateTimeFormat for YearMonth
353
*/
354
fun YearMonth.Format(
355
builder: DateTimeFormatBuilder.WithYearMonth.() -> Unit
356
): DateTimeFormat<YearMonth>
357
358
/**
359
* Create a UtcOffset format using the DSL
360
* @param builder DSL builder function
361
* @returns DateTimeFormat for UtcOffset
362
*/
363
fun UtcOffset.Format(
364
builder: DateTimeFormatBuilder.WithUtcOffset.() -> Unit
365
): DateTimeFormat<UtcOffset>
366
367
/**
368
* Create a DateTimeComponents format using the DSL
369
* @param builder DSL builder function
370
* @returns DateTimeFormat for DateTimeComponents
371
*/
372
fun DateTimeComponents.Format(
373
builder: DateTimeFormatBuilder.WithDateTime.() -> Unit
374
): DateTimeFormat<DateTimeComponents>
375
```
376
377
**Usage Examples:**
378
379
```kotlin
380
import kotlinx.datetime.*
381
382
// Custom LocalDate format: "25/12/2023"
383
val customDateFormat = LocalDate.Format {
384
dayOfMonth()
385
char('/')
386
monthNumber()
387
char('/')
388
year()
389
}
390
391
val date = LocalDate(2023, 12, 25)
392
println(date.format(customDateFormat)) // "25/12/2023"
393
394
// Custom LocalTime format: "3:30:45 PM"
395
val customTimeFormat = LocalTime.Format {
396
hour(padding = Padding.NONE) // No leading zero
397
char(':')
398
minute()
399
char(':')
400
second()
401
char(' ')
402
amPmMarker()
403
}
404
405
val time = LocalTime(15, 30, 45)
406
println(time.format(customTimeFormat)) // "3:30:45 PM"
407
408
// Custom LocalDateTime format with milliseconds: "2023-12-25 15:30:45.123"
409
val customDateTimeFormat = LocalDateTime.Format {
410
year()
411
char('-')
412
monthNumber()
413
char('-')
414
dayOfMonth()
415
char(' ')
416
hour()
417
char(':')
418
minute()
419
char(':')
420
second()
421
char('.')
422
secondFraction(minLength = 3, maxLength = 3) // Always 3 digits
423
}
424
425
val dateTime = LocalDateTime(2023, 12, 25, 15, 30, 45, 123000000)
426
println(dateTime.format(customDateTimeFormat)) // "2023-12-25 15:30:45.123"
427
428
// Complex format with day of week: "Monday, December 25, 2023"
429
val verboseFormat = LocalDate.Format {
430
// Note: This would require additional enum formatting support
431
monthNumber() // Would need month name support in actual implementation
432
char(' ')
433
dayOfMonth()
434
chars(", ")
435
year()
436
}
437
```
438
439
### DateTimeComponents
440
441
Container class for holding individual date/time components during formatting operations.
442
443
```kotlin { .api }
444
/**
445
* Container for date/time components used in formatting
446
* Holds individual components that can be formatted independently
447
*/
448
class DateTimeComponents {
449
// Date components
450
var year: Int?
451
var month: Int?
452
var dayOfMonth: Int?
453
var dayOfYear: Int?
454
var dayOfWeek: Int?
455
456
// Time components
457
var hour: Int?
458
var minute: Int?
459
var second: Int?
460
var nanosecond: Int?
461
462
// Offset component
463
var offsetSeconds: Int?
464
465
/**
466
* Convert to LocalDate if date components are available
467
* @returns LocalDate or null if insufficient components
468
*/
469
fun toLocalDate(): LocalDate?
470
471
/**
472
* Convert to LocalTime if time components are available
473
* @returns LocalTime or null if insufficient components
474
*/
475
fun toLocalTime(): LocalTime?
476
477
/**
478
* Convert to LocalDateTime if date and time components are available
479
* @returns LocalDateTime or null if insufficient components
480
*/
481
fun toLocalDateTime(): LocalDateTime?
482
483
/**
484
* Convert to UtcOffset if offset component is available
485
* @returns UtcOffset or null if offset not set
486
*/
487
fun toUtcOffset(): UtcOffset?
488
}
489
```
490
491
### Instant Formatting
492
493
Special formatting for Instant values that require offset specification.
494
495
```kotlin { .api }
496
/**
497
* Format an Instant using DateTimeComponents format with specified offset
498
* @param format DateTimeComponents format to use
499
* @param offset UTC offset for formatting the instant
500
* @returns Formatted string
501
*/
502
fun Instant.format(format: DateTimeFormat<DateTimeComponents>, offset: UtcOffset): String
503
504
/**
505
* Parse an Instant from string using DateTimeComponents format
506
* @param input String to parse
507
* @param format DateTimeComponents format to use
508
* @returns Parsed Instant
509
*/
510
fun Instant.Companion.parse(input: CharSequence, format: DateTimeFormat<DateTimeComponents>): Instant
511
```
512
513
**Usage Examples:**
514
515
```kotlin
516
import kotlinx.datetime.*
517
import kotlin.time.Clock
518
519
// Create a custom format for Instant
520
val instantFormat = DateTimeComponents.Format {
521
year()
522
char('-')
523
monthNumber()
524
char('-')
525
dayOfMonth()
526
char('T')
527
hour()
528
char(':')
529
minute()
530
char(':')
531
second()
532
offset(UtcOffsetFormat.ISO)
533
}
534
535
val now = Clock.System.now()
536
val offset = UtcOffset(hours = 5, minutes = 30)
537
538
// Format instant with offset
539
val formatted = now.format(instantFormat, offset)
540
println(formatted) // "2023-12-25T20:30:45+05:30"
541
542
// Parse back to instant
543
val parsed = Instant.parse(formatted, instantFormat)
544
```
545
546
## Advanced Formatting Patterns
547
548
### Conditional Formatting
549
550
```kotlin
551
import kotlinx.datetime.*
552
553
// Format that shows seconds only when non-zero
554
val conditionalTimeFormat = LocalTime.Format {
555
hour()
556
char(':')
557
minute()
558
// In a real implementation, this would need conditional support
559
// Currently not directly supported, would need custom format implementation
560
}
561
562
// Work around with multiple formats for different cases
563
val timeWithSeconds = LocalTime.Format {
564
hour()
565
char(':')
566
minute()
567
char(':')
568
second()
569
}
570
571
val timeWithoutSeconds = LocalTime.Format {
572
hour()
573
char(':')
574
minute()
575
}
576
577
fun formatTimeConditionally(time: LocalTime): String {
578
return if (time.second == 0 && time.nanosecond == 0) {
579
time.format(timeWithoutSeconds)
580
} else {
581
time.format(timeWithSeconds)
582
}
583
}
584
585
val time1 = LocalTime(15, 30) // No seconds
586
val time2 = LocalTime(15, 30, 45) // Has seconds
587
588
println(formatTimeConditionally(time1)) // "15:30"
589
println(formatTimeConditionally(time2)) // "15:30:45"
590
```
591
592
### Localized Formatting
593
594
While the core library focuses on ISO formats, custom localized formats can be created:
595
596
```kotlin
597
import kotlinx.datetime.*
598
599
// German date format: DD.MM.YYYY
600
val germanDateFormat = LocalDate.Format {
601
dayOfMonth()
602
char('.')
603
monthNumber()
604
char('.')
605
year()
606
}
607
608
// US date format: MM/DD/YYYY
609
val usDateFormat = LocalDate.Format {
610
monthNumber()
611
char('/')
612
dayOfMonth()
613
char('/')
614
year()
615
}
616
617
val date = LocalDate(2023, 12, 25)
618
println("German: ${date.format(germanDateFormat)}") // "25.12.2023"
619
println("US: ${date.format(usDateFormat)}") // "12/25/2023"
620
621
// 24-hour vs 12-hour time
622
val time24Format = LocalTime.Format {
623
hour()
624
char(':')
625
minute()
626
}
627
628
val time12Format = LocalTime.Format {
629
hour() // Would need 12-hour conversion logic
630
char(':')
631
minute()
632
char(' ')
633
amPmMarker()
634
}
635
636
val time = LocalTime(15, 30)
637
println("24-hour: ${time.format(time24Format)}") // "15:30"
638
// 12-hour would need additional conversion logic
639
```
640
641
### Complex Date Formats
642
643
```kotlin
644
import kotlinx.datetime.*
645
646
// ISO week date format (would need additional support)
647
val isoWeekFormat = LocalDate.Format {
648
year()
649
char('-')
650
chars("W")
651
// Would need week-of-year support
652
dayOfWeek()
653
}
654
655
// Ordinal date format: YYYY-DDD
656
val ordinalFormat = LocalDate.Format {
657
year()
658
char('-')
659
dayOfYear(padding = Padding.ZERO) // Pad to 3 digits
660
}
661
662
val date = LocalDate(2023, 12, 25)
663
println("Ordinal: ${date.format(ordinalFormat)}") // "2023-359"
664
665
// Custom verbose format with literals
666
val verboseFormat = LocalDate.Format {
667
chars("Year ")
668
year()
669
chars(", Month ")
670
monthNumber()
671
chars(", Day ")
672
dayOfMonth()
673
}
674
675
println("Verbose: ${date.format(verboseFormat)}") // "Year 2023, Month 12, Day 25"
676
```
677
678
## Error Handling
679
680
Parsing operations can fail and throw exceptions:
681
682
```kotlin { .api }
683
/**
684
* Thrown when date/time parsing fails
685
*/
686
class DateTimeParseException : RuntimeException {
687
constructor(message: String)
688
constructor(message: String, cause: Throwable?)
689
}
690
```
691
692
**Error Handling Examples:**
693
694
```kotlin
695
import kotlinx.datetime.*
696
697
val format = LocalDate.Format {
698
year()
699
char('-')
700
monthNumber()
701
char('-')
702
dayOfMonth()
703
}
704
705
// Safe parsing
706
val input = "2023-13-45" // Invalid date
707
val result = format.parseOrNull(input) // Returns null instead of throwing
708
709
if (result == null) {
710
println("Failed to parse: $input")
711
} else {
712
println("Parsed: $result")
713
}
714
715
// Exception-based parsing
716
try {
717
val parsed = format.parse(input)
718
println("Parsed: $parsed")
719
} catch (e: DateTimeParseException) {
720
println("Parse error: ${e.message}")
721
}
722
723
// Validate format before parsing
724
fun safeParse(input: String, format: DateTimeFormat<LocalDate>): LocalDate? {
725
return try {
726
format.parse(input)
727
} catch (e: Exception) {
728
null
729
}
730
}
731
```
732
733
## Format Debugging
734
735
Generate DSL code for existing formats:
736
737
```kotlin
738
import kotlinx.datetime.*
739
740
// Generate code for predefined format
741
val isoCode = DateTimeFormat.formatAsKotlinBuilderDsl(LocalDate.Formats.ISO)
742
println("ISO format DSL:")
743
println(isoCode)
744
745
// This would output something like:
746
// LocalDate.Format {
747
// year()
748
// char('-')
749
// monthNumber()
750
// char('-')
751
// dayOfMonth()
752
// }
753
```