// 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. import 'package:flutter/foundation.dart'; import 'material_localizations.dart'; /// Utility functions for working with dates. class DateUtils { // This class is not meant to be instantiated or extended; this constructor // prevents instantiation and extension. DateUtils._(); /// Returns a [DateTime] with the date of the original, but time set to /// midnight. static DateTime dateOnly(DateTime date) { return DateTime(date.year, date.month, date.day); } /// 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. static DateTimeRange datesOnly(DateTimeRange range) { return DateTimeRange(start: dateOnly(range.start), end: dateOnly(range.end)); } /// Returns true if the two [DateTime] objects have the same day, month, and /// year, or are both null. static bool isSameDay(DateTime? dateA, DateTime? dateB) { return dateA?.year == dateB?.year && dateA?.month == dateB?.month && dateA?.day == dateB?.day; } /// Returns true if the two [DateTime] objects have the same month and /// year, or are both null. static bool isSameMonth(DateTime? dateA, DateTime? dateB) { return dateA?.year == dateB?.year && dateA?.month == dateB?.month; } /// Determines the number of months between two [DateTime] objects. /// /// For example: /// ``` /// DateTime date1 = DateTime(year: 2019, month: 6, day: 15); /// DateTime date2 = DateTime(year: 2020, month: 1, day: 15); /// int delta = monthDelta(date1, date2); /// ``` /// /// The value for `delta` would be `7`. static int monthDelta(DateTime startDate, DateTime endDate) { return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month; } /// 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: /// ``` /// DateTime date = DateTime(year: 2019, month: 1, day: 15); /// DateTime futureDate = DateUtils.addMonthsToMonthDate(date, 3); /// ``` /// /// `date` would be January 15, 2019. /// `futureDate` would be April 1, 2019 since it adds 3 months. static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { return DateTime(monthDate.year, monthDate.month + monthsToAdd); } /// Returns a [DateTime] with the added number of days and time set to /// midnight. static DateTime addDaysToDate(DateTime date, int days) { return DateTime(date.year, date.month, date.day + days); } /// 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. 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; } /// 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. 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 List<int> 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, } /// Signature for predicating dates for enabled date selections. /// /// See [showDatePicker], which has a [SelectableDayPredicate] parameter used /// to specify allowable days in the date picker. typedef SelectableDayPredicate = bool Function(DateTime day); /// 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 class DateTimeRange { /// Creates a date range for the given start and end [DateTime]. DateTimeRange({ required this.start, required this.end, }) : assert(start != null), assert(end != null), assert(!start.isAfter(end)); /// The start of the range of dates. final DateTime start; /// The end of the range of dates. final DateTime 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'; }