Skip to content

Commit

Permalink
Merge pull request #174 from jama5262/ISSUE-172_worki_on_implementing…
Browse files Browse the repository at this point in the history
…_the_format_functionailty

[ISSUE-172] Implement the new datetime format functionality
  • Loading branch information
jama5262 committed Dec 30, 2022
2 parents 04c0489 + d67e586 commit 8e28251
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 51 deletions.
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) ?? '' : '';
}

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

0 comments on commit 8e28251

Please sign in to comment.