11import 'dart:convert' ;
2+
23import 'package:collection/collection.dart' ;
34import 'package:intl4x/datetime_format.dart' ;
45import 'package:intl4x/intl4x.dart' ;
@@ -10,29 +11,34 @@ import 'package:intl4x/intl4x.dart';
1011String 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+ }
0 commit comments