From 601fecc0e692f40bc919cf2e1a4df26a61bb35b6 Mon Sep 17 00:00:00 2001 From: Crt Vavros <4377556+smlu@users.noreply.github.com> Date: Wed, 29 May 2024 15:01:11 +0200 Subject: [PATCH] Refactor `parseDateYYMMDD` to handle future dates correctly --- lib/src/extension/string_apis.dart | 19 ++++++++------- lib/src/lds/df1/efdg12.dart | 2 +- lib/src/lds/mrz.dart | 16 ++++++------- lib/src/proto/dba_key.dart | 4 ++-- test/extensions_test.dart | 38 ++++++++++++++++++++++++------ 5 files changed, 52 insertions(+), 27 deletions(-) diff --git a/lib/src/extension/string_apis.dart b/lib/src/extension/string_apis.dart index 8f573da..5cea7e3 100644 --- a/lib/src/extension/string_apis.dart +++ b/lib/src/extension/string_apis.dart @@ -15,27 +15,28 @@ extension StringDecodeApis on String { } extension StringYYMMDDateApi on String { - DateTime parseDateYYMMDD() { + DateTime parseDateYYMMDD({bool futureDate = false}) { if (length < 6) { - throw FormatException("invalid length of compact date string"); + throw FormatException("Invalid length of compact date string"); } int y = int.parse(substring(0, 2)) + 2000; int m = int.parse(substring(2, 4)); int d = int.parse(substring(4, 6)); - // Sub 100 years from parsed year if greater than 10 years and 5 months from now. - final now = DateTime.now(); - final tenYearsFromNow = now.year + 10; - if (y > tenYearsFromNow || (y == tenYearsFromNow && now.month + 5 < m)) { - y -= 100; + if (!futureDate) { + // If parsed year is greater than now sub ub 100 years from it. + final now = DateTime.now(); + if (y > now.year || ( y == now.year && now.month > m)) { + y -= 100; + } } return DateTime(y, m, d); } - DateTime parseDate() { + DateTime parseDate({bool futureDate = false}) { if (length == 6) { - return this.parseDateYYMMDD(); + return this.parseDateYYMMDD(futureDate: futureDate); } else { return DateTime.parse(this); } diff --git a/lib/src/lds/df1/efdg12.dart b/lib/src/lds/df1/efdg12.dart index 3c190f9..b6e1099 100644 --- a/lib/src/lds/df1/efdg12.dart +++ b/lib/src/lds/df1/efdg12.dart @@ -41,7 +41,7 @@ class EfDG12 extends DataGroup { DateTime? get dateOfIssue => _dateOfIssue; String? get issuingAuthority => _issuingAuthority; - + EfDG12.fromBytes(Uint8List data) : super.fromBytes(data); diff --git a/lib/src/lds/mrz.dart b/lib/src/lds/mrz.dart index f9f7634..a5cad14 100644 --- a/lib/src/lds/mrz.dart +++ b/lib/src/lds/mrz.dart @@ -112,13 +112,13 @@ class MRZ { _docNum = _read(istream, 9); final cdDocNum = _readWithPad(istream, 1); _optData = _read(istream, 15); - dateOfBirth = _readDate(istream); + dateOfBirth = _readDate(istream, futureDate: false); _assertCheckDigit(dateOfBirth.formatYYMMDD(), _readCD(istream), "Data of Birth check digit mismatch"); gender = _read(istream, 1); - dateOfExpiry = _readDate(istream); + dateOfExpiry = _readDate(istream, futureDate: true); _assertCheckDigit(dateOfExpiry.formatYYMMDD(), _readCD(istream), "Data of Expiry check digit mismatch"); @@ -151,12 +151,12 @@ class MRZ { final cdDocNum = _readWithPad(istream, 1); nationality = _read(istream, 3); - dateOfBirth = _readDate(istream); + dateOfBirth = _readDate(istream, futureDate: false); _assertCheckDigit(dateOfBirth.formatYYMMDD(), _readCD(istream), "Data of Birth check digit mismatch"); gender = _read(istream, 1); - dateOfExpiry = _readDate(istream); + dateOfExpiry = _readDate(istream, futureDate: true); _assertCheckDigit(dateOfExpiry.formatYYMMDD(), _readCD(istream), "Data of Expiry check digit mismatch"); @@ -185,12 +185,12 @@ class MRZ { _docNum, _readCD(istream), "Document Number check digit mismatch"); nationality = _read(istream, 3); - dateOfBirth = _readDate(istream); + dateOfBirth = _readDate(istream, futureDate: false); _assertCheckDigit(dateOfBirth.formatYYMMDD(), _readCD(istream), "Data of Birth check digit mismatch"); gender = _read(istream, 1); - dateOfExpiry = _readDate(istream); + dateOfExpiry = _readDate(istream, futureDate: true); _assertCheckDigit(dateOfExpiry.formatYYMMDD(), _readCD(istream), "Data of Expiry check digit mismatch"); @@ -240,8 +240,8 @@ class MRZ { return _readWithPad(istream, maxLength).replaceAll(RegExp(r'<+$'), ''); } - static DateTime _readDate(InputStream istream) { - return _read(istream, 6).parseDateYYMMDD(); + static DateTime _readDate(InputStream istream, bool futureDate) { + return _read(istream, 6).parseDateYYMMDD(futureDate: futureDate); } static int _readCD(InputStream istream) { diff --git a/lib/src/proto/dba_key.dart b/lib/src/proto/dba_key.dart index 5edd576..c716e3c 100644 --- a/lib/src/proto/dba_key.dart +++ b/lib/src/proto/dba_key.dart @@ -99,10 +99,10 @@ class DBAKey extends AccessKey { String get mrtdNumber => _mrtdNum; /// Returns passport owner's date of birth used for calculating key seed. - DateTime get dateOfBirth => _dob.parseDateYYMMDD(); + DateTime get dateOfBirth => _dob.parseDateYYMMDD(futureDate: false); /// Returns passport date of expiry used for calculating key seed. - DateTime get dateOfExpiry => _doe.parseDateYYMMDD(); + DateTime get dateOfExpiry => _doe.parseDateYYMMDD(futureDate: true); /// Very sensitive data. Do not use in production! @override diff --git a/test/extensions_test.dart b/test/extensions_test.dart index 7f6c2b9..ba1e5ef 100644 --- a/test/extensions_test.dart +++ b/test/extensions_test.dart @@ -80,19 +80,43 @@ void main() { expect( '121212'.parseDateYYMMDD() , DateTime(2012, 12, 12) ); expect( '201212'.parseDateYYMMDD() , DateTime(2020, 12, 12) ); + expect( '891109'.parseDateYYMMDD(futureDate: false) , DateTime(1989, 11, 9) ); + expect( '760501'.parseDateYYMMDD(futureDate: false) , DateTime(1976, 05, 01) ); + expect( '000215'.parseDateYYMMDD(futureDate: false) , DateTime(2000, 02, 15) ); + expect( '111111'.parseDateYYMMDD(futureDate: false) , DateTime(2011, 11, 11) ); + expect( '121212'.parseDateYYMMDD(futureDate: false) , DateTime(2012, 12, 12) ); + expect( '201212'.parseDateYYMMDD(futureDate: false) , DateTime(2020, 12, 12) ); + + expect( '891109'.parseDateYYMMDD(futureDate: true) , DateTime(1989, 11, 9) ); + expect( '760501'.parseDateYYMMDD(futureDate: true) , DateTime(1976, 05, 01) ); + expect( '000215'.parseDateYYMMDD(futureDate: true) , DateTime(2000, 02, 15) ); + expect( '111111'.parseDateYYMMDD(futureDate: true) , DateTime(2011, 11, 11) ); + expect( '121212'.parseDateYYMMDD(futureDate: true) , DateTime(2012, 12, 12) ); + expect( '201212'.parseDateYYMMDD(futureDate: true) , DateTime(2020, 12, 12) ); + final now = DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day); - expect( now.formatYYMMDD().parseDateYYMMDD() , now ); + expect( now.formatYYMMDD().parseDateYYMMDD(), now ); + expect( now.formatYYMMDD().parseDateYYMMDD(futureDate: false), now ); + expect( now.formatYYMMDD().parseDateYYMMDD(futureDate: true) , now ); + // 1 month future date final nextMonth = DateTime(now.year, now.month + 1, now.day); - expect( nextMonth.formatYYMMDD().parseDateYYMMDD(), nextMonth ); + expect( nextMonth.formatYYMMDD().parseDateYYMMDD() != nextMonth, true ); // by default future date is not expected + expect( nextMonth.formatYYMMDD().parseDateYYMMDD(futureDate: false) != nextMonth, true ); + expect( nextMonth.formatYYMMDD().parseDateYYMMDD(futureDate: true) == nextMonth, true ); + // 10 years future date final tenFromNow = DateTime(now.year + 10, now.month, now.day); - expect( tenFromNow.formatYYMMDD().parseDateYYMMDD(), tenFromNow ); + expect( tenFromNow.formatYYMMDD().parseDateYYMMDD() != tenFromNow, true ); // by default future date is not expected + expect( tenFromNow.formatYYMMDD().parseDateYYMMDD(futureDate: false) != tenFromNow, true ); + expect( tenFromNow.formatYYMMDD().parseDateYYMMDD(futureDate: true) == tenFromNow, true ); - // 10 years and 6 months from now should wind date back for a century. + // 10 years and 6 months future date final tenAnd6MonthsFromNow = DateTime(now.year + 10, now.month + 6, now.day); final ninetyYearsAgo = DateTime(now.year - 90, now.month + 6, now.day); - expect( tenAnd6MonthsFromNow.formatYYMMDD().parseDateYYMMDD(), ninetyYearsAgo ); + expect( tenAnd6MonthsFromNow.formatYYMMDD().parseDateYYMMDD() , ninetyYearsAgo ); + expect( tenAnd6MonthsFromNow.formatYYMMDD().parseDateYYMMDD(futureDate: false), ninetyYearsAgo ); + expect( tenAnd6MonthsFromNow.formatYYMMDD().parseDateYYMMDD(futureDate: true) , tenAnd6MonthsFromNow ); }); }); @@ -152,7 +176,7 @@ void main() { Logger.root.level = Level.FINEST; Logger.root.trace(traceMsg); expect( logMsg, traceMsg ); - expect( level , Level.FINEST ); + expect( level , Level.FINEST ); }); test('Sensitive data log test', () { @@ -404,7 +428,7 @@ void main() { dlog.sdDebug(debugMsg); expect( dlogMsg, debugMsg ); expect( dlevel , Level.FINE ); - + // Verbose test Logger.root.logSensitiveData = false; dlog.logSensitiveData = false;