Skip to content

Commit dbabe1b

Browse files
authored
[Dart] Fix some issues and upgrade to new DateTime API (#509)
* Start fixing dart * datetime fixes * More * Use published version * Switch to icu77 * set version * Regenerate * slight cleanup * Add failing tests
1 parent 200297f commit dbabe1b

File tree

12 files changed

+283
-64
lines changed

12 files changed

+283
-64
lines changed

executors/dart/bin/executor.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void printVersion() {
8282
final dartVersion = version.substring(0, version.indexOf(' '));
8383
final versionInfo = {
8484
'platform': 'Dart Native',
85-
'icuVersion': '73', //TODO: get from ICU4X somehow
85+
'icuVersion': '77', //TODO: get from ICU4X somehow
8686
'platformVersion': dartVersion,
8787
'intlVersion': intl4xVersion,
8888
};

executors/dart/lib/collator.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import 'package:intl4x/collation.dart';
66
import 'package:intl4x/intl4x.dart';
77

88
String testCollation(String jsonEncoded) {
9-
final json =
10-
jsonDecode(jsonEncoded)
11-
as Map<
12-
String,
13-
dynamic
14-
>; // For the moment, use strings for easier interop
9+
final json = jsonDecode(jsonEncoded) as Map<String, dynamic>;
1510
// Global default locale
16-
final testLocale = json['locale'] as String? ?? 'en';
1711
final outputLine = <String, dynamic>{'label': json['label']};
18-
12+
if (json.containsKey('rules')) {
13+
outputLine['error_type'] = 'unsupported';
14+
outputLine['unsupported'] = 'unsupported_options';
15+
outputLine['error_message'] = 'Rules are not supported';
16+
return jsonEncode(outputLine);
17+
}
18+
final localeString = json['locale'] as String? ?? 'en';
19+
if (localeString == 'root') {
20+
outputLine['error_type'] = 'unsupported';
21+
outputLine['unsupported'] = 'unsupported_options';
22+
outputLine['error_message'] = 'Locale `root` is unsupported';
23+
return jsonEncode(outputLine);
24+
}
1925
// Set up collator object with optional locale and testOptions.
2026
final s1 = json['s1'];
2127
final s2 = json['s2'];
@@ -36,6 +42,7 @@ String testCollation(String jsonEncoded) {
3642
.where((value) => value.jsName == json['case_first'])
3743
.firstOrNull ??
3844
CaseFirst.localeDependent;
45+
final compareType = json['compare_type'] as String?;
3946

4047
if (s1 == null || s2 == null) {
4148
outputLine.addAll({
@@ -45,7 +52,7 @@ String testCollation(String jsonEncoded) {
4552
});
4653
} else {
4754
try {
48-
final coll = Intl(locale: Locale.parse(testLocale));
55+
final coll = Intl(locale: Locale.parse(localeString));
4956

5057
final collationOptions = CollationOptions(
5158
ignorePunctuation: ignorePunctuation,
@@ -55,7 +62,17 @@ String testCollation(String jsonEncoded) {
5562
);
5663

5764
final compared = coll.collation(collationOptions).compare(s1, s2);
58-
final result = compared <= 0;
65+
66+
bool result;
67+
if (compareType == '=') {
68+
// Check for strict equality comparison
69+
result = compared == 0;
70+
} else if (compareType != null && compareType.startsWith('<')) {
71+
// Check results with different compare types
72+
result = compared < 0;
73+
} else {
74+
result = compared <= 0;
75+
}
5976
outputLine['result'] = result;
6077

6178
if (result != true) {
Lines changed: 141 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'dart:convert';
2+
23
import 'package:collection/collection.dart';
34
import 'package:intl4x/datetime_format.dart';
45
import 'package:intl4x/intl4x.dart';
@@ -10,29 +11,34 @@ import 'package:intl4x/intl4x.dart';
1011
String testDateTimeFmt(String jsonEncoded) {
1112
final json = jsonDecode(jsonEncoded) as Map<String, dynamic>;
1213

13-
final label = json['label'];
1414
var localeString = json['locale'] as String?; // locale can be null from JSON
1515
if (localeString == null || localeString.isEmpty) {
1616
localeString = 'und'; // Default to 'und' if locale is null or empty
1717
}
1818

19+
final returnJson = <String, dynamic>{'label': json['label']};
20+
1921
// Parse Locale string
2022
Locale locale;
2123
try {
2224
locale = Locale.parse(localeString.replaceAll('_', '-'));
2325
} catch (e) {
24-
return jsonEncode({
25-
'label': label,
26-
'error': 'Invalid locale format: ${e.toString()}',
27-
'locale': localeString,
28-
});
26+
returnJson['error'] = 'Invalid locale format: ${e.toString()}';
27+
returnJson['locale'] = localeString;
28+
return jsonEncode(returnJson);
2929
}
3030

3131
var testOptionsJson = <String, dynamic>{};
3232
if (json['options'] != null) {
3333
testOptionsJson = json['options'] as Map<String, dynamic>;
3434
}
3535

36+
if (testOptionsJson['dateTimeFormatType'] == 'atTime') {
37+
returnJson['error_type'] = 'unsupported';
38+
returnJson['unsupported'] = '`at` not supported';
39+
return jsonEncode(returnJson);
40+
}
41+
3642
// Initialize DateTimeFormatOptions
3743
var dateTimeFormatOptions = DateTimeFormatOptions();
3844

@@ -53,11 +59,22 @@ String testDateTimeFmt(String jsonEncoded) {
5359
}
5460

5561
// ignore: unused_local_variable - to be used with the timezoneformatter
56-
String? timezone;
57-
if (testOptionsJson.containsKey('time_zone')) {
58-
timezone = testOptionsJson['time_zone'] as String;
62+
String? timeZoneName;
63+
int? offsetSeconds;
64+
if (testOptionsJson.containsKey('timeZone') &&
65+
json.containsKey('tz_offset_secs')) {
66+
timeZoneName = testOptionsJson['timeZone'] as String;
67+
offsetSeconds = (json['tz_offset_secs'] as num).toInt();
5968
}
6069

70+
final semanticSkeleton = testOptionsJson['semanticSkeleton'] as String?;
71+
final semanticSkeletonLength =
72+
testOptionsJson['semanticSkeletonLength'] as String?;
73+
74+
final dateStyle = testOptionsJson['dateStyle'] as String?;
75+
final timeStyle = testOptionsJson['timeStyle'] as String?;
76+
final yearStyle = testOptionsJson['yearStyle'] as String?;
77+
6178
DateTime? testDate;
6279
if (json['input_string'] != null) {
6380
final isoDateString = json['input_string'] as String;
@@ -72,34 +89,133 @@ String testDateTimeFmt(String jsonEncoded) {
7289
try {
7390
testDate = DateTime.parse(testDateString);
7491
} catch (e) {
75-
return jsonEncode({
76-
'label': label,
77-
'error': 'Invalid input_string date format: ${e.toString()}',
78-
'input_string': isoDateString,
79-
});
92+
returnJson['error'] = 'Invalid input_string date format: ${e.toString()}';
93+
returnJson['input_string'] = isoDateString;
94+
return jsonEncode(returnJson);
8095
}
8196
} else {
8297
testDate = DateTime.now();
8398
}
8499

85-
final returnJson = <String, dynamic>{'label': label};
86100
final dtFormatter = Intl(
87101
locale: locale,
88102
).dateTimeFormat(dateTimeFormatOptions);
89103

90-
try {} catch (error) {
91-
returnJson['error'] = 'DateTimeFormat Constructor: ${error.toString()}';
92-
returnJson['options'] = testOptionsJson;
93-
return jsonEncode(returnJson);
94-
}
95-
96104
try {
97-
final formattedDt = dtFormatter.ymd(testDate);
105+
final formatter = semanticSkeleton != null
106+
? getFormatterForSkeleton(
107+
semanticSkeleton,
108+
semanticSkeletonLength,
109+
dtFormatter,
110+
)
111+
: getFormatterForStyle(dateStyle, timeStyle, yearStyle, dtFormatter);
112+
String formattedDt;
113+
if (timeStyle == 'full' && timeZoneName != null) {
114+
final offset = Duration(seconds: offsetSeconds!);
115+
final timeZone = TimeZone(name: timeZoneName, offset: offset);
116+
final timeZoneStyle = 'long';
117+
final zonedFormatter = getZonedFormatter(timeZoneStyle, formatter);
118+
formattedDt = zonedFormatter.format(testDate.add(offset), timeZone);
119+
} else {
120+
formattedDt = formatter.format(testDate);
121+
}
98122
returnJson['result'] = formattedDt;
99-
returnJson['actual_options'] = dateTimeFormatOptions.toString();
100-
} catch (error) {
101-
returnJson['unsupported'] = ': ${error.toString()}';
123+
} on Exception catch (e) {
124+
returnJson['error_type'] = 'unsupported';
125+
returnJson['unsupported'] = ': ${e.toString()}';
126+
return jsonEncode(returnJson);
102127
}
128+
returnJson['actual_options'] = dateTimeFormatOptions.humanReadable;
129+
returnJson['options'] = testOptionsJson;
103130

104131
return jsonEncode(returnJson);
105132
}
133+
134+
DateTimeFormatter getFormatterForSkeleton(
135+
String semanticSkeleton,
136+
String? semanticSkeletonLength,
137+
DateTimeFormatBuilder dtFormatter,
138+
) {
139+
// The provided Rust code implies a more complex logic, but here we'll map the known skeletons.
140+
// The Rust code's `None => None` and `None => Ok(...)` branches aren't directly translatable
141+
// to a Dart function that must return a Formatter. We'll handle the valid cases and throw for others.
142+
143+
final semanticDateStyle = switch (semanticSkeletonLength) {
144+
'short' => DateFormatStyle.short,
145+
'medium' => DateFormatStyle.medium,
146+
'long' => DateFormatStyle.long,
147+
_ => throw Exception(),
148+
};
149+
return switch (semanticSkeleton) {
150+
'D' || 'DT' || 'DTZ' => dtFormatter.d(),
151+
'MD' => dtFormatter.md(),
152+
'MDT' || 'MDTZ' => dtFormatter.mdt(dateStyle: semanticDateStyle),
153+
'YMD' || 'YMDT' || 'YMDTZ' => dtFormatter.ymd(dateStyle: semanticDateStyle),
154+
'YMDE' => dtFormatter.ymde(dateStyle: semanticDateStyle),
155+
'YMDET' || 'YMDETZ' => dtFormatter.ymdet(dateStyle: semanticDateStyle),
156+
'M' => dtFormatter.m(),
157+
'Y' => dtFormatter.y(),
158+
'T' || 'Z' || 'TZ' => dtFormatter.t(),
159+
_ => throw Exception('Unknown skeleton: $semanticSkeleton'),
160+
};
161+
}
162+
163+
ZonedDateTimeFormatter getZonedFormatter(
164+
String timeZoneStyle,
165+
DateTimeFormatter formatter,
166+
) => switch (timeZoneStyle) {
167+
'short' => formatter.withTimeZoneShort(),
168+
'long' => formatter.withTimeZoneLong(),
169+
'full' => formatter.withTimeZoneLongGeneric(),
170+
String() => throw Exception('Unknown time zone style `$timeZoneStyle`'),
171+
};
172+
173+
DateTimeFormatter getFormatterForStyle(
174+
String? dateStyle,
175+
String? timeStyle,
176+
String? yearStyle,
177+
DateTimeFormatBuilder dtFormatter,
178+
) => switch ((dateStyle, timeStyle, yearStyle)) {
179+
('medium', null, _) => dtFormatter.ymd(dateStyle: DateFormatStyle.medium),
180+
(null, 'short', _) => dtFormatter.t(style: TimeFormatStyle.short),
181+
('full', 'short', null) => dtFormatter.ymdet(
182+
dateStyle: DateFormatStyle.full,
183+
timeStyle: TimeFormatStyle.short,
184+
),
185+
('full', 'full', null) => dtFormatter.ymdet(
186+
dateStyle: DateFormatStyle.full,
187+
timeStyle: TimeFormatStyle.full,
188+
),
189+
('short', 'full', null) => dtFormatter.ymdt(
190+
dateStyle: DateFormatStyle.short,
191+
timeStyle: TimeFormatStyle.full,
192+
),
193+
('short', 'full', 'with_era') => dtFormatter.ymdet(
194+
dateStyle: DateFormatStyle.short,
195+
timeStyle: TimeFormatStyle.full,
196+
),
197+
(_, _, 'with_era') => dtFormatter.ymde(),
198+
(_, _, _) => throw Exception(
199+
'Unknown combination of date style `$dateStyle`, time style `$timeStyle`, and year style `$yearStyle`',
200+
),
201+
};
202+
203+
extension on DateTimeFormatOptions {
204+
String get humanReadable {
205+
final fields = <String, dynamic>{
206+
if (calendar != null) 'calendar': calendar,
207+
if (dayPeriod != null) 'dayPeriod': dayPeriod,
208+
if (numberingSystem != null) 'numberingSystem': numberingSystem,
209+
if (clockstyle != null) 'clockstyle': clockstyle,
210+
if (era != null) 'era': era,
211+
if (timestyle != null) 'timestyle': timestyle,
212+
if (fractionalSecondDigits != null)
213+
'fractionalSecondDigits': fractionalSecondDigits,
214+
'formatMatcher': formatMatcher,
215+
};
216+
final entries = fields.entries
217+
.map((e) => '${e.key}: ${e.value}')
218+
.join(', ');
219+
return 'DateTimeFormatOptions($entries)';
220+
}
221+
}

executors/dart/lib/lang_names.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ String testLangNames(String jsonEncoded) {
2626
}
2727

2828
final languageLabel = json['language_label'] as String;
29+
if (languageLabel.contains('u-kr')) {
30+
outputLine.addAll({
31+
'unsupported': 'u-kr extension not supported',
32+
'error_retry': false, // Do not repeat
33+
});
34+
return jsonEncode(outputLine);
35+
}
36+
2937
Locale languageLabelLocale;
3038
try {
3139
languageLabelLocale = Locale.parse(languageLabel);
@@ -49,7 +57,6 @@ String testLangNames(String jsonEncoded) {
4957
outputLine['result'] = resultLangName;
5058
} catch (error) {
5159
outputLine.addAll({
52-
'error_type': 'unsupported',
5360
'error_detail': error.toString(),
5461
'actual_options': options.toJson(),
5562
'error_retry': false, // Do not repeat

executors/dart/lib/numberformat.dart

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ NumberFormatOptions _decimalPatternToOptions(
280280
);
281281
return numberFormatOptions.copyWith(roundingMode: roundingMode);
282282
} else {
283-
return numberFormatOptions;
283+
//TODO: remove this halfEven default override, as soon as it is always passed in the numberformat args.
284+
return numberFormatOptions.copyWith(roundingMode: RoundingMode.halfEven);
284285
}
285286
}
286287

@@ -324,9 +325,6 @@ NumberFormatOptions _fromJson(Map<String, dynamic> options) {
324325
final signDisplay = SignDisplay.values
325326
.where((element) => element.name == options['signDisplay'])
326327
.firstOrNull;
327-
final localeMatcher = LocaleMatcher.values
328-
.where((element) => element.jsName == options['localeMatcher'])
329-
.firstOrNull;
330328
final useGrouping = Grouping.values
331329
.where((element) => element.jsName == options['useGrouping'])
332330
.firstOrNull;
@@ -374,7 +372,6 @@ NumberFormatOptions _fromJson(Map<String, dynamic> options) {
374372
return NumberFormatOptions.custom().copyWith(
375373
style: style,
376374
currency: currency,
377-
localeMatcher: localeMatcher,
378375
signDisplay: signDisplay,
379376
notation: notation,
380377
useGrouping: useGrouping,
@@ -391,11 +388,10 @@ extension on NumberFormatOptions {
391388
return {
392389
'style': style.name,
393390
'currency': currency,
394-
'localeMatcher': localeMatcher.jsName,
395391
'signDisplay': signDisplay.name,
396392
'notation': notation.name,
397393
'useGrouping': useGrouping.jsName,
398-
'numberingSystem': numberingSystem?.toString(),
394+
'numberingSystem': numberingSystem,
399395
'roundingMode': roundingMode.name,
400396
'trailingZeroDisplay': trailingZeroDisplay.name,
401397
'minimumIntegerDigits': minimumIntegerDigits,

executors/dart/lib/version.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/// This file is autogenerated by bin/set_version.dart, do not modify manually.
2-
const intl4xVersion = '0.12.2';
2+
const intl4xVersion = '0.13.0';

executors/dart/out/version.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
const dartVersion = "0.12.2";
1+
const dartVersion = "0.13.0";
22
module.exports = { dartVersion };

0 commit comments

Comments
 (0)