diff --git a/analysis_options.yaml b/analysis_options.yaml index 8cccf3e..9f8939f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,4 +2,8 @@ include: package:very_good_analysis/analysis_options.yaml analyzer: exclude: - - example/* \ No newline at end of file + - example/* + +linter: + rules: + use_late_for_private_fields_and_variables: false diff --git a/lib/src/extensions.dart b/lib/src/extensions.dart index 3c589fa..ed5bd3d 100644 --- a/lib/src/extensions.dart +++ b/lib/src/extensions.dart @@ -2,6 +2,7 @@ import 'package:any_date/any_date.dart'; import 'package:meta/meta.dart'; /// a collection of extensions on [DateTime] +@internal extension DateTimeExtension on DateTime { /// returns a [DateTime] as UTC after applying the offset DateTime copyWithOffset(String offset) { @@ -111,6 +112,8 @@ extension DateTimeExtension on DateTime { allowRollover: true, ); } + + DateTime get dateOnly => DateTime(year, month, day); } const _parser = AnyDate(); diff --git a/lib/src/param_cleanup_rules.dart b/lib/src/param_cleanup_rules.dart index 9d52a48..15f4c17 100644 --- a/lib/src/param_cleanup_rules.dart +++ b/lib/src/param_cleanup_rules.dart @@ -84,7 +84,7 @@ const _knownSeparators = {..._usedSeparators, ..._specialSeparators}; /// these are the separators used by the default DateTime.parse String _replaceSeparators(String formattedString, Iterable separators) { - var result = formattedString; + var result = _replaceComma(formattedString); result = replaceUtc(result); final unknownSeparators = separators.toSet().difference(_knownSeparators); @@ -97,6 +97,17 @@ String _replaceSeparators(String formattedString, Iterable separators) { return _restoreMillisecons(result, separator).replaceAll(separator, '-'); } +String _replaceComma(String formattedString) { + final re1 = RegExp(r',\s+'); + final re2 = RegExp(r',\s+'); + final re3 = RegExp(r'\s+'); + + return formattedString + .replaceAll(re1, ' ') + .replaceAll(re2, ' ') + .replaceAll(re3, ' '); +} + String _restoreMillisecons(String formattedString, String separator) { // regex with T00:00:00-000 final r = RegExp( @@ -111,9 +122,19 @@ String _restoreMillisecons(String formattedString, String separator) { ); } +// .reversed because it works for disambiguation +// (e.g., in vi locale try 'thang 12' before 'thang 1') +Iterable _sortMonths(Iterable months) { + final sorted = months.toList() + ..sort((a, b) => a.name.length.compareTo(b.name.length)) + ..sort((a, b) => a.number.compareTo(b.number)); + + return sorted.reversed; +} + Month? _expectMonth(DateParsingParameters parameters) { final timestamp = parameters.formattedString.toLowerCase(); - final month = parameters.parserInfo.months.where( + final month = _sortMonths(parameters.parserInfo.months).where( (element) => element.name.tryToInt() == null && timestamp.contains(element.name.toLowerCase()), @@ -125,12 +146,22 @@ Month? _expectMonth(DateParsingParameters parameters) { return month.firstOrNullExtension ?? english.firstOrNullExtension; } +// .reversed because it works for disambiguation +// (e.g., in vi locale try 'thang 12' before 'thang 1') +Iterable _sortWeekdays(Iterable weekdays) { + final sorted = weekdays.toList() + ..sort((a, b) => a.name.length.compareTo(b.name.length)) + ..sort((a, b) => a.number.compareTo(b.number)); + + return sorted.reversed; +} + Weekday? _expectWeekday(DateParsingParameters parameters) { // TODO(gbassisp): allow weekday in any part of the string // currently unsupported because some locales can have a conflict between // month and weekday (e.g., "Mar" in French for Mardi and Mars) final timestamp = parameters.formattedString.toLowerCase(); - var weekday = parameters.parserInfo.weekdays + var weekday = _sortWeekdays(parameters.parserInfo.weekdays) .where( (element) => timestamp.startsWith(element.name.toLowerCase()), // (element) => timestamp diff --git a/test/failing_locales_test.dart b/test/failing_locales_test.dart new file mode 100644 index 0000000..49a78c8 --- /dev/null +++ b/test/failing_locales_test.dart @@ -0,0 +1,54 @@ +// the following locales are failing: +// vi +// nyn +// ln +// zh-TW +// zh_HK +// zh_CN +// zh +// ko +// mn +// ja +import 'package:any_date/any_date.dart'; +import 'package:any_date/src/extensions.dart'; +import 'package:intl/intl.dart'; +import 'package:test/test.dart'; + +import 'test_values.dart'; + +const _failing = { + 'vi', + 'nyn', + 'ln', + 'zh_TW', + 'zh_HK', + 'zh_CN', + 'zh', + 'ko', + 'mn', + 'ja', +}; +Future main() async { + await initializeDateFormatting(); + + final date = DateTime.now().dateOnly; + group('ensure failing locales are working', () { + for (final l in _failing) { + final format = DateFormat.yMMMMd(l); + final formatted = format.format(date); + test('locale $l can parse $formatted text month', () { + final parser = AnyDate.fromLocale(l); + + final parsed = parser.tryParse(formatted); + + expect(parsed, equals(date)); + }); + + test('sanity check - locale $l can self-parse $formatted text month', () { + final parsed = format.parse(formatted); + + expect(parsed, equals(date)); + }); + } + }); +}