Skip to content

Commit 9ff66dc

Browse files
authored
Merge pull request #6 from gbassisp/feature/git-format
Feature/git format
2 parents d4e0062 + cb0e011 commit 9ff66dc

File tree

6 files changed

+145
-33
lines changed

6 files changed

+145
-33
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11

2+
## 1.0.4
3+
4+
- Added support for git non-sense format, e.g., `Thu May 16 10:18:07 2024 +0930` and `Thu May 16 10:18:07pm 2024 +0930`
5+
26
## 1.0.3
37

48
- Merged in `0.1.14`:

lib/src/any_date_rules.dart

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,36 +13,45 @@ const _yearPattern = r'(?<year>\d+)';
1313
const _ambiguousYearPattern = r'(?<year>\d{2})';
1414
const _dayPattern = r'(?<day>[0-3]?\d)';
1515
const _textMonthPattern = r'(?<month>\w+)';
16-
const _monthPattern = r'(?<month>[0-1]?\d)';
16+
const _monthPattern = r'(?<month>(0?\d)|(1[0-2]))';
1717
// const _anyMonthPattern = r'(?<month>\d{1,2}|\w+)';
1818
const _hourPattern = r'(?<hour>[0-2]?\d)';
1919
const _minutePattern = r'(?<minute>[0-5]?\d)';
2020
const _secondPattern = r'(?<second>[0-5]?\d)';
21-
const _microsecondPattern = r'(?<microsecond>\d{1,6})';
21+
const _microsecondPattern = r'(?<microsecond>\d+)';
2222
final _separatorPattern = '[${usedSeparators.reduce((v1, v2) => '$v1,$v2')}]+';
2323
final _s = _separatorPattern;
2424
// final _separatorPattern =
2525
// '(${usedSeparators.reduce((v1, v2) => '$v1|$v2')})+';
2626
// avoid const because this will be updated soon-ish
2727
String get _timeSep => ':';
28-
final _hmPattern = '$_hourPattern$_timeSep$_minutePattern';
29-
final _hmsPattern = '$_hmPattern$_timeSep$_secondPattern';
30-
final _hmsMsPattern = '$_hmsPattern.$_microsecondPattern';
28+
final _hmIdeal = '$_hourPattern$_timeSep$_minutePattern';
29+
final _hmsIdeal = '$_hmIdeal$_timeSep$_secondPattern';
30+
final _hmsMsIdeal = '$_hmsIdeal\\.$_microsecondPattern';
3131

3232
/// ideal time expressions that uses only ':' as separators
3333
@internal
3434
final idealTimePatterns = [
35-
_hmsMsPattern,
36-
_hmsPattern, // + r'(\D|$)',
37-
_hmPattern,
35+
_hmsMsIdeal,
36+
_hmsIdeal, // + r'(\D|$)',
37+
_hmIdeal,
3838
_hourPattern,
3939
];
4040

4141
/// original patterns used in implementation that allows non-sense separators,
4242
/// such as "-"
4343
/// this is not entirely supported because was never tested for "h" or "min"
4444
/// separators
45-
final _timePatterns = idealTimePatterns.map((e) => e.replaceAll(_timeSep, _s));
45+
final _hmPattern = '$_hourPattern$_s$_minutePattern';
46+
final _hmsPattern = '$_hmPattern$_s$_secondPattern';
47+
final _hmsMsPattern = '$_hmsPattern.$_microsecondPattern';
48+
49+
final _timePatterns = [
50+
_hmsMsPattern,
51+
_hmsPattern, // + r'(\D|$)',
52+
_hmPattern,
53+
_hourPattern,
54+
];
4655

4756
/// default parsing rule from dart core
4857
DateTime? dateTimeTryParse(String formattedString) =>

lib/src/param_cleanup_rules.dart

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -122,44 +122,65 @@ Month? _expectMonth(DateParsingParameters parameters) {
122122
(element) => timestamp.contains(element.name.toLowerCase()),
123123
);
124124

125-
return month.firstOrNullExtenstion ?? english.firstOrNullExtenstion;
125+
return month.firstOrNullExtension ?? english.firstOrNullExtension;
126126
}
127127

128128
Weekday? _expectWeekday(DateParsingParameters parameters) {
129+
// TODO(gbassisp): allow weekday in any part of the string
130+
// currently unsupported because some locales can have a conflict between
131+
// month and weekday (e.g., "Mar" in French for Mardi and Mars)
129132
final timestamp = parameters.formattedString.toLowerCase();
130-
final weekday = parameters.parserInfo.weekdays
133+
var weekday = parameters.parserInfo.weekdays
131134
.where(
132135
(element) => timestamp.startsWith(element.name.toLowerCase()),
136+
// (element) => timestamp
137+
// .contains(RegExp('\\D${element.name}', caseSensitive: false)),
133138
)
134-
.firstOrNullExtenstion;
135-
final english = allWeekdays
139+
.firstOrNullExtension;
140+
if (weekday != null) return weekday;
141+
// weekday = parameters.parserInfo.weekdays
142+
// .where((element) => timestamp.endsWith(element.name.toLowerCase()))
143+
// .firstOrNullExtension;
144+
// if (weekday != null) return weekday;
145+
146+
// english
147+
weekday = allWeekdays
136148
.where(
137149
(element) => timestamp.startsWith(element.name.toLowerCase()),
138150
)
139-
.firstOrNullExtenstion;
151+
.firstOrNullExtension;
152+
if (weekday != null) return weekday;
140153

141-
return weekday ?? english;
154+
return allWeekdays
155+
.where(
156+
(element) => timestamp.endsWith(element.name.toLowerCase()),
157+
)
158+
.firstOrNullExtension;
142159
}
143160

144161
final _exprs = [...idealTimePatterns]..removeLast();
145162
final _betterTimeComponent = CleanupRule((params) {
146163
String padLeft(String? original) => (original ?? '').padLeft(2, '0');
147164
String padRight(String? original) => (original ?? '').padRight(3, '0');
165+
String cleanAmpm(String? original) =>
166+
original?.replaceAll(RegExp(r'(\.|-)'), '').toLowerCase() ?? '';
148167

149168
for (final e in _exprs) {
150-
final re = RegExp(e);
151-
final matches = re.allMatches(params.formattedString);
169+
const ampm = r'\s*(?<ampm>(a|p)(\.|-)?m(\.|-)?\W)?';
170+
final re = RegExp(e + ampm, caseSensitive: false);
171+
final s = '${params.formattedString} ';
172+
final matches = re.allMatches(s);
152173
// unsure what to do if many matches
153174
if (matches.length == 1) {
154175
final m = matches.first;
155-
final newString = params.formattedString.replaceAllMapped(
156-
re,
157-
(match) => '${padLeft(m.namedGroup('hour'))}:'
158-
'${padLeft(m.tryNamedGroup('minute'))}:'
159-
'${padLeft(m.tryNamedGroup('second'))}'
160-
'${m.tryNamedGroup('microsecond') != null ? '.'
161-
'${padRight(m.tryNamedGroup('microsecond'))}' : ''}',
162-
);
176+
final newTime = ' ${padLeft(m.namedGroup('hour'))}:'
177+
'${padLeft(m.tryNamedGroup('minute'))}:'
178+
'${padLeft(m.tryNamedGroup('second'))}'
179+
'${m.tryNamedGroup('microsecond') != null ? '.'
180+
'${padRight(m.tryNamedGroup('microsecond'))}' : ''} '
181+
'${cleanAmpm(m.tryNamedGroup('ampm'))}';
182+
final newString = s.replaceAllMapped(re, (_) => '') + newTime;
183+
163184
params
164185
..formattedString = newString
165186
..simplifiedString = newString;
@@ -198,6 +219,6 @@ extension _GroupNames on RegExpMatch {
198219
}
199220

200221
extension _IterableX<T> on Iterable<T> {
201-
T? get firstOrNullExtenstion => isEmpty ? null : first;
222+
T? get firstOrNullExtension => isEmpty ? null : first;
202223
// T? get lastOrNull => isEmpty ? null : last;
203224
}

pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: any_date
22
description: A package for parsing String into DateTime in any format. Easy way to parse a date in any format and in any language, while always respecting ISO and RFC formats.
3-
version: 1.0.3
3+
version: 1.0.4
44
repository: https://github.com/gbassisp/any_date
55
homepage: https://github.com/gbassisp/any_date
66

test/locale_based_test.dart

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,11 @@ Future<void> main() async {
103103
final englishMonths = AnyDate.defaultSettings.months;
104104
final englishWeekdays = AnyDate.defaultSettings.weekdays;
105105
final longWeekdays = englishWeekdays.sublist(0, 7);
106-
final shortWeekdays = englishWeekdays.sublist(7)
107-
..removeWhere((element) => element.name == 'Sept');
106+
final shortWeekdays = englishWeekdays.sublist(7);
108107
test('english speaking - american format', () {
109108
final locale = Locale.fromSubtags(languageCode: 'en', countryCode: 'US');
110109
final longMonths = englishMonths.sublist(0, 12);
111-
final shortMonths = englishMonths.sublist(12)
112-
..removeWhere((element) => element.name == 'Sept');
110+
final shortMonths = englishMonths.sublist(12);
113111

114112
expect(locale.usesMonthFirst, isTrue);
115113
expect(locale.usesYearFirst, isFalse);
@@ -122,8 +120,7 @@ Future<void> main() async {
122120
test('english speaking - normal format', () {
123121
final locale = Locale.fromSubtags(languageCode: 'en', countryCode: 'AU');
124122
final longMonths = englishMonths.sublist(0, 12);
125-
final shortMonths = englishMonths.sublist(12)
126-
..removeWhere((element) => element.name == 'Sep');
123+
final shortMonths = englishMonths.sublist(12);
127124

128125
expect(locale.usesMonthFirst, isFalse);
129126
expect(locale.usesYearFirst, isFalse);
@@ -135,6 +132,26 @@ Future<void> main() async {
135132
});
136133

137134
group('locale specific cases', () {
135+
// this is to ensure 日 is not mis-interpreted between day of the week and
136+
// day of the month
137+
test(
138+
'2024年8月31日 on ja with format y年M月d日 resulted in null, '
139+
'but expected 2024-08-31 00:00:00.000', () {
140+
const locale = 'ja';
141+
const formatted = '2024年8月31日';
142+
const format = 'y年M月d日';
143+
final formatter = DateFormat(format);
144+
final expected = DateTime.parse('2024-08-31 00:00:00.000');
145+
final parser = AnyDate.fromLocale(locale);
146+
147+
expect(
148+
formatter.parseLoose(formatted),
149+
equals(expected),
150+
reason: 'sanity check that DateFormat $format can parse $formatted',
151+
);
152+
expect(parser.tryParse(formatted), equals(expected));
153+
});
154+
138155
const unambiguousEnglish = {
139156
'March 27, 2024',
140157
'March 27 2024',

test/nonsense_formats_test.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import 'package:any_date/any_date.dart';
2+
import 'package:any_date/src/extensions.dart';
23
import 'package:test/test.dart';
34

5+
import 'test_values.dart';
6+
47
void main() {
58
group('lack of separators formats', () {
69
const parser = AnyDate();
@@ -93,4 +96,62 @@ void main() {
9396
expect(parser.tryParse(formatted), equals(expected));
9497
});
9598
});
99+
100+
group('git', () {
101+
final parser = parsers.first;
102+
test('git default nonsense 1', () {
103+
const formatted = 'Thu May 16 10:18:07 2024 +0930';
104+
final expected =
105+
DateTime.utc(2024, 5, 16, 10, 18, 7).copyWithOffset('+0930');
106+
107+
expect(parser.tryParse(formatted), equals(expected));
108+
});
109+
test('git default nonsense 2', () {
110+
const formatted = 'Thu May 16 10:18:07 2024 -0930';
111+
final expected =
112+
DateTime.utc(2024, 5, 16, 10, 18, 7).copyWithOffset('-0930');
113+
114+
expect(parser.tryParse(formatted), equals(expected));
115+
});
116+
for (final am in [
117+
'am',
118+
'AM',
119+
'a.m.',
120+
'a.m',
121+
'A.M',
122+
' am',
123+
' AM',
124+
' a.m.',
125+
' a.m',
126+
' A.M',
127+
]) {
128+
test('git default nonsense 3 - "$am"', () {
129+
final formatted = 'Thu, May 16 10:18:07$am 2024 -0930';
130+
final expected =
131+
DateTime.utc(2024, 5, 16, 10, 18, 7).copyWithOffset('-0930');
132+
133+
expect(parser.tryParse(formatted), equals(expected));
134+
});
135+
}
136+
for (final pm in [
137+
'pm',
138+
'PM',
139+
'p.m.',
140+
'p.m',
141+
'P.M',
142+
' pm',
143+
' PM',
144+
' p.m.',
145+
' p.m',
146+
' P.M',
147+
]) {
148+
test('git default nonsense 4 - "$pm"', () {
149+
final formatted = 'Thu, May 16 10:18:07$pm 2024 -0930';
150+
final expected =
151+
DateTime.utc(2024, 5, 16, 22, 18, 7).copyWithOffset('-0930');
152+
153+
expect(parser.tryParse(formatted), equals(expected));
154+
});
155+
}
156+
});
96157
}

0 commit comments

Comments
 (0)