Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-172] Implement the new datetime format functionality #174

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/default_display.dart
Expand Up @@ -3,6 +3,7 @@ import 'package:intl/intl.dart';
class DefaultDisplay {
// todo add examples as docs comments here
// check of doc comments from here will be shown or is it from the jiffy file
// todo also test in other locales e.g E(locale).format
String E(DateTime dateTime) => DateFormat.E().format(dateTime);

String EEEE(DateTime dateTime) => DateFormat.EEEE().format(dateTime);
Expand Down
57 changes: 32 additions & 25 deletions lib/src/display.dart
Expand Up @@ -2,6 +2,7 @@ import 'package:intl/intl.dart';

import 'getter.dart';
import 'enums/units.dart';
import 'locale/locale.dart';
import 'manipulator.dart';
import 'utils/exception.dart';

Expand All @@ -13,13 +14,20 @@ class Display {

String formatToISO8601(DateTime dateTime) => dateTime.toIso8601String();

String format(DateTime dateTime, String pattern, String ordinal) {
final escapedPattern = _replaceEscapePattern(pattern);
final newPattern = _replaceOrdinalDatePattern(escapedPattern, ordinal);
String format(DateTime dateTime, String pattern, Locale locale) {
if (pattern.trim().isEmpty) {
throw JiffyException('The provided pattern for datetime `$dateTime` '
'cannot be blank');
}
try {
final escapedPattern = _replaceEscapePattern(pattern);
final ordinal = locale.ordinal(getter.date(dateTime));
final newPattern = _replaceOrdinalDatePattern(escapedPattern, ordinal);
return DateFormat(newPattern).format(dateTime);
} on Exception catch (e) {
throw JiffyException(e.toString());
} catch (error, stackTrace) {
throw JiffyException('The pattern `$pattern` might be invalid: \n'
'Error: $error'
'Stack Trace: $stackTrace');
}
}

Expand Down Expand Up @@ -72,25 +80,30 @@ class Display {
}

String _replaceEscapePattern(String input) {
return input.replaceAll('[', '\'').replaceAll(']', '\'');
return input
.replaceAll('\'', '\'\'')
.replaceAll('[', '\'')
.replaceAll(']', '\'');
}

String _replaceOrdinalDatePattern(String input, String suffix) {
final regex = _matchesOrdinalDatePattern().allMatches(input);
String _replaceOrdinalDatePattern(String input, String ordinal) {
var matches = _matchesOrdinalDatePattern(input);
var pattern = input;
regex.forEach((match) {
if (match.group(1) == 'do') {
pattern = input.replaceRange(
match.start, match.end, 'd${suffix.isNotEmpty ? "'$suffix'" : ''}');
}
});

while (matches.isNotEmpty) {
final match = matches.first;
pattern = pattern.replaceRange(
match.start, match.end, 'd${ordinal.isNotEmpty ? "'$ordinal'" : ''}');
matches = _matchesOrdinalDatePattern(pattern);
}
return pattern;
}

// todo understand what this regex pattern does
Pattern _matchesOrdinalDatePattern() {
return RegExp(
'''(?<unquote>[^"'\\s]\\w*)|(?:["][^"]+?["])|(?:['][^']+?['])''');
List<Match> _matchesOrdinalDatePattern(String input) {
return RegExp('''\'[^\']*\'|(do)''')
.allMatches(input)
.where((match) => match.group(1) == 'do')
.toList();
}

num _monthDiff(DateTime firstDateTime, DateTime secondDateTime) {
Expand Down Expand Up @@ -127,13 +140,7 @@ class Display {
return -(monthDiff + offset);
}

int _absFloor(num number) {
if (number < 0) {
return number.ceil();
} else {
return number.floor();
}
}
int _absFloor(num number) => number < 0 ? number.ceil() : number.floor();

DateTime _addMonths(DateTime dateTime, int months) {
return manipulator.add(dateTime, 0, 0, 0, 0, 0, 0, 0, months, 0);
Expand Down
1 change: 0 additions & 1 deletion lib/src/getter.dart
Expand Up @@ -56,7 +56,6 @@ class Getter {
return int.parse(DateFormat('Q').format(dateTime));
}

// todo see if you can use a formatter class
int dayOfYear(DateTime dateTime) {
return int.parse(DateFormat('D').format(dateTime));
}
Expand Down
14 changes: 9 additions & 5 deletions lib/src/parser.dart
Expand Up @@ -2,19 +2,21 @@ import 'package:intl/intl.dart';

import 'enums/units.dart';
import 'getter.dart';
import 'locale/locale.dart';
import 'utils/exception.dart';

class Parser {
final Getter getter;

Parser(this.getter);

DateTime fromString(String input, String? pattern, List<String> ordinals) {
DateTime fromString(String input, String? pattern, Locale locale) {
if (pattern != null) {
if (pattern.trim().isEmpty) {
throw JiffyException('The provided pattern for `$input` cannot '
'be empty');
'be blank');
}
final ordinals = locale.ordinals()!;
return parseString(
_replaceParseInput(input, ordinals), _replacePatternInput(pattern));
}
Expand Down Expand Up @@ -81,14 +83,16 @@ class Parser {
return input
.replaceFirst(' pm', ' PM')
.replaceFirst(' am', ' AM')
.replaceFirst(_matchesOrdinalDates(ordinals), '');
.replaceFirst(_matchesOrdinalDates(input, ordinals), '');
}

// todo fix this regex replacement of the ordinals when there is a space between
// example `12 th`
// todo test if empty ordinals are provided
Pattern _matchesOrdinalDates(List<String> ordinals) {
return RegExp(r'(?<=[0-9])(?:' + ordinals.join('|') + ')');
String _matchesOrdinalDates(String input, List<String> ordinals) {
final matches =
RegExp(r'\d+\s*(' + ordinals.join('|') + ')').allMatches(input);
return matches.isNotEmpty ? matches.first.group(1) ?? '' : '';
Comment on lines -90 to +95
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolves issue #72

}

bool _matchesHyphenStringDateTime(String input) {
Expand Down
189 changes: 188 additions & 1 deletion test/src/display_test.dart
@@ -1,8 +1,10 @@
import 'package:jiffy/src/display.dart';
import 'package:jiffy/src/enums/startOfWeek.dart';
import 'package:jiffy/src/enums/units.dart';
import 'package:jiffy/src/getter.dart';
import 'package:jiffy/src/locale/enLocale.dart';
import 'package:jiffy/src/manipulator.dart';
import 'package:test/scaffolding.dart';
import 'package:jiffy/src/utils/exception.dart';
import 'package:test/test.dart';

void main() {
Expand All @@ -11,6 +13,96 @@ void main() {

final underTest = Display(getter, manipulator);

test('Should successfully format datetime is iso when pattern not provided',
() {
// Setup
final dateTime = DateTime(1997, 9, 23, 12, 11, 22, 123, 456);

final expectedFormat = '1997-09-23T12:11:22.123456';

// Execute
final actualFormat = underTest.formatToISO8601(dateTime);

// Verify
expect(actualFormat, expectedFormat);
});

group('Test format with pattern', () {
for (var testData in formatWithPatternDateTimeTestData()) {
test('Should successfully format datetime when pattern is provided', () {
// Setup
final locale = EnLocale(StartOfWeek.MONDAY);

// Execute
final actualFormat =
underTest.format(testData['dateTime'], testData['pattern'], locale);

// Verify
expect(actualFormat, testData['expectedFormat']);
});
}

for (var testData in formatWithEscapedPatternDateTimeTestData()) {
test('Should successfully format datetime with escaped pattern', () {
// Setup
final locale = EnLocale(StartOfWeek.MONDAY);

// Execute
final actualFormat =
underTest.format(testData['dateTime'], testData['pattern'], locale);

// Verify
expect(actualFormat, testData['expectedFormat']);
});
}

for (var testData in formatWithOrdinalPatternDateTimeTestData()) {
test('Should successfully format datetime with ordinal pattern', () {
// Setup
final locale = EnLocale(StartOfWeek.MONDAY);

// Execute
final actualFormat =
underTest.format(testData['dateTime'], testData['pattern'], locale);

// Verify
expect(actualFormat, testData['expectedFormat']);
});
}

test('Should throw JiffyException if provided pattern in blank', () {
// Setup
final dateTime = DateTime(1997, 9, 23, 12, 11, 22, 123, 456);
final pattern = '';
final locale = EnLocale(StartOfWeek.MONDAY);

final expectedExceptionMessage = 'The provided pattern for datetime '
'`$dateTime` cannot be blank';

// Execute and Verify
expect(
() => underTest.format(dateTime, pattern, locale),
throwsA(isA<JiffyException>().having((e) => e.message, 'message',
contains(expectedExceptionMessage))));
});

test('Should throw JiffyException if provided pattern is invalid', () {
// Setup
final dateTime = DateTime(1997, 9, 23, 12, 11, 22, 123, 456);
final pattern = 'invalid-pattern';
final locale = EnLocale(StartOfWeek.MONDAY);

final expectedExceptionMessage =
'The pattern `$pattern` might be invalid';

// Execute and Verify
expect(
() => underTest.format(dateTime, pattern, locale),
throwsA(isA<JiffyException>().having((e) => e.message, 'message',
contains(expectedExceptionMessage))));
});
});

group('Test diff', () {
for (var testData in diffDateTimeTestData()) {
test('Should successfully get difference between two datetime', () {
Expand Down Expand Up @@ -45,6 +137,101 @@ void main() {
});
}

List<Map<String, dynamic>> formatWithPatternDateTimeTestData() {
return [
{
'dateTime': DateTime(1997, 9, 23, 12, 11, 22, 123, 456),
'pattern': 'yyyy MMM dd hh:mm:ss',
'expectedFormat': '1997 Sep 23 12:11:22'
},
{
'dateTime': DateTime(1997, 9, 23, 12, 11, 22, 123, 456),
'pattern': 'yyyy MMM, dd hh:mm a',
'expectedFormat': '1997 Sep, 23 12:11 PM'
},
{
'dateTime': DateTime(1997, 9, 23, 11, 11, 22, 123, 456),
'pattern': 'yyyy MMM dd hh:mm a',
'expectedFormat': '1997 Sep 23 11:11 AM'
},
{
'dateTime': DateTime(1997, 9, 23, 11, 11, 22, 123, 456),
'pattern': 'do MMMM',
'expectedFormat': '23rd September'
},
{
'dateTime': DateTime(1997, 9, 23, 11, 11, 22, 123, 456),
'pattern': 'EEEE',
'expectedFormat': 'Tuesday'
}
];
}

List<Map<String, dynamic>> formatWithEscapedPatternDateTimeTestData() {
return [
{
'dateTime': DateTime(1969, 7, 20, 20, 18, 04),
'pattern': '[The moon landing was on] do MMMM, yyyy',
'expectedFormat': 'The moon landing was on 20th July, 1969'
},
{
'dateTime': DateTime(1997, 9, 23, 12, 11, 22, 123, 456),
'pattern': '[Today\'s date is] do [in the month of] MMMM',
'expectedFormat': 'Today\'s date is 23rd in the month of September'
},
{
'dateTime': DateTime(1997, 9, 23, 12, 11, 22, 123, 456),
'pattern': '[It\'s] hh [o\'clock]',
'expectedFormat': 'It\'s 12 o\'clock'
},
{
'dateTime': DateTime(1997, 9, 23, 12, 11, 22, 123, 456),
'pattern': "[It's] hh [o'clock]",
'expectedFormat': "It's 12 o'clock"
}
];
}

List<Map<String, dynamic>> formatWithOrdinalPatternDateTimeTestData() {
return [
{
'dateTime': DateTime(2022, 1, 1),
'pattern': 'do',
'expectedFormat': '1st'
},
{
'dateTime': DateTime(2022, 1, 12),
'pattern': 'do',
'expectedFormat': '12th'
},
{
'dateTime': DateTime(2022, 1, 21),
'pattern': 'do',
'expectedFormat': '21st'
},
{
'dateTime': DateTime(2022, 1, 2),
'pattern': 'do',
'expectedFormat': '2nd'
},
{
'dateTime': DateTime(2022, 1, 3),
'pattern': 'do',
'expectedFormat': '3rd'
},
{
'dateTime': DateTime(2022, 1, 4),
'pattern': 'do',
'expectedFormat': '4th'
},
{
'dateTime': DateTime(2022, 1, 11),
'pattern': 'do',
'expectedFormat': '11th'
}
];
}

List<Map<String, dynamic>> diffDateTimeTestData() {
return [
{
Expand Down