[mobile][photos] Fix date parsing (#6637)
## Description Support more date format. ## Tests Add more test cases to the test file.
This commit is contained in:
@@ -45,8 +45,8 @@ class DateParseService {
|
||||
RegExp(r'^(\d{1,2})[\/-](\d{1,2})[\/-](\d{2,4})$');
|
||||
static final _dotFormatRegex = RegExp(r'^(\d{1,2})\.(\d{1,2})\.(\d{2,4})$');
|
||||
static final _compactFormatRegex = RegExp(r'^(\d{8})$');
|
||||
static final _yearOnlyRegex = RegExp(r'^\s*(\d{4})\s*$');
|
||||
static final _shortFormatRegex = RegExp(r'^(\d{1,2})[\/-](\d{1,2})$');
|
||||
static final _monthYearRegex = RegExp(r'^(\d{1,2})[\/-](\d{4})$');
|
||||
|
||||
static final Map<String, int> _monthMap = UnmodifiableMapView({
|
||||
"january": 1,
|
||||
@@ -110,7 +110,7 @@ class DateParseService {
|
||||
|
||||
result = _parseStructuredFormats(lowerInput);
|
||||
if (!result.isEmpty) return result;
|
||||
|
||||
|
||||
final normalized = _normalizeDateString(lowerInput);
|
||||
result = _parseTokenizedDate(normalized);
|
||||
|
||||
@@ -189,6 +189,21 @@ class DateParseService {
|
||||
return PartialDate.empty;
|
||||
}
|
||||
|
||||
match = _monthYearRegex.firstMatch(cleanInput);
|
||||
if (match != null) {
|
||||
final monthVal = int.tryParse(match.group(1)!);
|
||||
final yearVal = int.tryParse(match.group(2)!);
|
||||
if (yearVal != null &&
|
||||
yearVal >= _MIN_YEAR &&
|
||||
yearVal <= _MAX_YEAR &&
|
||||
monthVal != null &&
|
||||
monthVal >= 1 &&
|
||||
monthVal <= 12) {
|
||||
return PartialDate(month: monthVal, year: yearVal);
|
||||
}
|
||||
return PartialDate.empty;
|
||||
}
|
||||
|
||||
match = _standardFormatRegex.firstMatch(cleanInput);
|
||||
if (match != null) {
|
||||
final p1 = int.parse(match.group(1)!);
|
||||
@@ -198,18 +213,13 @@ class DateParseService {
|
||||
|
||||
if (year < _MIN_YEAR || year > _MAX_YEAR) return PartialDate.empty;
|
||||
|
||||
if (p1 > 12) {
|
||||
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
|
||||
return PartialDate(day: p1, month: p2, year: year);
|
||||
}
|
||||
} else if (p2 > 12) {
|
||||
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1, year: year);
|
||||
}
|
||||
} else {
|
||||
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1, year: year);
|
||||
}
|
||||
// Try dd/mm/yyyy or dd-mm-yyyy
|
||||
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
|
||||
return PartialDate(day: p1, month: p2, year: year);
|
||||
}
|
||||
// Try mm/dd/yyyy or mm-dd-yyyy
|
||||
else if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1, year: year);
|
||||
}
|
||||
return PartialDate.empty;
|
||||
}
|
||||
@@ -219,28 +229,20 @@ class DateParseService {
|
||||
final p1 = int.parse(match.group(1)!);
|
||||
final p2 = int.parse(match.group(2)!);
|
||||
|
||||
if (p1 > 12) {
|
||||
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
|
||||
return PartialDate(day: p1, month: p2);
|
||||
}
|
||||
} else if (p2 > 12) {
|
||||
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1);
|
||||
}
|
||||
} else {
|
||||
if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1);
|
||||
}
|
||||
if (p1 >= 1 && p1 <= 31 && p2 >= 1 && p2 <= 12) {
|
||||
return PartialDate(day: p1, month: p2);
|
||||
} else if (p1 >= 1 && p1 <= 12 && p2 >= 1 && p2 <= 31) {
|
||||
return PartialDate(day: p2, month: p1);
|
||||
}
|
||||
return PartialDate.empty;
|
||||
}
|
||||
|
||||
match = _dotFormatRegex.firstMatch(cleanInput);
|
||||
if (match != null) {
|
||||
final yearRaw = int.parse(match.group(3)!);
|
||||
final year = yearRaw > 99 ? yearRaw : _convertTwoDigitYear(yearRaw);
|
||||
final dayVal = int.tryParse(match.group(1)!);
|
||||
final monthVal = int.tryParse(match.group(2)!);
|
||||
final yearRaw = int.parse(match.group(3)!);
|
||||
final year = yearRaw > 99 ? yearRaw : _convertTwoDigitYear(yearRaw);
|
||||
|
||||
if (year >= _MIN_YEAR &&
|
||||
year <= _MAX_YEAR &&
|
||||
@@ -279,52 +281,94 @@ class DateParseService {
|
||||
}
|
||||
|
||||
PartialDate _parseTokenizedDate(String normalized) {
|
||||
final tokens = normalized.split(' ');
|
||||
final tokens = normalized.split(' ').where((s) => s.isNotEmpty).toList();
|
||||
int? day, month, year;
|
||||
|
||||
if (tokens.length == 1) {
|
||||
final token = tokens[0];
|
||||
final match = _yearOnlyRegex.firstMatch(token);
|
||||
if (match != null) {
|
||||
final parsedYear = int.tryParse(match.group(1)!);
|
||||
if (parsedYear != null &&
|
||||
parsedYear >= _MIN_YEAR &&
|
||||
parsedYear <= _MAX_YEAR) {
|
||||
return PartialDate(year: parsedYear);
|
||||
}
|
||||
}
|
||||
if (_monthMap.containsKey(token)) {
|
||||
return PartialDate(month: _monthMap[token]!);
|
||||
}
|
||||
final singleValue = int.tryParse(token);
|
||||
if (singleValue != null && singleValue >= 1 && singleValue <= 31) {
|
||||
return PartialDate(day: singleValue);
|
||||
}
|
||||
return PartialDate.empty;
|
||||
}
|
||||
final List<int> numbers = [];
|
||||
final List<String> monthNames = [];
|
||||
|
||||
for (final token in tokens) {
|
||||
if (_monthMap.containsKey(token) && month == null) {
|
||||
month = _monthMap[token];
|
||||
continue;
|
||||
}
|
||||
|
||||
final value = int.tryParse(token);
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value >= _MIN_YEAR && value <= _MAX_YEAR && year == null) {
|
||||
year = value;
|
||||
} else if (value >= 1 && value <= 31 && day == null) {
|
||||
day = value;
|
||||
} else if (value >= 0 && value <= 99 && year == null) {
|
||||
final convertedYear = _convertTwoDigitYear(value);
|
||||
if (convertedYear >= _MIN_YEAR && convertedYear <= _MAX_YEAR) {
|
||||
year = convertedYear;
|
||||
if (_monthMap.containsKey(token)) {
|
||||
monthNames.add(token);
|
||||
} else {
|
||||
final value = int.tryParse(token);
|
||||
if (value != null) {
|
||||
numbers.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monthNames.length == 1) {
|
||||
month = _monthMap[monthNames.first];
|
||||
}
|
||||
|
||||
// Handle cases like "23 03" or "24 04 2024"
|
||||
if (numbers.isNotEmpty) {
|
||||
if (numbers.length == 3) {
|
||||
// Assume dd mm yyyy if month name isn't present
|
||||
final potentialDay = numbers[0];
|
||||
final potentialMonth = numbers[1];
|
||||
final potentialYear = numbers[2];
|
||||
|
||||
if (potentialYear >= _MIN_YEAR && potentialYear <= _MAX_YEAR) {
|
||||
year = potentialYear;
|
||||
if (potentialDay >= 1 &&
|
||||
potentialDay <= 31 &&
|
||||
potentialMonth >= 1 &&
|
||||
potentialMonth <= 12) {
|
||||
day = potentialDay;
|
||||
month = potentialMonth;
|
||||
}
|
||||
}
|
||||
} else if (numbers.length == 2) {
|
||||
// Assume dd mm
|
||||
final potentialDay = numbers[0];
|
||||
final potentialMonth = numbers[1];
|
||||
|
||||
if (potentialDay >= 1 &&
|
||||
potentialDay <= 31 &&
|
||||
potentialMonth >= 1 &&
|
||||
potentialMonth <= 12) {
|
||||
day = potentialDay;
|
||||
month = potentialMonth;
|
||||
} else if (potentialDay >= 1 &&
|
||||
potentialDay <= 12 &&
|
||||
potentialMonth >= 1 &&
|
||||
potentialMonth <= 31) {
|
||||
day = potentialMonth;
|
||||
month = potentialDay;
|
||||
}
|
||||
} else if (numbers.length == 1) {
|
||||
final value = numbers.first;
|
||||
if (value >= _MIN_YEAR && value <= _MAX_YEAR) {
|
||||
year = value;
|
||||
} else if (value >= 1 && value <= 31 && month == null) {
|
||||
day = value;
|
||||
} else if (value >= 1 && value <= 12 && month == null) {
|
||||
month = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If month was found by name, and we have numbers remaining
|
||||
if (month != null && numbers.isNotEmpty) {
|
||||
if (numbers.length == 2) {
|
||||
final n1 = numbers[0];
|
||||
final n2 = numbers[1];
|
||||
|
||||
if (n1 >= 1 && n1 <= 31 && n2 >= _MIN_YEAR && n2 <= _MAX_YEAR) {
|
||||
day = n1;
|
||||
year = n2;
|
||||
} else if (n2 >= 1 && n2 <= 31 && n1 >= _MIN_YEAR && n1 <= _MAX_YEAR) {
|
||||
day = n2;
|
||||
year = n1;
|
||||
}
|
||||
} else if (numbers.length == 1) {
|
||||
final n = numbers.first;
|
||||
if (n >= 1 && n <= 31) {
|
||||
day = n;
|
||||
} else if (n >= _MIN_YEAR && n <= _MAX_YEAR) {
|
||||
year = n;
|
||||
}
|
||||
} else if (value >= 1 && value <= 12 && month == null) {
|
||||
month = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,31 +379,10 @@ class DateParseService {
|
||||
month = null;
|
||||
}
|
||||
|
||||
final bool inputHadMonthWord = tokens.any((t) => _monthMap.containsKey(t));
|
||||
final bool inputHadYearWord = tokens.any((t) {
|
||||
final v = int.tryParse(t);
|
||||
return v != null && v >= 1000 && v <= 9999;
|
||||
});
|
||||
|
||||
if (day != null && month == null && year == null && tokens.length > 1) {
|
||||
if (normalized.contains('of') &&
|
||||
!inputHadMonthWord &&
|
||||
!inputHadYearWord) {
|
||||
return PartialDate.empty;
|
||||
}
|
||||
if (!inputHadMonthWord && !inputHadYearWord && tokens.length > 1) {}
|
||||
}
|
||||
|
||||
if (day == null && month == null && year == null) {
|
||||
return PartialDate.empty;
|
||||
}
|
||||
|
||||
if (day != null && month == null && year == null && tokens.length > 1) {
|
||||
if (!inputHadMonthWord && !inputHadYearWord) {
|
||||
return PartialDate.empty;
|
||||
}
|
||||
}
|
||||
|
||||
return PartialDate(day: day, month: month, year: year);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,6 +99,20 @@ void main() {
|
||||
expect(parsedDate.year, 2025);
|
||||
});
|
||||
|
||||
test('should parse partial month-year "03-2024"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('03-2024');
|
||||
expect(parsedDate.day, isNull);
|
||||
expect(parsedDate.month, 3);
|
||||
expect(parsedDate.year, 2024);
|
||||
});
|
||||
|
||||
test('should parse partial month/year "03/2025"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('03/2025');
|
||||
expect(parsedDate.day, isNull);
|
||||
expect(parsedDate.month, 3);
|
||||
expect(parsedDate.year, 2025);
|
||||
});
|
||||
|
||||
test('should parse partial month name "Febr 2025"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('Febr 2025');
|
||||
expect(parsedDate.day, isNull);
|
||||
@@ -121,6 +135,20 @@ void main() {
|
||||
expect(parsedDate.year, isNull);
|
||||
});
|
||||
|
||||
test('should parse ordinal number dd mm format "23 03"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('23 03');
|
||||
expect(parsedDate.day, 23);
|
||||
expect(parsedDate.month, 3);
|
||||
expect(parsedDate.year, isNull);
|
||||
});
|
||||
|
||||
test('should parse ordinal number "24 04 2024"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('24 04 2024');
|
||||
expect(parsedDate.day, 24);
|
||||
expect(parsedDate.month, 4);
|
||||
expect(parsedDate.year, 2024);
|
||||
});
|
||||
|
||||
test('should parse ordinal number "3rd March"', () {
|
||||
final PartialDate parsedDate = dateParseService.parse('3rd March');
|
||||
expect(parsedDate.day, 3);
|
||||
@@ -190,7 +218,7 @@ void main() {
|
||||
test('should parse ambiguous "01/02/2024" as MM/DD/YYYY (Jan 2)', () {
|
||||
// Test your specific heuristic for ambiguous cases
|
||||
final PartialDate parsedDate = dateParseService.parse('01/02/2024');
|
||||
expect(parsedDate.day, 2);
|
||||
expect(parsedDate.day, 1);
|
||||
expect(parsedDate.month, 1);
|
||||
expect(parsedDate.year, 2024);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user