Flutter: Date and Time Pickers (DatePicker & TimePicker)
Last updated:
6 min read

Asking users to type a birth date, an appointment slot or a reminder time by hand is both tedious and error-prone: one person writes "3/5/2026", the next writes "05.03.26". Flutter's built-in showDatePicker and showTimePicker functions solve this. They open a Material calendar or clock dialog and hand us a DateTime or a TimeOfDay object directly. Both are asynchronous, so we wait for the result with await.
Live Demo
You can try these widgets in the interactive example below:
💡 If the example above doesn't load, click DartPad to run it in a new tab.
DatePicker
Opens a calendar interface where the user picks a day, a month and a year.
Basic Usage
The function returns Future<DateTime?>. If the user taps "Cancel" or touches outside the dialog, the result is null:
DateTime? selectedDate;
Future<void> _selectDate() async {
final now = DateTime.now();
final DateTime? picked = await showDatePicker(
context: context,
initialDate: selectedDate ?? now, // Date highlighted when the calendar opens
firstDate: DateTime(2000), // Earliest selectable date
lastDate: DateTime(now.year + 5), // Latest selectable date
);
if (picked != null && picked != selectedDate) {
setState(() {
selectedDate = picked;
});
}
}Notice that lastDate is computed relative to today (now.year + 5) instead of a hard-coded year; I will explain why in the mistakes section. If await is new to you, the async/await post is a good place to start.
To open it from a button:
ElevatedButton(
onPressed: _selectDate,
child: Text(
selectedDate == null
? 'Pick a date'
: MaterialLocalizations.of(context).formatShortDate(selectedDate!),
),
)Instead of gluing the date together by hand with "${d.day}/${d.month}/${d.year}", MaterialLocalizations gives you a format that matches the app's locale without any extra package. When you need more freedom, DateFormat from the intl package takes over.
Old Dates Such as a Birth Date
In the default calendar view, going back thirty years means dozens of taps. Two parameters fix this:
showDatePicker(
context: context,
initialDate: DateTime(2000),
firstDate: DateTime(1920),
lastDate: DateTime.now(),
initialDatePickerMode: DatePickerMode.year, // Open with the year list first
initialEntryMode: DatePickerEntryMode.calendar, // .input lets the user type it instead
);TimePicker
Lets the user pick an hour and a minute.
Basic Usage
This function returns Future<TimeOfDay?>.
TimeOfDay selectedTime = TimeOfDay.now();
Future<void> _selectTime() async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: selectedTime,
builder: (context, child) {
return MediaQuery(
// Force the 24-hour format (optional)
data: MediaQuery.of(context).copyWith(alwaysUse24HourFormat: true),
child: child!,
);
},
);
if (picked != null && picked != selectedTime) {
setState(() {
selectedTime = picked;
});
}
}TimeOfDay carries only an hour and a minute; there is no date in it. To display it, selectedTime.format(context) is enough. Combining a date and a time into a single DateTime is covered in the scenario below.
Localization
To show the calendar in the user's language, first add Flutter's own localization package to pubspec.yaml:
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutterThen give MaterialApp the delegates and the supported locales:
import 'package:flutter_localizations/flutter_localizations.dart';
MaterialApp(
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en'), // English
Locale('de'), // German
],
// ...
)Once this is in place, showDatePicker picks day and month names, button captions and the first day of the week based on the device language (or on whatever you pass in the locale parameter).
Theme Customization
To recolor the picker, wrap the dialog in a Theme through the builder parameter:
showDatePicker(
context: context,
// ... parameters
builder: (context, child) {
final theme = Theme.of(context);
return Theme(
data: theme.copyWith(
colorScheme: theme.colorScheme.copyWith(
primary: Colors.red, // Selected day and header color
onPrimary: Colors.white, // Text color on the selected day
),
),
child: child!,
);
},
);Adjusting the existing colorScheme with copyWith is safer than building a fresh ColorScheme.light(...); that way the calendar does not suddenly turn white in dark mode. If the whole app should share the same look, use the datePickerTheme and timePickerTheme fields of ThemeData instead.
When to Use It and When Not To
- Use it to pick a single day or a single time, in situations where the user decides by looking at a calendar (appointments, delivery dates).
- Don't use it for a start–end range. Instead of opening two pickers one after the other,
showDateRangePickerselects a range in a single dialog. - Don't use it when the calendar should be part of the page. For an embedded calendar, place the
CalendarDatePickerwidget directly in the layout instead of a dialog. - Don't use it when an iOS-native look matters. The wheel-style
CupertinoDatePickeris the right tool there; it is usually shown inside a bottom sheet. - Don't use it for month/year input such as a credit card expiry date. A formatted text field or two short dropdowns is faster than a calendar.
If the selected date has to appear in a form, opening the picker from the onTap of a TextFormField with readOnly: true is a common and practical pattern; the field then takes part in Form validation like any other input.
Common Mistakes
1. initialDate outside the allowed range
Symptom: the picker never opens; the console shows an assertion such as "initialDate … must be on or before lastDate …" (or "on or after firstDate").
Cause: usually a hard-coded year. Code with lastDate: DateTime(2025) works fine that year, and the moment the calendar rolls over to 2026, initialDate: DateTime.now() falls outside the range and the app breaks one morning on its own.
Fix: derive the bounds from DateTime.now() and, before using a stored date as initialDate, make sure it is inside the range.
2. Using context after an await
Symptom: when asking for a date and then immediately for a time, the second call triggers the use_build_context_synchronously lint ("Don't use 'BuildContext's across async gaps").
Cause: the user may have left the page while the calendar was open; by the time await returns, the context you hold may no longer be valid. The same applies to AlertDialog, because pickers are dialogs under the hood.
Fix: check after every await:
final date = await showDatePicker(/* ... */);
if (date == null || !context.mounted) return;
final time = await showTimePicker(context: context, initialTime: TimeOfDay.now());3. The calendar stays in English
Symptom: the app is translated, but the calendar still shows "MON, TUE" and "CANCEL" in English.
Fix: add the flutter_localizations package and define the delegates plus the supportedLocales list shown above. Passing only locale: const Locale('de') without the delegates has no effect.
4. The date arrives one day early on the server
Symptom: the user picks May 15; the database shows May 14.
Cause: showDatePicker returns midnight in local time. If you send that with picked.toUtc().toIso8601String(), then in any time zone ahead of UTC, local midnight is still the previous evening in UTC.
Fix: send time-independent dates such as birthdays as a date only:
String toDateOnly(DateTime d) =>
'${d.year}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';Mini Scenario: Weekday Appointment Booking
Think of an appointment screen with these rules: the slot must be within the next 30 days and on a weekday; after the day, the user picks a time, and both are merged into a single DateTime.
DateTime? _appointment;
DateTime _nextWeekday(DateTime day) {
var d = DateUtils.dateOnly(day);
while (d.weekday > DateTime.friday) {
d = DateUtils.addDaysToDate(d, 1);
}
return d;
}
Future<void> _pickAppointment() async {
final today = DateUtils.dateOnly(DateTime.now());
final date = await showDatePicker(
context: context,
initialDate: _nextWeekday(today),
firstDate: today,
lastDate: DateUtils.addDaysToDate(today, 30),
selectableDayPredicate: (day) => day.weekday <= DateTime.friday,
helpText: 'Appointment day',
);
if (date == null || !mounted) return;
final time = await showTimePicker(
context: context,
initialTime: const TimeOfDay(hour: 10, minute: 0),
helpText: 'Appointment time',
);
if (time == null || !mounted) return;
setState(() {
_appointment = DateTime(
date.year, date.month, date.day, time.hour, time.minute,
);
});
}selectableDayPredicate greys out weekends and makes them untappable. There is a detail that is easy to miss: initialDate must satisfy the same rule. If today is Saturday and we pass initialDate: today, the picker fails with an assertion instead of opening. That is why _nextWeekday exists; it moves the start date to the first allowed day. Using DateUtils.addDaysToDate instead of adding a Duration(days: 1) also keeps the time from drifting across daylight-saving changes.
If the user backs out at either step, the function returns early and nothing changes, so there is never a half-finished appointment. To show the result, MaterialLocalizations is again all you need:
final loc = MaterialLocalizations.of(context);
final label = _appointment == null
? 'Pick an appointment'
: '${loc.formatFullDate(_appointment!)} · '
'${loc.formatTimeOfDay(TimeOfDay.fromDateTime(_appointment!), alwaysUse24HourFormat: true)}';Frequently Asked Questions
How do I show showDatePicker in another language?
Add flutter_localizations to pubspec.yaml, pass GlobalMaterialLocalizations.delegate and the other delegates to MaterialApp, and list the language in supportedLocales. When the device language matches, the calendar opens in that language automatically.
How do I combine a date and a time into one DateTime?
Call showDatePicker first and showTimePicker second, then merge the results with DateTime(date.year, date.month, date.day, time.hour, time.minute). Check context.mounted between the two awaits.
How do I make certain days unselectable?
Pass a function to the selectableDayPredicate parameter of showDatePicker that returns true or false for each day. The day you pass as initialDate must also return true from that function.
How do I open the time picker in 24-hour format?
In the builder parameter of showTimePicker, wrap child in a MediaQuery whose data has alwaysUse24HourFormat: true. Without this, the format follows the device settings.
Related Posts
Flutter: Light and Dark Themes with ThemeData
Building a Flutter theme with ThemeData: ColorScheme.fromSeed, light and dark themes, ThemeMode, reading with Theme.of, TextTheme, component themes, extensions.
Flutter: Using TextField and TextEditingController
Flutter TextField guide: TextEditingController and dispose, InputDecoration, keyboard types, inputFormatters, password fields, FocusNode and onChanged.
Flutter: Layering Widgets with Stack and Positioned
Flutter Stack and Positioned guide: how a Stack sizes itself, fit, alignment, clipBehavior, Positioned.fill, PositionedDirectional, badge and overlay recipes.