Skip to content

Commit 6d6d791

Browse files
Added calendar delegate to support custom calendar systems (#161874)
Added `CalendarDelegate` class that supports plugging in custom calendar logics other than Gregorian Calendar System. Here is an example implementation for Nepali(Bikram Sambat) Calendar System: https://github.com/sarbagyastha/nepali_date_picker/blob/m3/lib/src/nepali_calendar_delegate.dart Demo using the `NepaliDatePickerDelegate`: https://date.sarbagyastha.com.np/ Fixes flutter/flutter#77531, flutter/flutter#161873 ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --------- Co-authored-by: Tong Mu <dkwingsmt@users.noreply.github.com>
1 parent 83781ae commit 6d6d791

File tree

10 files changed

+1095
-108
lines changed

10 files changed

+1095
-108
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
7+
/// Flutter code sample demonstrating how to use a custom [CalendarDelegate]
8+
/// with [CalendarDatePicker] to implement a hypothetical calendar system
9+
/// where even-numbered months have 21 days, odd-numbered months have 28 days,
10+
/// and every month starts on a Monday.
11+
12+
void main() => runApp(const CalendarDatePickerApp());
13+
14+
class CalendarDatePickerApp extends StatelessWidget {
15+
const CalendarDatePickerApp({super.key});
16+
17+
@override
18+
Widget build(BuildContext context) {
19+
return const MaterialApp(home: CalendarDatePickerExample());
20+
}
21+
}
22+
23+
class CalendarDatePickerExample extends StatefulWidget {
24+
const CalendarDatePickerExample({super.key});
25+
26+
@override
27+
State<CalendarDatePickerExample> createState() => _CalendarDatePickerExampleState();
28+
}
29+
30+
class _CalendarDatePickerExampleState extends State<CalendarDatePickerExample> {
31+
DateTime? selectedDate;
32+
33+
@override
34+
Widget build(BuildContext context) {
35+
return Scaffold(
36+
appBar: AppBar(title: const Text('Custom Calendar')),
37+
body: Column(
38+
spacing: 16,
39+
children: <Widget>[
40+
CalendarDatePicker(
41+
initialDate: DateTime(2025, 2, 8),
42+
firstDate: DateTime(2025),
43+
lastDate: DateTime(2026),
44+
onDateChanged: (DateTime pickedDate) {
45+
setState(() {
46+
selectedDate = pickedDate;
47+
});
48+
},
49+
calendarDelegate: const CustomCalendarDelegate(),
50+
),
51+
const Divider(height: 1),
52+
Text(
53+
selectedDate != null
54+
? '${selectedDate!.day}/${selectedDate!.month}/${selectedDate!.year}'
55+
: 'No date selected',
56+
),
57+
],
58+
),
59+
);
60+
}
61+
}
62+
63+
/// A custom calendar system where even-numbered months have 21 days,
64+
/// odd-numbered months have 28 days, and every month starts on a Monday.
65+
///
66+
/// This hypothetical calendar follows a fixed structure:
67+
/// - **Even-numbered months (2, 4, 6, etc.)** always have **21 days**.
68+
/// - **Odd-numbered months (1, 3, 5, etc.)** always have **28 days**.
69+
/// - **The first day of every month is always a Monday**, ensuring a consistent weekly alignment.
70+
class CustomCalendarDelegate extends CalendarDelegate<DateTime> {
71+
const CustomCalendarDelegate();
72+
73+
@override
74+
int getDaysInMonth(int year, int month) {
75+
return month.isEven ? 21 : 28;
76+
}
77+
78+
@override
79+
int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
80+
return 1;
81+
}
82+
83+
// ------------------------------------------------------------------------
84+
// All the implementations below are based on the Gregorian calendar system.
85+
86+
@override
87+
DateTime now() => DateTime.now();
88+
89+
@override
90+
DateTime dateOnly(DateTime date) => DateUtils.dateOnly(date);
91+
92+
@override
93+
int monthDelta(DateTime startDate, DateTime endDate) => DateUtils.monthDelta(startDate, endDate);
94+
95+
@override
96+
DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
97+
return DateUtils.addMonthsToMonthDate(monthDate, monthsToAdd);
98+
}
99+
100+
@override
101+
DateTime addDaysToDate(DateTime date, int days) => DateUtils.addDaysToDate(date, days);
102+
103+
@override
104+
DateTime getMonth(int year, int month) => DateTime(year, month);
105+
106+
@override
107+
DateTime getDay(int year, int month, int day) => DateTime(year, month, day);
108+
109+
@override
110+
String formatMonthYear(DateTime date, MaterialLocalizations localizations) {
111+
return localizations.formatMonthYear(date);
112+
}
113+
114+
@override
115+
String formatMediumDate(DateTime date, MaterialLocalizations localizations) {
116+
return localizations.formatMediumDate(date);
117+
}
118+
119+
@override
120+
String formatShortMonthDay(DateTime date, MaterialLocalizations localizations) {
121+
return localizations.formatShortMonthDay(date);
122+
}
123+
124+
@override
125+
String formatShortDate(DateTime date, MaterialLocalizations localizations) {
126+
return localizations.formatShortDate(date);
127+
}
128+
129+
@override
130+
String formatFullDate(DateTime date, MaterialLocalizations localizations) {
131+
return localizations.formatFullDate(date);
132+
}
133+
134+
@override
135+
String formatCompactDate(DateTime date, MaterialLocalizations localizations) {
136+
return localizations.formatCompactDate(date);
137+
}
138+
139+
@override
140+
DateTime? parseCompactDate(String? inputString, MaterialLocalizations localizations) {
141+
return localizations.parseCompactDate(inputString);
142+
}
143+
144+
@override
145+
String dateHelpText(MaterialLocalizations localizations) {
146+
return localizations.dateHelpText;
147+
}
148+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/material.dart';
6+
import 'package:flutter_api_samples/material/date_picker/custom_calendar_date_picker.0.dart'
7+
as example;
8+
import 'package:flutter_test/flutter_test.dart';
9+
10+
void main() {
11+
Text getLastDayText(WidgetTester tester) {
12+
final Finder dayFinder = find.descendant(of: find.byType(Ink), matching: find.byType(Text));
13+
return tester.widget(dayFinder.last);
14+
}
15+
16+
testWidgets('Days are based on the calendar delegate', (WidgetTester tester) async {
17+
await tester.pumpWidget(const example.CalendarDatePickerApp());
18+
19+
final Finder nextMonthButton = find.byIcon(Icons.chevron_right);
20+
21+
Text lastDayText = getLastDayText(tester);
22+
expect(find.text('February 2025'), findsOneWidget);
23+
expect(lastDayText.data, equals('21'));
24+
25+
await tester.tap(nextMonthButton);
26+
await tester.pumpAndSettle();
27+
28+
lastDayText = getLastDayText(tester);
29+
expect(find.text('March 2025'), findsOneWidget);
30+
expect(lastDayText.data, equals('28'));
31+
32+
await tester.tap(nextMonthButton);
33+
await tester.pumpAndSettle();
34+
35+
lastDayText = getLastDayText(tester);
36+
expect(find.text('April 2025'), findsOneWidget);
37+
expect(lastDayText.data, equals('21'));
38+
39+
await tester.tap(nextMonthButton);
40+
await tester.pumpAndSettle();
41+
42+
lastDayText = getLastDayText(tester);
43+
expect(lastDayText.data, equals('28'));
44+
});
45+
}

0 commit comments

Comments
 (0)