blob: 6ee56e331db903f3938cf80f886d5a0e3a5186db [file] [log] [blame] [edit]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'calendar_date_picker.dart';
/// @docImport 'date_picker.dart';
/// @docImport 'text_field.dart';
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'material_localizations.dart';
/// Controls the calendar system used in the date picker.
///
/// A [CalendarDelegate] defines how dates are interpreted, formatted, and
/// navigated within the picker. Different calendar systems (e.g., Gregorian,
/// Nepali, Hijri, Buddhist) can be supported by providing custom implementations.
///
/// {@tool dartpad}
/// This example demonstrates how a [CalendarDelegate] is used to implement a
/// custom calendar system in the date picker.
///
/// ** See code in examples/api/lib/material/date_picker/custom_calendar_date_picker.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [GregorianCalendarDelegate], the default implementation for the Gregorian calendar.
/// * [CalendarDatePicker], which uses this delegate to manage calendar-specific behavior.
abstract class CalendarDelegate<T extends DateTime> {
/// Creates a calendar delegate.
const CalendarDelegate();
/// Returns a [DateTime] representing the current date and time.
T now();
/// {@macro flutter.material.date.dateOnly}
T dateOnly(T date);
/// {@macro flutter.material.date.datesOnly}
DateTimeRange<T> datesOnly(DateTimeRange<T> range) {
return DateTimeRange<T>(start: dateOnly(range.start), end: dateOnly(range.end));
}
/// {@macro flutter.material.date.isSameDay}
bool isSameDay(T? dateA, T? dateB) {
return dateA?.year == dateB?.year && dateA?.month == dateB?.month && dateA?.day == dateB?.day;
}
/// {@macro flutter.material.date.isSameMonth}
bool isSameMonth(T? dateA, T? dateB) {
return dateA?.year == dateB?.year && dateA?.month == dateB?.month;
}
/// {@macro flutter.material.date.monthDelta}
int monthDelta(T startDate, T endDate);
/// {@macro flutter.material.date.addMonthsToMonthDate}
T addMonthsToMonthDate(T monthDate, int monthsToAdd);
/// {@macro flutter.material.date.addDaysToDate}
T addDaysToDate(T date, int days);
/// {@macro flutter.material.date.firstDayOffset}
int firstDayOffset(int year, int month, MaterialLocalizations localizations);
/// Returns the number of days in a month, according to the calendar system.
int getDaysInMonth(int year, int month);
/// Returns a [DateTime] with the given [year] and [month].
T getMonth(int year, int month);
/// Returns a [DateTime] with the given [year], [month], and [day].
T getDay(int year, int month, int day);
/// Formats the month and the year of the given [date].
///
/// The returned string does not contain the day of the month. This appears
/// in the date picker invoked using [showDatePicker].
String formatMonthYear(T date, MaterialLocalizations localizations);
/// Full unabbreviated year format, e.g. 2017 rather than 17.
String formatYear(int year, MaterialLocalizations localizations) {
return localizations.formatYear(DateTime(year));
}
/// Formats the date using a medium-width format.
///
/// Abbreviates month and days of week. This appears in the header of the date
/// picker invoked using [showDatePicker].
///
/// Examples:
///
/// - US English: Wed, Sep 27
/// - Russian: ср, сент. 27
String formatMediumDate(T date, MaterialLocalizations localizations);
/// Formats the month and day of the given [date].
///
/// Examples:
///
/// - US English: Feb 21
/// - Russian: 21 февр.
String formatShortMonthDay(T date, MaterialLocalizations localizations);
/// Formats the date using a short-width format.
///
/// Includes the abbreviation of the month, the day and year.
///
/// Examples:
///
/// - US English: Feb 21, 2019
/// - Russian: 21 февр. 2019 г.
String formatShortDate(T date, MaterialLocalizations localizations);
/// Formats day of week, month, day of month and year in a long-width format.
///
/// Does not abbreviate names. Appears in spoken announcements of the date
/// picker invoked using [showDatePicker], when accessibility mode is on.
///
/// Examples:
///
/// - US English: Wednesday, September 27, 2017
/// - Russian: Среда, Сентябрь 27, 2017
String formatFullDate(T date, MaterialLocalizations localizations);
/// Formats the date in a compact format.
///
/// Usually just the numeric values for the for day, month and year are used.
///
/// Examples:
///
/// - US English: 02/21/2019
/// - Russian: 21.02.2019
///
/// See also:
/// * [parseCompactDate], which will convert a compact date string to a [DateTime].
String formatCompactDate(T date, MaterialLocalizations localizations);
/// Converts the given compact date formatted string into a [DateTime].
///
/// The format of the string must be a valid compact date format for the
/// given locale. If the text doesn't represent a valid date, `null` will be
/// returned.
///
/// See also:
/// * [formatCompactDate], which will convert a [DateTime] into a string in the compact format.
T? parseCompactDate(String? inputString, MaterialLocalizations localizations);
/// The help text used on an empty [InputDatePickerFormField] to indicate
/// to the user the date format being asked for.
String dateHelpText(MaterialLocalizations localizations);
}
/// A [CalendarDelegate] implementation for the Gregorian calendar system.
///
/// The Gregorian calendar is the most widely used civil calendar worldwide.
/// This delegate provides standard date interpretation, formatting, and
/// navigation based on the Gregorian system.
///
/// This delegate is the default calendar system for [CalendarDatePicker].
///
/// See also:
/// * [CalendarDelegate], the base class for defining custom calendars.
/// * [CalendarDatePicker], which uses this delegate for date selection.
class GregorianCalendarDelegate extends CalendarDelegate<DateTime> {
/// Creates a calendar delegate that uses the Gregorian calendar and the
/// conventions of the current [MaterialLocalizations].
const GregorianCalendarDelegate();
@override
DateTime now() => DateTime.now();
@override
DateTime dateOnly(DateTime date) => DateUtils.dateOnly(date);
@override
int monthDelta(DateTime startDate, DateTime endDate) => DateUtils.monthDelta(startDate, endDate);
@override
DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
return DateUtils.addMonthsToMonthDate(monthDate, monthsToAdd);
}
@override
DateTime addDaysToDate(DateTime date, int days) => DateUtils.addDaysToDate(date, days);
@override
int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
return DateUtils.firstDayOffset(year, month, localizations);
}
/// {@macro flutter.material.date.getDaysInMonth}
@override
int getDaysInMonth(int year, int month) => DateUtils.getDaysInMonth(year, month);
@override
DateTime getMonth(int year, int month) => DateTime(year, month);
@override
DateTime getDay(int year, int month, int day) => DateTime(year, month, day);
@override
String formatMonthYear(DateTime date, MaterialLocalizations localizations) {
return localizations.formatMonthYear(date);
}
@override
String formatMediumDate(DateTime date, MaterialLocalizations localizations) {
return localizations.formatMediumDate(date);
}
@override
String formatShortMonthDay(DateTime date, MaterialLocalizations localizations) {
return localizations.formatShortMonthDay(date);
}
@override
String formatShortDate(DateTime date, MaterialLocalizations localizations) {
return localizations.formatShortDate(date);
}
@override
String formatFullDate(DateTime date, MaterialLocalizations localizations) {
return localizations.formatFullDate(date);
}
@override
String formatCompactDate(DateTime date, MaterialLocalizations localizations) {
return localizations.formatCompactDate(date);
}
@override
DateTime? parseCompactDate(String? inputString, MaterialLocalizations localizations) {
return localizations.parseCompactDate(inputString);
}
@override
String dateHelpText(MaterialLocalizations localizations) {
return localizations.dateHelpText;
}
}
/// Utility functions for working with dates.
abstract final class DateUtils {
/// {@template flutter.material.date.dateOnly}
/// Returns a [DateTime] with the date of the original, but time set to
/// midnight.
/// {@endtemplate}
static DateTime dateOnly(DateTime date) {
return DateTime(date.year, date.month, date.day);
}
/// {@template flutter.material.date.datesOnly}
/// Returns a [DateTimeRange] with the dates of the original, but with times
/// set to midnight.
///
/// See also:
/// * [dateOnly], which does the same thing for a single date.
/// {@endtemplate}
static DateTimeRange datesOnly(DateTimeRange range) {
return DateTimeRange(start: dateOnly(range.start), end: dateOnly(range.end));
}
/// {@template flutter.material.date.isSameDay}
/// Returns true if the two [DateTime] objects have the same day, month, and
/// year, or are both null.
/// {@endtemplate}
static bool isSameDay(DateTime? dateA, DateTime? dateB) {
return dateA?.year == dateB?.year && dateA?.month == dateB?.month && dateA?.day == dateB?.day;
}
/// {@template flutter.material.date.isSameMonth}
/// Returns true if the two [DateTime] objects have the same month and
/// year, or are both null.
/// {@endtemplate}
static bool isSameMonth(DateTime? dateA, DateTime? dateB) {
return dateA?.year == dateB?.year && dateA?.month == dateB?.month;
}
/// {@template flutter.material.date.monthDelta}
/// Determines the number of months between two [DateTime] objects.
///
/// For example:
///
/// ```dart
/// DateTime date1 = DateTime(2019, 6, 15);
/// DateTime date2 = DateTime(2020, 1, 15);
/// int delta = DateUtils.monthDelta(date1, date2);
/// ```
///
/// The value for `delta` would be `7`.
/// {@endtemplate}
static int monthDelta(DateTime startDate, DateTime endDate) {
return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
}
/// {@template flutter.material.date.addMonthsToMonthDate}
/// Returns a [DateTime] that is [monthDate] with the added number
/// of months and the day set to 1 and time set to midnight.
///
/// For example:
///
/// ```dart
/// DateTime date = DateTime(2019, 1, 15);
/// DateTime futureDate = DateUtils.addMonthsToMonthDate(date, 3);
/// ```
///
/// `date` would be January 15, 2019.
/// `futureDate` would be April 1, 2019 since it adds 3 months.
/// {@endtemplate}
static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) {
return DateTime(monthDate.year, monthDate.month + monthsToAdd);
}
/// {@template flutter.material.date.addDaysToDate}
/// Returns a [DateTime] with the added number of days and time set to
/// midnight.
/// {@endtemplate}
static DateTime addDaysToDate(DateTime date, int days) {
return DateTime(date.year, date.month, date.day + days);
}
/// {@template flutter.material.date.firstDayOffset}
/// Computes the offset from the first day of the week that the first day of
/// the [month] falls on.
///
/// For example, September 1, 2017 falls on a Friday, which in the calendar
/// localized for United States English appears as:
///
/// S M T W T F S
/// _ _ _ _ _ 1 2
///
/// The offset for the first day of the months is the number of leading blanks
/// in the calendar, i.e. 5.
///
/// The same date localized for the Russian calendar has a different offset,
/// because the first day of week is Monday rather than Sunday:
///
/// M T W T F S S
/// _ _ _ _ 1 2 3
///
/// So the offset is 4, rather than 5.
///
/// This code consolidates the following:
///
/// - [DateTime.weekday] provides a 1-based index into days of week, with 1
/// falling on Monday.
/// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index
/// into the [MaterialLocalizations.narrowWeekdays] list.
/// - [MaterialLocalizations.narrowWeekdays] list provides localized names of
/// days of week, always starting with Sunday and ending with Saturday.
/// {@endtemplate}
static int firstDayOffset(int year, int month, MaterialLocalizations localizations) {
// 0-based day of week for the month and year, with 0 representing Monday.
final int weekdayFromMonday = DateTime(year, month).weekday - 1;
// 0-based start of week depending on the locale, with 0 representing Sunday.
int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex;
// firstDayOfWeekIndex recomputed to be Monday-based, in order to compare with
// weekdayFromMonday.
firstDayOfWeekIndex = (firstDayOfWeekIndex - 1) % 7;
// Number of days between the first day of week appearing on the calendar,
// and the day corresponding to the first of the month.
return (weekdayFromMonday - firstDayOfWeekIndex) % 7;
}
/// {@template flutter.material.date.getDaysInMonth}
/// Returns the number of days in a month, according to the proleptic
/// Gregorian calendar.
///
/// This applies the leap year logic introduced by the Gregorian reforms of
/// 1582. It will not give valid results for dates prior to that time.
/// {@endtemplate}
static int getDaysInMonth(int year, int month) {
if (month == DateTime.february) {
final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
return isLeapYear ? 29 : 28;
}
const daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return daysInMonth[month - 1];
}
}
/// Mode of date entry method for the date picker dialog.
///
/// In [calendar] mode, a calendar grid is displayed and the user taps the
/// day they wish to select. In [input] mode, a [TextField] is displayed and
/// the user types in the date they wish to select.
///
/// [calendarOnly] and [inputOnly] are variants of the above that don't
/// allow the user to change to the mode.
///
/// See also:
///
/// * [showDatePicker] and [showDateRangePicker], which use this to control
/// the initial entry mode of their dialogs.
enum DatePickerEntryMode {
/// User picks a date from calendar grid. Can switch to [input] by activating
/// a mode button in the dialog.
calendar,
/// User can input the date by typing it into a text field.
///
/// Can switch to [calendar] by activating a mode button in the dialog.
input,
/// User can only pick a date from calendar grid.
///
/// There is no user interface to switch to another mode.
calendarOnly,
/// User can only input the date by typing it into a text field.
///
/// There is no user interface to switch to another mode.
inputOnly,
}
/// Initial display of a calendar date picker.
///
/// Either a grid of available years or a monthly calendar.
///
/// See also:
///
/// * [showDatePicker], which shows a dialog that contains a Material Design
/// date picker.
/// * [CalendarDatePicker], widget which implements the Material Design date picker.
enum DatePickerMode {
/// Choosing a month and day.
day,
/// Choosing a year.
year,
}
/// Encapsulates a start and end [DateTime] that represent the range of dates.
///
/// The range includes the [start] and [end] dates. The [start] and [end] dates
/// may be equal to indicate a date range of a single day. The [start] date must
/// not be after the [end] date.
///
/// See also:
/// * [showDateRangePicker], which displays a dialog that allows the user to
/// select a date range.
@immutable
@optionalTypeArgs
class DateTimeRange<T extends DateTime> {
/// Creates a date range for the given start and end [DateTime].
DateTimeRange({required this.start, required this.end}) : assert(!start.isAfter(end));
/// The start of the range of dates.
final T start;
/// The end of the range of dates.
final T end;
/// Returns a [Duration] of the time between [start] and [end].
///
/// See [DateTime.difference] for more details.
Duration get duration => end.difference(start);
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is DateTimeRange && other.start == start && other.end == end;
}
@override
int get hashCode => Object.hash(start, end);
@override
String toString() => '$start - $end';
}