Merge pull request 'Dob & Gender Fix' (#314) from dev_aamir into master

Reviewed-on: https://34.17.182.140/Haroon6138/HMG_Patient_App_New/pulls/314
master
Haroon6138 1 day ago
commit 99dfd2cea9

@ -562,18 +562,118 @@ class AuthenticationViewModel extends ChangeNotifier {
}
void onDobChange(String? date) {
if (calenderType == CalenderEnum.hijri) {
// Use DateRangCalenderModel for accurate Umm al-Qura conversion
final calendarModel = DateRangCalenderModel(appState: _appState, navigationService: _errorHandlerService, dialogService: _dialogService);
var hijriDate = calendarModel.gregorianToHijri(DateTime.parse(date!));
DateTime hijriDateTimeForController = DateTime(hijriDate.year, hijriDate.month, hijriDate.day);
dob = Utils.formatDateForApi(date);
dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String());
} else {
dobController.text = Utils.formatDateToDisplay(date!);
dob = Utils.formatDateForApi(date);
if (date == null || date.isEmpty) {
debugPrint('onDobChange: date is null or empty');
return;
}
notifyListeners();
try {
if (calenderType == CalenderEnum.hijri) {
// Use DateRangCalenderModel for accurate Umm al-Qura conversion
final calendarModel = DateRangCalenderModel(
appState: _appState,
navigationService: _errorHandlerService,
dialogService: _dialogService,
);
// Parse the Gregorian date with validation
DateTime gregorianDate;
try {
gregorianDate = DateTime.parse(date);
} catch (parseError) {
debugPrint('onDobChange: Failed to parse date "$date" - $parseError');
// Try to handle common date format issues
gregorianDate = _tryParseDateFlexibly(date);
}
// Convert to Hijri
final hijriDate = calendarModel.gregorianToHijri(gregorianDate);
// Validate Hijri date components
if (hijriDate.year <= 0 || hijriDate.month <= 0 || hijriDate.day <= 0) {
debugPrint('onDobChange: Invalid Hijri date components - Year: ${hijriDate.year}, Month: ${hijriDate.month}, Day: ${hijriDate.day}');
return;
}
// Create DateTime from Hijri components for display
final hijriDateTimeForController = DateTime(hijriDate.year, hijriDate.month, hijriDate.day);
// Format for API (use original Gregorian date)
dob = Utils.formatDateForApi(date);
// Format for display (use Hijri)
dobController.text = Utils.formatHijriDateToDisplay(hijriDateTimeForController.toIso8601String());
} else {
// Gregorian calendar mode
// Validate the date can be parsed
try {
DateTime.parse(date);
} catch (parseError) {
debugPrint('onDobChange: Failed to parse date "$date" - $parseError');
// Try flexible parsing to validate
_tryParseDateFlexibly(date);
}
// Format for display
dobController.text = Utils.formatDateToDisplay(date);
// Format for API
dob = Utils.formatDateForApi(date);
}
clearDobError();
notifyListeners();
} catch (e, stackTrace) {
debugPrint('onDobChange: Unexpected error processing date "$date" - $e');
debugPrint('Stack trace: $stackTrace');
// Set error state if needed
// You can add error handling UI feedback here
}
}
/// Helper method to handle flexible date parsing for common formats
DateTime _tryParseDateFlexibly(String dateString) {
// Remove extra whitespace
dateString = dateString.trim();
// Try standard ISO format first
try {
return DateTime.parse(dateString);
} catch (_) {}
// Try common formats with regex
final patterns = [
RegExp(r'^(\d{4})-(\d{1,2})-(\d{1,2})$'), // yyyy-M-d or yyyy-MM-dd
RegExp(r'^(\d{1,2})/(\d{1,2})/(\d{4})$'), // d/M/yyyy or dd/MM/yyyy
RegExp(r'^(\d{4})/(\d{1,2})/(\d{1,2})$'), // yyyy/M/d or yyyy/MM/dd
];
for (var pattern in patterns) {
final match = pattern.firstMatch(dateString);
if (match != null) {
try {
if (pattern.pattern.startsWith(r'^\d{4}')) {
// Year first formats
final year = int.parse(match.group(1)!);
final month = int.parse(match.group(2)!);
final day = int.parse(match.group(3)!);
return DateTime(year, month, day);
} else {
// Day first formats
final day = int.parse(match.group(1)!);
final month = int.parse(match.group(2)!);
final year = int.parse(match.group(3)!);
return DateTime(year, month, day);
}
} catch (_) {}
}
}
// If all else fails, throw descriptive error
throw FormatException('Unable to parse date: "$dateString". Expected format: yyyy-MM-dd');
}
Future<void> loadCountriesData() async {
@ -583,15 +683,21 @@ class AuthenticationViewModel extends ChangeNotifier {
}
void onMaritalStatusChange(String? status) {
maritalStatus = MaritalStatusTypeExtension.fromType(status)!;
clearMaritalStatusError();
notifyListeners();
final newMaritalStatus = MaritalStatusTypeExtension.fromType(status);
if (newMaritalStatus != null) {
maritalStatus = newMaritalStatus;
clearMaritalStatusError();
notifyListeners();
}
}
void onGenderChange(String? status) {
genderType = GenderTypeExtension.fromType(status)!;
clearGenderError();
notifyListeners();
final newGender = GenderTypeExtension.fromType(status);
if (newGender != null) {
genderType = newGender;
clearGenderError();
notifyListeners();
}
}
void clearEmailInput() {

@ -73,8 +73,11 @@ class BloodDonationViewModel extends ChangeNotifier {
}
void onGenderChange(String? status) {
selectedGender = GenderTypeExtension.fromType(status)!;
notifyListeners();
final newGender = GenderTypeExtension.fromType(status);
if (newGender != null) {
selectedGender = newGender;
notifyListeners();
}
}
setSelectedBloodGroup(BloodGroupListModel bloodGroup) {

@ -0,0 +1,365 @@
import 'dart:developer' as dev;
import 'package:flutter_test/flutter_test.dart';
import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_calendar_model.dart';
/// Comprehensive test to analyze date conversion issues between Gregorian and Hijri calendars
/// This test simulates the onDobChange method logic and tests 200 years of dates
void main() {
group('Date Conversion Analysis - 200 Years', () {
// Test results storage
final List<DateConversionResult> results = [];
final List<String> errors = [];
test('Test Gregorian to Hijri conversion for 200 years', () {
print('\n========================================');
print('GREGORIAN TO HIJRI CONVERSION TEST');
print('Testing dates from 1925 to 2125 (200 years)');
print('========================================\n');
int totalDates = 0;
int successfulConversions = 0;
int failedConversions = 0;
// Test dates from 1925 to 2125 (200 years)
for (int year = 1925; year <= 2125; year++) {
// Test all 12 months
for (int month = 1; month <= 12; month++) {
// Test key days in each month (1st, 15th, last day)
final daysToTest = [1, 15, _getLastDayOfMonth(year, month)];
for (int day in daysToTest) {
totalDates++;
try {
// Create Gregorian date
final gregorianDate = DateTime(year, month, day);
final isoString = gregorianDate.toIso8601String();
// Test parsing (this is where the error occurs)
final parsedDate = DateTime.parse(isoString);
// Test Hijri conversion
final hijriDate = HijriGregConverter.gregorianToHijri(parsedDate);
// Test creating DateTime from Hijri components
final hijriDateTime = DateTime(hijriDate.year, hijriDate.month, hijriDate.day);
// Test formatting methods
final apiFormat = _formatDateForApi(isoString);
final displayFormat = _formatDateToDisplay(isoString);
final hijriDisplayFormat = _formatHijriDateToDisplay(hijriDateTime.toIso8601String());
// Store successful result
results.add(DateConversionResult(
gregorianDate: gregorianDate,
isoString: isoString,
hijriYear: hijriDate.year,
hijriMonth: hijriDate.month,
hijriDay: hijriDate.day,
apiFormat: apiFormat,
displayFormat: displayFormat,
hijriDisplayFormat: hijriDisplayFormat,
success: true,
));
successfulConversions++;
} catch (e) {
failedConversions++;
final errorMsg = 'ERROR on $year-${month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}: $e';
errors.add(errorMsg);
print(errorMsg);
results.add(DateConversionResult(
gregorianDate: DateTime(year, month, day),
isoString: 'FAILED',
hijriYear: 0,
hijriMonth: 0,
hijriDay: 0,
apiFormat: 'FAILED',
displayFormat: 'FAILED',
hijriDisplayFormat: 'FAILED',
success: false,
errorMessage: e.toString(),
));
}
}
}
// Print progress every 10 years
if (year % 10 == 0) {
print('Processed up to year $year... (Success: $successfulConversions, Failed: $failedConversions)');
}
}
// Print summary
print('\n========================================');
print('TEST SUMMARY');
print('========================================');
print('Total dates tested: $totalDates');
print('Successful conversions: $successfulConversions');
print('Failed conversions: $failedConversions');
print('Success rate: ${(successfulConversions / totalDates * 100).toStringAsFixed(2)}%');
print('========================================\n');
// Print all errors
if (errors.isNotEmpty) {
print('\n========================================');
print('ALL ERRORS (${errors.length} total):');
print('========================================');
for (var error in errors) {
print(error);
}
}
// Print sample successful conversions
if (results.where((r) => r.success).isNotEmpty) {
print('\n========================================');
print('SAMPLE SUCCESSFUL CONVERSIONS (First 5):');
print('========================================');
int count = 0;
for (var result in results.where((r) => r.success)) {
if (count >= 5) break;
print('Gregorian: ${result.gregorianDate}');
print(' ISO: ${result.isoString}');
print(' Hijri: ${result.hijriYear}-${result.hijriMonth}-${result.hijriDay}');
print(' API Format: ${result.apiFormat}');
print(' Display Format: ${result.displayFormat}');
print(' Hijri Display: ${result.hijriDisplayFormat}');
print('---');
count++;
}
}
// Test should fail if there are any errors
expect(failedConversions, 0, reason: 'Found $failedConversions failed conversions');
});
test('Test specific edge cases', () {
print('\n========================================');
print('EDGE CASES TEST');
print('========================================\n');
final edgeCases = [
DateTime(1900, 1, 1), // Very old date
DateTime(1970, 1, 1), // Unix epoch
DateTime(2000, 1, 1), // Y2K
DateTime(2000, 2, 29), // Leap year
DateTime(2001, 2, 28), // Non-leap year
DateTime(2024, 12, 31), // Recent date
DateTime(2025, 1, 1), // Near future
DateTime(2100, 12, 31), // Far future
DateTime(2124, 2, 29), // Leap year in far future
DateTime(2125, 12, 31), // End of test range
];
for (var testDate in edgeCases) {
try {
final isoString = testDate.toIso8601String();
final parsedDate = DateTime.parse(isoString);
final hijriDate = HijriGregConverter.gregorianToHijri(parsedDate);
print('${testDate.toString().split(' ')[0]} -> Hijri: ${hijriDate.year}-${hijriDate.month}-${hijriDate.day}');
} catch (e) {
print('${testDate.toString().split(' ')[0]} -> ERROR: $e');
fail('Edge case failed: $testDate - $e');
}
}
});
test('Test DateRangCalenderModel with Umm al-Qura offset', () {
print('\n========================================');
print('UMM AL-QURA OFFSET TEST');
print('========================================\n');
// We can't fully test DateRangCalenderModel without full context,
// but we can test the conversion logic
final testDates = [
DateTime(2024, 1, 1),
DateTime(2024, 6, 15),
DateTime(2024, 12, 31),
DateTime(2025, 1, 1),
];
for (var testDate in testDates) {
try {
// Simulate the Umm al-Qura offset (-1 day)
final adjustedDate = testDate.subtract(const Duration(days: 1));
final hijriDate = HijriGregConverter.gregorianToHijri(adjustedDate);
print('Original: ${testDate.toString().split(' ')[0]}');
print(' Adjusted (-1 day): ${adjustedDate.toString().split(' ')[0]}');
print(' Hijri: ${hijriDate.year}-${hijriDate.month}-${hijriDate.day}');
print('---');
} catch (e) {
print('ERROR on ${testDate.toString().split(' ')[0]}: $e');
fail('Umm al-Qura offset test failed: $testDate - $e');
}
}
});
test('Test problematic date formats', () {
print('\n========================================');
print('PROBLEMATIC DATE FORMATS TEST');
print('========================================\n');
final problematicFormats = [
'2024-01-01T00:00:00.000',
'2024-01-01T00:00:00.000Z',
'2024-01-01T00:00:00',
'2024-01-01',
'2024-1-1',
'2024-01-1',
'2024-1-01',
];
for (var format in problematicFormats) {
try {
final parsed = DateTime.parse(format);
print('✓ Format "$format" parsed successfully: ${parsed.toString()}');
} catch (e) {
print('✗ Format "$format" FAILED to parse: $e');
}
}
});
});
}
// Helper function to get last day of month
int _getLastDayOfMonth(int year, int month) {
if (month == 12) {
return DateTime(year + 1, 1, 1).subtract(const Duration(days: 1)).day;
}
return DateTime(year, month + 1, 1).subtract(const Duration(days: 1)).day;
}
// Replicate Utils methods for testing
String _formatDateForApi(String isoDateString) {
try {
final dateTime = DateTime.parse(isoDateString);
final year = dateTime.year.toString();
final month = dateTime.month.toString().padLeft(2, '0');
final day = dateTime.day.toString().padLeft(2, '0');
return '$day/$month/$year';
} catch (e) {
return 'ERROR: $e';
}
}
String _formatDateToDisplay(String isoDateString) {
try {
final dateTime = DateTime.parse(isoDateString);
final day = dateTime.day.toString().padLeft(2, '0');
final year = dateTime.year.toString();
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
final month = monthNames[dateTime.month - 1];
return '$day $month, $year';
} catch (e) {
return 'ERROR: $e';
}
}
String _formatHijriDateToDisplay(String hijriDateString) {
try {
final datePart = hijriDateString.split("T").first;
final parts = datePart.split('-');
if (parts.length != 3) return 'ERROR: Invalid format';
final day = parts[2].padLeft(2, '0');
final year = parts[0];
const hijriMonthNames = [
'Muharram',
'Safar',
'Rabi I',
'Rabi II',
'Jumada I',
'Jumada II',
'Rajab',
'Sha\'ban',
'Ramadan',
'Shawwal',
'Dhu al-Qi\'dah',
'Dhu al-Hijjah'
];
final monthIndex = int.tryParse(parts[1]) ?? 1;
final month = hijriMonthNames[monthIndex - 1];
return '$day $month, $year';
} catch (e) {
return 'ERROR: $e';
}
}
// Stub for HijriGregConverter (since we're testing the logic)
class HijriGregConverter {
static HijriGregDate gregorianToHijri(DateTime gregorianDate) {
// This is a simplified conversion for testing
// In reality, this would use the actual Hijri calendar library
// Basic astronomical calculation (not exact Umm al-Qura)
final julianDay = _gregorianToJulianDay(gregorianDate);
final hijriDate = _julianDayToHijri(julianDay);
return hijriDate;
}
static int _gregorianToJulianDay(DateTime date) {
int a = (14 - date.month) ~/ 12;
int y = date.year + 4800 - a;
int m = date.month + 12 * a - 3;
return date.day + (153 * m + 2) ~/ 5 + 365 * y + y ~/ 4 - y ~/ 100 + y ~/ 400 - 32045;
}
static HijriGregDate _julianDayToHijri(int julianDay) {
// Basic conversion (simplified for testing)
int l = julianDay - 1948440 + 10632;
int n = (l - 1) ~/ 10631;
l = l - 10631 * n + 354;
int j = ((10985 - l) ~/ 5316) * ((50 * l) ~/ 17719) + (l ~/ 5670) * ((43 * l) ~/ 15238);
l = l - ((30 - j) ~/ 15) * ((17719 * j) ~/ 50) - (j ~/ 16) * ((15238 * j) ~/ 43) + 29;
int month = (24 * l) ~/ 709;
int day = l - (709 * month) ~/ 24;
int year = 30 * n + j - 30;
return HijriGregDate(year: year, month: month, day: day);
}
}
class HijriGregDate {
final int year;
final int month;
final int day;
HijriGregDate({required this.year, required this.month, required this.day});
}
// Data class to store test results
class DateConversionResult {
final DateTime gregorianDate;
final String isoString;
final int hijriYear;
final int hijriMonth;
final int hijriDay;
final String apiFormat;
final String displayFormat;
final String hijriDisplayFormat;
final bool success;
final String? errorMessage;
DateConversionResult({
required this.gregorianDate,
required this.isoString,
required this.hijriYear,
required this.hijriMonth,
required this.hijriDay,
required this.apiFormat,
required this.displayFormat,
required this.hijriDisplayFormat,
required this.success,
this.errorMessage,
});
}
Loading…
Cancel
Save