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 | /// @docImport 'calendar_date_picker.dart'; |
6 | /// @docImport 'date_picker.dart'; |
7 | /// @docImport 'text_field.dart'; |
8 | library; |
9 | |
10 | import 'package:flutter/foundation.dart'; |
11 | import 'package:flutter/widgets.dart'; |
12 | |
13 | import 'material_localizations.dart'; |
14 | |
15 | /// Controls the calendar system used in the date picker. |
16 | /// |
17 | /// A [CalendarDelegate] defines how dates are interpreted, formatted, and |
18 | /// navigated within the picker. Different calendar systems (e.g., Gregorian, |
19 | /// Nepali, Hijri, Buddhist) can be supported by providing custom implementations. |
20 | /// |
21 | /// {@tool dartpad} |
22 | /// This example demonstrates how a [CalendarDelegate] is used to implement a |
23 | /// custom calendar system in the date picker. |
24 | /// |
25 | /// ** See code in examples/api/lib/material/date_picker/custom_calendar_date_picker.0.dart ** |
26 | /// {@end-tool} |
27 | /// |
28 | /// See also: |
29 | /// |
30 | /// * [GregorianCalendarDelegate], the default implementation for the Gregorian calendar. |
31 | /// * [CalendarDatePicker], which uses this delegate to manage calendar-specific behavior. |
32 | abstract class CalendarDelegate<T extends DateTime> { |
33 | /// Creates a calendar delegate. |
34 | const CalendarDelegate(); |
35 | |
36 | /// Returns a [DateTime] representing the current date and time. |
37 | T now(); |
38 | |
39 | /// {@macro flutter.material.date.dateOnly} |
40 | T dateOnly(T date); |
41 | |
42 | /// {@macro flutter.material.date.datesOnly} |
43 | DateTimeRange<T> datesOnly(DateTimeRange<T> range) { |
44 | return DateTimeRange<T>(start: dateOnly(range.start), end: dateOnly(range.end)); |
45 | } |
46 | |
47 | /// {@macro flutter.material.date.isSameDay} |
48 | bool isSameDay(T? dateA, T? dateB) { |
49 | return dateA?.year == dateB?.year && dateA?.month == dateB?.month && dateA?.day == dateB?.day; |
50 | } |
51 | |
52 | /// {@macro flutter.material.date.isSameMonth} |
53 | bool isSameMonth(T? dateA, T? dateB) { |
54 | return dateA?.year == dateB?.year && dateA?.month == dateB?.month; |
55 | } |
56 | |
57 | /// {@macro flutter.material.date.monthDelta} |
58 | int monthDelta(T startDate, T endDate); |
59 | |
60 | /// {@macro flutter.material.date.addMonthsToMonthDate} |
61 | T addMonthsToMonthDate(T monthDate, int monthsToAdd); |
62 | |
63 | /// {@macro flutter.material.date.addDaysToDate} |
64 | T addDaysToDate(T date, int days); |
65 | |
66 | /// {@macro flutter.material.date.firstDayOffset} |
67 | int firstDayOffset(int year, int month, MaterialLocalizations localizations); |
68 | |
69 | /// Returns the number of days in a month, according to the calendar system. |
70 | int getDaysInMonth(int year, int month); |
71 | |
72 | /// Returns a [DateTime] with the given [year] and [month]. |
73 | T getMonth(int year, int month); |
74 | |
75 | /// Returns a [DateTime] with the given [year], [month], and [day]. |
76 | T getDay(int year, int month, int day); |
77 | |
78 | /// Formats the month and the year of the given [date]. |
79 | /// |
80 | /// The returned string does not contain the day of the month. This appears |
81 | /// in the date picker invoked using [showDatePicker]. |
82 | String formatMonthYear(T date, MaterialLocalizations localizations); |
83 | |
84 | /// Full unabbreviated year format, e.g. 2017 rather than 17. |
85 | String formatYear(int year, MaterialLocalizations localizations) { |
86 | return localizations.formatYear(DateTime(year)); |
87 | } |
88 | |
89 | /// Formats the date using a medium-width format. |
90 | /// |
91 | /// Abbreviates month and days of week. This appears in the header of the date |
92 | /// picker invoked using [showDatePicker]. |
93 | /// |
94 | /// Examples: |
95 | /// |
96 | /// - US English: Wed, Sep 27 |
97 | /// - Russian: ср, сент. 27 |
98 | String formatMediumDate(T date, MaterialLocalizations localizations); |
99 | |
100 | /// Formats the month and day of the given [date]. |
101 | /// |
102 | /// Examples: |
103 | /// |
104 | /// - US English: Feb 21 |
105 | /// - Russian: 21 февр. |
106 | String formatShortMonthDay(T date, MaterialLocalizations localizations); |
107 | |
108 | /// Formats the date using a short-width format. |
109 | /// |
110 | /// Includes the abbreviation of the month, the day and year. |
111 | /// |
112 | /// Examples: |
113 | /// |
114 | /// - US English: Feb 21, 2019 |
115 | /// - Russian: 21 февр. 2019 г. |
116 | String formatShortDate(T date, MaterialLocalizations localizations); |
117 | |
118 | /// Formats day of week, month, day of month and year in a long-width format. |
119 | /// |
120 | /// Does not abbreviate names. Appears in spoken announcements of the date |
121 | /// picker invoked using [showDatePicker], when accessibility mode is on. |
122 | /// |
123 | /// Examples: |
124 | /// |
125 | /// - US English: Wednesday, September 27, 2017 |
126 | /// - Russian: Среда, Сентябрь 27, 2017 |
127 | String formatFullDate(T date, MaterialLocalizations localizations); |
128 | |
129 | /// Formats the date in a compact format. |
130 | /// |
131 | /// Usually just the numeric values for the for day, month and year are used. |
132 | /// |
133 | /// Examples: |
134 | /// |
135 | /// - US English: 02/21/2019 |
136 | /// - Russian: 21.02.2019 |
137 | /// |
138 | /// See also: |
139 | /// * [parseCompactDate], which will convert a compact date string to a [DateTime]. |
140 | String formatCompactDate(T date, MaterialLocalizations localizations); |
141 | |
142 | /// Converts the given compact date formatted string into a [DateTime]. |
143 | /// |
144 | /// The format of the string must be a valid compact date format for the |
145 | /// given locale. If the text doesn't represent a valid date, `null` will be |
146 | /// returned. |
147 | /// |
148 | /// See also: |
149 | /// * [formatCompactDate], which will convert a [DateTime] into a string in the compact format. |
150 | T? parseCompactDate(String? inputString, MaterialLocalizations localizations); |
151 | |
152 | /// The help text used on an empty [InputDatePickerFormField] to indicate |
153 | /// to the user the date format being asked for. |
154 | String dateHelpText(MaterialLocalizations localizations); |
155 | } |
156 | |
157 | /// A [CalendarDelegate] implementation for the Gregorian calendar system. |
158 | /// |
159 | /// The Gregorian calendar is the most widely used civil calendar worldwide. |
160 | /// This delegate provides standard date interpretation, formatting, and |
161 | /// navigation based on the Gregorian system. |
162 | /// |
163 | /// This delegate is the default calendar system for [CalendarDatePicker]. |
164 | /// |
165 | /// See also: |
166 | /// * [CalendarDelegate], the base class for defining custom calendars. |
167 | /// * [CalendarDatePicker], which uses this delegate for date selection. |
168 | class GregorianCalendarDelegate extends CalendarDelegate<DateTime> { |
169 | /// Creates a calendar delegate that uses the Gregorian calendar and the |
170 | /// conventions of the current [MaterialLocalizations]. |
171 | const GregorianCalendarDelegate(); |
172 | |
173 | @override |
174 | DateTime now() => DateTime.now(); |
175 | |
176 | @override |
177 | DateTime dateOnly(DateTime date) => DateUtils.dateOnly(date); |
178 | |
179 | @override |
180 | int monthDelta(DateTime startDate, DateTime endDate) => DateUtils.monthDelta(startDate, endDate); |
181 | |
182 | @override |
183 | DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { |
184 | return DateUtils.addMonthsToMonthDate(monthDate, monthsToAdd); |
185 | } |
186 | |
187 | @override |
188 | DateTime addDaysToDate(DateTime date, int days) => DateUtils.addDaysToDate(date, days); |
189 | |
190 | @override |
191 | int firstDayOffset(int year, int month, MaterialLocalizations localizations) { |
192 | return DateUtils.firstDayOffset(year, month, localizations); |
193 | } |
194 | |
195 | /// {@macro flutter.material.date.getDaysInMonth} |
196 | @override |
197 | int getDaysInMonth(int year, int month) => DateUtils.getDaysInMonth(year, month); |
198 | |
199 | @override |
200 | DateTime getMonth(int year, int month) => DateTime(year, month); |
201 | |
202 | @override |
203 | DateTime getDay(int year, int month, int day) => DateTime(year, month, day); |
204 | |
205 | @override |
206 | String formatMonthYear(DateTime date, MaterialLocalizations localizations) { |
207 | return localizations.formatMonthYear(date); |
208 | } |
209 | |
210 | @override |
211 | String formatMediumDate(DateTime date, MaterialLocalizations localizations) { |
212 | return localizations.formatMediumDate(date); |
213 | } |
214 | |
215 | @override |
216 | String formatShortMonthDay(DateTime date, MaterialLocalizations localizations) { |
217 | return localizations.formatShortMonthDay(date); |
218 | } |
219 | |
220 | @override |
221 | String formatShortDate(DateTime date, MaterialLocalizations localizations) { |
222 | return localizations.formatShortDate(date); |
223 | } |
224 | |
225 | @override |
226 | String formatFullDate(DateTime date, MaterialLocalizations localizations) { |
227 | return localizations.formatFullDate(date); |
228 | } |
229 | |
230 | @override |
231 | String formatCompactDate(DateTime date, MaterialLocalizations localizations) { |
232 | return localizations.formatCompactDate(date); |
233 | } |
234 | |
235 | @override |
236 | DateTime? parseCompactDate(String? inputString, MaterialLocalizations localizations) { |
237 | return localizations.parseCompactDate(inputString); |
238 | } |
239 | |
240 | @override |
241 | String dateHelpText(MaterialLocalizations localizations) { |
242 | return localizations.dateHelpText; |
243 | } |
244 | } |
245 | |
246 | /// Utility functions for working with dates. |
247 | abstract final class DateUtils { |
248 | /// {@template flutter.material.date.dateOnly} |
249 | /// Returns a [DateTime] with the date of the original, but time set to |
250 | /// midnight. |
251 | /// {@endtemplate} |
252 | static DateTime dateOnly(DateTime date) { |
253 | return DateTime(date.year, date.month, date.day); |
254 | } |
255 | |
256 | /// {@template flutter.material.date.datesOnly} |
257 | /// Returns a [DateTimeRange] with the dates of the original, but with times |
258 | /// set to midnight. |
259 | /// |
260 | /// See also: |
261 | /// * [dateOnly], which does the same thing for a single date. |
262 | /// {@endtemplate} |
263 | static DateTimeRange datesOnly(DateTimeRange range) { |
264 | return DateTimeRange(start: dateOnly(range.start), end: dateOnly(range.end)); |
265 | } |
266 | |
267 | /// {@template flutter.material.date.isSameDay} |
268 | /// Returns true if the two [DateTime] objects have the same day, month, and |
269 | /// year, or are both null. |
270 | /// {@endtemplate} |
271 | static bool isSameDay(DateTime? dateA, DateTime? dateB) { |
272 | return dateA?.year == dateB?.year && dateA?.month == dateB?.month && dateA?.day == dateB?.day; |
273 | } |
274 | |
275 | /// {@template flutter.material.date.isSameMonth} |
276 | /// Returns true if the two [DateTime] objects have the same month and |
277 | /// year, or are both null. |
278 | /// {@endtemplate} |
279 | static bool isSameMonth(DateTime? dateA, DateTime? dateB) { |
280 | return dateA?.year == dateB?.year && dateA?.month == dateB?.month; |
281 | } |
282 | |
283 | /// {@template flutter.material.date.monthDelta} |
284 | /// Determines the number of months between two [DateTime] objects. |
285 | /// |
286 | /// For example: |
287 | /// |
288 | /// ```dart |
289 | /// DateTime date1 = DateTime(2019, 6, 15); |
290 | /// DateTime date2 = DateTime(2020, 1, 15); |
291 | /// int delta = DateUtils.monthDelta(date1, date2); |
292 | /// ``` |
293 | /// |
294 | /// The value for `delta` would be `7`. |
295 | /// {@endtemplate} |
296 | static int monthDelta(DateTime startDate, DateTime endDate) { |
297 | return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month; |
298 | } |
299 | |
300 | /// {@template flutter.material.date.addMonthsToMonthDate} |
301 | /// Returns a [DateTime] that is [monthDate] with the added number |
302 | /// of months and the day set to 1 and time set to midnight. |
303 | /// |
304 | /// For example: |
305 | /// |
306 | /// ```dart |
307 | /// DateTime date = DateTime(2019, 1, 15); |
308 | /// DateTime futureDate = DateUtils.addMonthsToMonthDate(date, 3); |
309 | /// ``` |
310 | /// |
311 | /// `date` would be January 15, 2019. |
312 | /// `futureDate` would be April 1, 2019 since it adds 3 months. |
313 | /// {@endtemplate} |
314 | static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { |
315 | return DateTime(monthDate.year, monthDate.month + monthsToAdd); |
316 | } |
317 | |
318 | /// {@template flutter.material.date.addDaysToDate} |
319 | /// Returns a [DateTime] with the added number of days and time set to |
320 | /// midnight. |
321 | /// {@endtemplate} |
322 | static DateTime addDaysToDate(DateTime date, int days) { |
323 | return DateTime(date.year, date.month, date.day + days); |
324 | } |
325 | |
326 | /// {@template flutter.material.date.firstDayOffset} |
327 | /// Computes the offset from the first day of the week that the first day of |
328 | /// the [month] falls on. |
329 | /// |
330 | /// For example, September 1, 2017 falls on a Friday, which in the calendar |
331 | /// localized for United States English appears as: |
332 | /// |
333 | /// S M T W T F S |
334 | /// _ _ _ _ _ 1 2 |
335 | /// |
336 | /// The offset for the first day of the months is the number of leading blanks |
337 | /// in the calendar, i.e. 5. |
338 | /// |
339 | /// The same date localized for the Russian calendar has a different offset, |
340 | /// because the first day of week is Monday rather than Sunday: |
341 | /// |
342 | /// M T W T F S S |
343 | /// _ _ _ _ 1 2 3 |
344 | /// |
345 | /// So the offset is 4, rather than 5. |
346 | /// |
347 | /// This code consolidates the following: |
348 | /// |
349 | /// - [DateTime.weekday] provides a 1-based index into days of week, with 1 |
350 | /// falling on Monday. |
351 | /// - [MaterialLocalizations.firstDayOfWeekIndex] provides a 0-based index |
352 | /// into the [MaterialLocalizations.narrowWeekdays] list. |
353 | /// - [MaterialLocalizations.narrowWeekdays] list provides localized names of |
354 | /// days of week, always starting with Sunday and ending with Saturday. |
355 | /// {@endtemplate} |
356 | static int firstDayOffset(int year, int month, MaterialLocalizations localizations) { |
357 | // 0-based day of week for the month and year, with 0 representing Monday. |
358 | final int weekdayFromMonday = DateTime(year, month).weekday - 1; |
359 | |
360 | // 0-based start of week depending on the locale, with 0 representing Sunday. |
361 | int firstDayOfWeekIndex = localizations.firstDayOfWeekIndex; |
362 | |
363 | // firstDayOfWeekIndex recomputed to be Monday-based, in order to compare with |
364 | // weekdayFromMonday. |
365 | firstDayOfWeekIndex = (firstDayOfWeekIndex - 1) % 7; |
366 | |
367 | // Number of days between the first day of week appearing on the calendar, |
368 | // and the day corresponding to the first of the month. |
369 | return (weekdayFromMonday - firstDayOfWeekIndex) % 7; |
370 | } |
371 | |
372 | /// {@template flutter.material.date.getDaysInMonth} |
373 | /// Returns the number of days in a month, according to the proleptic |
374 | /// Gregorian calendar. |
375 | /// |
376 | /// This applies the leap year logic introduced by the Gregorian reforms of |
377 | /// 1582. It will not give valid results for dates prior to that time. |
378 | /// {@endtemplate} |
379 | static int getDaysInMonth(int year, int month) { |
380 | if (month == DateTime.february) { |
381 | final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); |
382 | return isLeapYear ? 29 : 28; |
383 | } |
384 | const List<int> daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; |
385 | return daysInMonth[month - 1]; |
386 | } |
387 | } |
388 | |
389 | /// Mode of date entry method for the date picker dialog. |
390 | /// |
391 | /// In [calendar] mode, a calendar grid is displayed and the user taps the |
392 | /// day they wish to select. In [input] mode, a [TextField] is displayed and |
393 | /// the user types in the date they wish to select. |
394 | /// |
395 | /// [calendarOnly] and [inputOnly] are variants of the above that don't |
396 | /// allow the user to change to the mode. |
397 | /// |
398 | /// See also: |
399 | /// |
400 | /// * [showDatePicker] and [showDateRangePicker], which use this to control |
401 | /// the initial entry mode of their dialogs. |
402 | enum DatePickerEntryMode { |
403 | /// User picks a date from calendar grid. Can switch to [input] by activating |
404 | /// a mode button in the dialog. |
405 | calendar, |
406 | |
407 | /// User can input the date by typing it into a text field. |
408 | /// |
409 | /// Can switch to [calendar] by activating a mode button in the dialog. |
410 | input, |
411 | |
412 | /// User can only pick a date from calendar grid. |
413 | /// |
414 | /// There is no user interface to switch to another mode. |
415 | calendarOnly, |
416 | |
417 | /// User can only input the date by typing it into a text field. |
418 | /// |
419 | /// There is no user interface to switch to another mode. |
420 | inputOnly, |
421 | } |
422 | |
423 | /// Initial display of a calendar date picker. |
424 | /// |
425 | /// Either a grid of available years or a monthly calendar. |
426 | /// |
427 | /// See also: |
428 | /// |
429 | /// * [showDatePicker], which shows a dialog that contains a Material Design |
430 | /// date picker. |
431 | /// * [CalendarDatePicker], widget which implements the Material Design date picker. |
432 | enum DatePickerMode { |
433 | /// Choosing a month and day. |
434 | day, |
435 | |
436 | /// Choosing a year. |
437 | year, |
438 | } |
439 | |
440 | /// Signature for predicating dates for enabled date selections. |
441 | /// |
442 | /// See [showDatePicker], which has a [SelectableDayPredicate] parameter used |
443 | /// to specify allowable days in the date picker. |
444 | typedef SelectableDayPredicate = bool Function(DateTime day); |
445 | |
446 | /// Encapsulates a start and end [DateTime] that represent the range of dates. |
447 | /// |
448 | /// The range includes the [start] and [end] dates. The [start] and [end] dates |
449 | /// may be equal to indicate a date range of a single day. The [start] date must |
450 | /// not be after the [end] date. |
451 | /// |
452 | /// See also: |
453 | /// * [showDateRangePicker], which displays a dialog that allows the user to |
454 | /// select a date range. |
455 | @immutable |
456 | @optionalTypeArgs |
457 | class DateTimeRange<T extends DateTime> { |
458 | /// Creates a date range for the given start and end [DateTime]. |
459 | DateTimeRange({required this.start, required this.end}) : assert(!start.isAfter(end)); |
460 | |
461 | /// The start of the range of dates. |
462 | final T start; |
463 | |
464 | /// The end of the range of dates. |
465 | final T end; |
466 | |
467 | /// Returns a [Duration] of the time between [start] and [end]. |
468 | /// |
469 | /// See [DateTime.difference] for more details. |
470 | Duration get duration => end.difference(start); |
471 | |
472 | @override |
473 | bool operator ==(Object other) { |
474 | if (other.runtimeType != runtimeType) { |
475 | return false; |
476 | } |
477 | return other is DateTimeRange && other.start == start && other.end == end; |
478 | } |
479 | |
480 | @override |
481 | int get hashCode => Object.hash(start, end); |
482 | |
483 | @override |
484 | String toString() => '$start -$end '; |
485 | } |
486 |
Definitions
- CalendarDelegate
- CalendarDelegate
- now
- dateOnly
- datesOnly
- isSameDay
- isSameMonth
- monthDelta
- addMonthsToMonthDate
- addDaysToDate
- firstDayOffset
- getDaysInMonth
- getMonth
- getDay
- formatMonthYear
- formatYear
- formatMediumDate
- formatShortMonthDay
- formatShortDate
- formatFullDate
- formatCompactDate
- parseCompactDate
- dateHelpText
- GregorianCalendarDelegate
- GregorianCalendarDelegate
- now
- dateOnly
- monthDelta
- addMonthsToMonthDate
- addDaysToDate
- firstDayOffset
- getDaysInMonth
- getMonth
- getDay
- formatMonthYear
- formatMediumDate
- formatShortMonthDay
- formatShortDate
- formatFullDate
- formatCompactDate
- parseCompactDate
- dateHelpText
- DateUtils
- dateOnly
- datesOnly
- isSameDay
- isSameMonth
- monthDelta
- addMonthsToMonthDate
- addDaysToDate
- firstDayOffset
- getDaysInMonth
- DatePickerEntryMode
- DatePickerMode
- DateTimeRange
- DateTimeRange
- duration
- ==
- hashCode
Learn more about Flutter for embedded and desktop on industrialflutter.com