// 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. // Common date utility functions used by the date picker implementation // NOTE: This is an internal implementation file. Even though there are public // classes and functions defined here, they are only meant to be used by the // date picker implementation and are not exported as part of the Material library. // See pickers.dart for exactly what is considered part of the public API. import '../material_localizations.dart'; import 'date_picker_common.dart'; /// Returns a [DateTime] with just the date of the original, but no time set. DateTime dateOnly(DateTime date) { return DateTime(date.year, date.month, date.day); } /// Returns a [DateTimeRange] with the dates of the original without any times set. 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. bool isSameDay(DateTime dateA, DateTime dateB) { return dateA.year == dateB.year && dateA.month == dateB.month && dateA.day == dateB.day; } /// 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`. int monthDelta(DateTime startDate, DateTime endDate) { return (endDate.year - startDate.year) * 12 + endDate.month - startDate.month; } /// Returns a [DateTime] with the added number of months and truncates any day /// and time information. /// /// For example: /// ``` /// DateTime date = DateTime(year: 2019, month: 1, day: 15); /// DateTime futureDate = _addMonthsToMonthDate(date, 3); /// ``` /// /// `date` would be January 15, 2019. /// `futureDate` would be April 1, 2019 since it adds 3 months and truncates /// any additional date information. DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) { return DateTime(monthDate.year, monthDate.month + monthsToAdd); } /// 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. 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. int getDaysInMonth(int year, int month) { if (month == DateTime.february) { final bool isLeapYear = (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0); if (isLeapYear) return 29; return 28; } const List<int> daysInMonth = <int>[31, -1, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; return daysInMonth[month - 1]; } /// Returns a locale-appropriate string to describe the start of a date range. /// /// If `startDate` is null, then it defaults to 'Start Date', otherwise if it /// is in the same year as the `endDate` then it will use the short month /// day format (i.e. 'Jan 21'). Otherwise it will return the short date format /// (i.e. 'Jan 21, 2020'). String formatRangeStartDate(MaterialLocalizations localizations, DateTime startDate, DateTime endDate) { return startDate == null ? 'Start Date' : (endDate == null || startDate.year == endDate.year) ? localizations.formatShortMonthDay(startDate) : localizations.formatShortDate(startDate); } /// Returns an locale-appropriate string to describe the end of a date range. /// /// If `endDate` is null, then it defaults to 'End Date', otherwise if it /// is in the same year as the `startDate` and the `currentDate` then it will /// just use the short month day format (i.e. 'Jan 21'), otherwise it will /// include the year (i.e. 'Jan 21, 2020'). String formatRangeEndDate(MaterialLocalizations localizations, DateTime startDate, DateTime endDate, DateTime currentDate) { return endDate == null ? 'End Date' : (startDate != null && startDate.year == endDate.year && startDate.year == currentDate.year) ? localizations.formatShortMonthDay(endDate) : localizations.formatShortDate(endDate); }