Skip to content

Commit

Permalink
Update Dart duration serialization to be compatible with Kotlin ISO-8…
Browse files Browse the repository at this point in the history
…601 duration string parsing
  • Loading branch information
erksch committed Feb 22, 2025
1 parent fca8034 commit 3c722b3
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 6 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## unreleased

- Fix conflicts with same method names across different modules on iOS by prefixing method names
- Update Dart duration serialization to be compatible with Kotlin ISO-8601 duration string parsing

## v0.1.0-rc.2

Expand Down
8 changes: 8 additions & 0 deletions example/flutter/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class _MyAppState extends State<MyApp> {
print(await myTestModule.localDateMethod(DateTime.now()));
print(await myTestModule.localTimeMethod(TimeOfDay.now()));
print(await myTestModule.durationMethod(const Duration(seconds: 123)));
print(await myTestModule.durationMethod(const Duration(days: 1000)));

print(await myTestModule.dateClassMethod(MyDateClass(
DateTime.now(),
Expand All @@ -147,6 +148,13 @@ class _MyAppState extends State<MyApp> {
const Duration(seconds: 123),
DateTime.now().toUtc())));

print(await myTestModule.dateClassMethod(MyDateClass(
DateTime.now(),
TimeOfDay.now(),
DateTime.now(),
const Duration(days: 1000),
DateTime.now().toUtc())));

final broadcaster = myTestModule.intEvents;

// this starts a collect on the counter flow that is shared across all listeners
Expand Down
11 changes: 10 additions & 1 deletion example/flutter/lib/MyTestModule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,16 @@ final localDateSerialized = localDate.toIso8601String().split('T').first;
return result;
}
Future<Duration> durationMethod(Duration duration) async {
final durationSerialized = duration.toIso8601String();
final durationSerialized = duration.toIso8601String().replaceFirstMapped(RegExp(r'P([^T]*)(T.*)?'), (m) {
int totalDays = 0;
String datePart = m[1]!
.replaceAllMapped(RegExp(r'(\d+)Y'), (y) { totalDays += int.parse(y[1]!) * 365; return ''; })
.replaceAllMapped(RegExp(r'(\d+)M'), (m) { totalDays += int.parse(m[1]!) * 30; return ''; })
.replaceAllMapped(RegExp(r'(\d+)W'), (w) { totalDays += int.parse(w[1]!) * 7; return ''; })
.replaceAllMapped(RegExp(r'(\d+)D'), (d) { totalDays += int.parse(d[1]!); return ''; });

return 'P' + (totalDays > 0 ? '${totalDays}D' : '') + (m[2] ?? '');
});
final invokeResult = await methodChannelToNative.invokeMethod<String>(
'MyTestModule_durationMethod',
[durationSerialized],
Expand Down
11 changes: 10 additions & 1 deletion example/flutter/lib/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,16 @@ MyDateClass(this.date, this.time, this.dateTime, this.duration, this.instant);
static TimeOfDay _timeFromJson(String json) => TimeOfDay.fromDateTime(DateTime.parse("1998-01-01T$json:00.000"));


static String _durationToJson(Duration obj) => obj.toIso8601String();
static String _durationToJson(Duration obj) => obj.toIso8601String().replaceFirstMapped(RegExp(r'P([^T]*)(T.*)?'), (m) {
int totalDays = 0;
String datePart = m[1]!
.replaceAllMapped(RegExp(r'(\d+)Y'), (y) { totalDays += int.parse(y[1]!) * 365; return ''; })
.replaceAllMapped(RegExp(r'(\d+)M'), (m) { totalDays += int.parse(m[1]!) * 30; return ''; })
.replaceAllMapped(RegExp(r'(\d+)W'), (w) { totalDays += int.parse(w[1]!) * 7; return ''; })
.replaceAllMapped(RegExp(r'(\d+)D'), (d) { totalDays += int.parse(d[1]!); return ''; });

return 'P' + (totalDays > 0 ? '${totalDays}D' : '') + (m[2] ?? '');
});
static Duration _durationFromJson(String json) => parseIso8601Duration(json);


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,7 @@ ${declaration.declarations.filterIsInstance<KSClassDeclaration>()
val serializedType: String,
)

val customSerializations = listOf(
private val customSerializations = listOf(
// TimeOfDay is part of the material package and is not serializable by default
CustomSerialization(
detect = { prop ->
Expand Down Expand Up @@ -548,7 +548,7 @@ ${declaration.declarations.filterIsInstance<KSClassDeclaration>()
this.toDartType() is DartType.Duration
}
},
serializeFnString = { varName, _ -> "$varName.toIso8601String()" },
serializeFnString = { varName, _ -> "$varName.toIso8601String().$patchIso8601DurationStringDartCode" },
deserializeFnString = { varName, _ -> "parseIso8601Duration($varName)" },
serializedType = "String",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ internal fun DartType.getDartSerializationStatement(varName: String): String? {
is DartType.Primitive.Int,
is DartType.Primitive.String -> return null
}
is DartType.Duration -> "${varName}.toIso8601String()"
is DartType.Duration -> "${varName}.toIso8601String().$patchIso8601DurationStringDartCode"
is DartType.TimeOfDay -> "\"${'$'}{$varName.hour.toString().padLeft(2, '0')}:${'$'}{$varName.minute.toString().padLeft(2, '0')}\""
is DartType.LocalDate -> "${varName}.toIso8601String().split('T').first"
is DartType.DateTime, is DartType.LocalDateTime -> "$varName.toIso8601String()"
Expand All @@ -362,4 +362,23 @@ internal fun DartType.getDartSerializationStatement(varName: String): String? {
is DartType.List -> "jsonEncode(${transformEnumsForSerialization(varName, this)})"
is DartType.Map -> "jsonEncode(${transformEnumsForSerialization(varName, this)})"
} + ";"
}
}

/**
* The Pub package we use to parse Dart Durations to ISO-8601 strings, [iso_duration](https://github.com/pti/iso_duration),
* uses W, M, Y for day designators, which are not supported in kotlin.time.Duration ISO-8601 string parsing,
* see [here](https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.time/-duration/-companion/parse-iso-string.html).
*
* This code runs as an extension on a "x.toIso8601String()" call to patch
* the ISO-8601 duration string to use D instead of W, M, Y with W=7D, M=30D, Y=365D.
*/
internal const val patchIso8601DurationStringDartCode = """replaceFirstMapped(RegExp(r'P([^T]*)(T.*)?'), (m) {
int totalDays = 0;
String datePart = m[1]!
.replaceAllMapped(RegExp(r'(\d+)Y'), (y) { totalDays += int.parse(y[1]!) * 365; return ''; })
.replaceAllMapped(RegExp(r'(\d+)M'), (m) { totalDays += int.parse(m[1]!) * 30; return ''; })
.replaceAllMapped(RegExp(r'(\d+)W'), (w) { totalDays += int.parse(w[1]!) * 7; return ''; })
.replaceAllMapped(RegExp(r'(\d+)D'), (d) { totalDays += int.parse(d[1]!); return ''; });
return 'P' + (totalDays > 0 ? '${"$"}{totalDays}D' : '') + (m[2] ?? '');
})"""

0 comments on commit 3c722b3

Please sign in to comment.