Line data Source code
1 : /// Bunch of useful functions for date pickers.
2 : class DatePickerUtils {
3 : /// Returns if two objects have same year, month and day.
4 : /// Time doesn't matter.
5 4 : static bool sameDate(DateTime dateTimeOne, DateTime dateTimeTwo) =>
6 12 : dateTimeOne.year == dateTimeTwo.year &&
7 12 : dateTimeOne.month == dateTimeTwo.month &&
8 12 : dateTimeOne.day == dateTimeTwo.day;
9 :
10 : /// Returns if two objects have same year and month.
11 : /// Day and time don't matter/
12 1 : static bool sameMonth(DateTime dateTimeOne, DateTime dateTimeTwo) =>
13 3 : dateTimeOne.year == dateTimeTwo.year &&
14 3 : dateTimeOne.month == dateTimeTwo.month;
15 :
16 : // Do not use this directly - call getDaysInMonth instead.
17 : static const List<int> _daysInMonth = <int>[
18 : 31,
19 : -1,
20 : 31,
21 : 30,
22 : 31,
23 : 30,
24 : 31,
25 : 31,
26 : 30,
27 : 31,
28 : 30,
29 : 31
30 : ];
31 :
32 : /// Returns the number of days in a month, according to the proleptic
33 : /// Gregorian calendar.
34 : ///
35 : /// This applies the leap year logic introduced by the Gregorian reforms of
36 : /// 1582. It will not give valid results for dates prior to that time.
37 1 : static int getDaysInMonth(int year, int month) {
38 1 : if (month == DateTime.february) {
39 : final bool isLeapYear =
40 0 : (year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0);
41 : return isLeapYear ? 29 : 28;
42 : }
43 2 : return _daysInMonth[month - 1];
44 : }
45 :
46 : /// Returns number of months between [startDate] and [endDate]
47 0 : static int monthDelta(DateTime startDate, DateTime endDate) =>
48 0 : (endDate.year - startDate.year) * 12 + endDate.month - startDate.month;
49 :
50 : /// Add months to a month truncated date.
51 0 : static DateTime addMonthsToMonthDate(DateTime monthDate, int monthsToAdd) =>
52 : // year is switched automatically if new month > 12
53 0 : DateTime(monthDate.year, monthDate.month + monthsToAdd);
54 :
55 : /// Returns number of years between [startDate] and [endDate]
56 0 : static int yearDelta(DateTime startDate, DateTime endDate) =>
57 0 : endDate.year - startDate.year;
58 :
59 : /// Returns start of the first day of the week with given day.
60 : ///
61 : /// Start of the week calculated using firstDayIndex which is int from 0 to 6
62 : /// where 0 points to Sunday and 6 points to Saturday.
63 : /// (according to MaterialLocalization.firstDayIfWeekIndex)
64 1 : static DateTime getFirstDayOfWeek(DateTime day, int firstDayIndex) {
65 : // from 1 to 7 where 1 points to Monday and 7 points to Sunday
66 1 : int weekday = day.weekday;
67 :
68 : // to match weekdays where Sunday is 7 not 0
69 1 : if (firstDayIndex == 0) firstDayIndex = 7;
70 :
71 1 : int diff = weekday - firstDayIndex;
72 1 : if (diff < 0) diff = 7 + diff;
73 :
74 1 : DateTime firstDayOfWeek = DateTime(
75 1 : day.year,
76 1 : day.month,
77 2 : day.day - diff,
78 1 : day.hour,
79 1 : day.minute,
80 1 : day.second,
81 : );
82 1 : firstDayOfWeek = startOfTheDay(firstDayOfWeek);
83 :
84 : return firstDayOfWeek;
85 : }
86 :
87 : /// Returns end of the last day of the week with given day.
88 : ///
89 : /// Start of the week calculated using firstDayIndex which is int from 0 to 6
90 : /// where 0 points to Sunday and 6 points to Saturday.
91 : /// (according to MaterialLocalization.firstDayIfWeekIndex)
92 1 : static DateTime getLastDayOfWeek(DateTime day, int firstDayIndex) {
93 : // from 1 to 7 where 1 points to Monday and 7 points to Sunday
94 1 : int weekday = day.weekday;
95 :
96 : // to match weekdays where Sunday is 7 not 0
97 1 : if (firstDayIndex == 0) firstDayIndex = 7;
98 :
99 1 : int lastDayIndex = firstDayIndex - 1;
100 1 : if (lastDayIndex == 0) lastDayIndex = 7;
101 :
102 1 : int diff = lastDayIndex - weekday;
103 1 : if (diff < 0) diff = 7 + diff;
104 :
105 1 : DateTime lastDayOfWeek = DateTime(
106 1 : day.year,
107 1 : day.month,
108 2 : day.day + diff,
109 1 : day.hour,
110 1 : day.minute,
111 1 : day.second,
112 : );
113 1 : lastDayOfWeek = endOfTheDay(lastDayOfWeek);
114 :
115 : return lastDayOfWeek;
116 : }
117 :
118 : /// Returns end of the given day.
119 : ///
120 : /// End time is 1 millisecond before start of the next day.
121 5 : static DateTime endOfTheDay(DateTime date) {
122 25 : DateTime tomorrowStart = DateTime(date.year, date.month, date.day + 1);
123 5 : DateTime result = tomorrowStart.subtract(const Duration(milliseconds: 1));
124 :
125 : return result;
126 : }
127 :
128 : /// Returns start of the given day.
129 : ///
130 : /// Start time is 00:00:00.
131 5 : static DateTime startOfTheDay(DateTime date) =>
132 20 : DateTime(date.year, date.month, date.day);
133 :
134 : /// Returns first shown date for the [curMonth].
135 : ///
136 : /// First shown date is not always 1st day of the [curMonth].
137 : /// It can be day from previous month if [showEndOfPrevMonth] is true.
138 : ///
139 : /// If [showEndOfPrevMonth] is true empty day cells before 1st [curMonth]
140 : /// are filled with days from the previous month.
141 0 : static DateTime firstShownDate({
142 : required DateTime curMonth,
143 : required bool showEndOfPrevMonth,
144 : required int firstDayOfWeekFromSunday,
145 : }) {
146 0 : DateTime result = DateTime(curMonth.year, curMonth.month, 1);
147 :
148 : if (showEndOfPrevMonth) {
149 0 : int firstDayOffset = computeFirstDayOffset(
150 0 : curMonth.year, curMonth.month, firstDayOfWeekFromSunday);
151 0 : if (firstDayOffset == 0) return result;
152 :
153 0 : int prevMonth = curMonth.month - 1;
154 0 : if (prevMonth < 1) prevMonth = 12;
155 :
156 0 : int prevYear = prevMonth == 12 ? curMonth.year - 1 : curMonth.year;
157 :
158 0 : int daysInPrevMonth = getDaysInMonth(prevYear, prevMonth);
159 0 : int firstShownDay = daysInPrevMonth - firstDayOffset + 1;
160 0 : result = DateTime(prevYear, prevMonth, firstShownDay);
161 : }
162 :
163 : return result;
164 : }
165 :
166 : /// Returns last shown date for the [curMonth].
167 : ///
168 : /// Last shown date is not always last day of the [curMonth].
169 : /// It can be day from next month if [showStartNextMonth] is true.
170 : ///
171 : /// If [showStartNextMonth] is true empty day cells after last day
172 : /// of [curMonth] are filled with days from the next month.
173 0 : static DateTime lastShownDate({
174 : required DateTime curMonth,
175 : required bool showStartNextMonth,
176 : required int firstDayOfWeekFromSunday,
177 : }) {
178 0 : int daysInCurMonth = getDaysInMonth(curMonth.year, curMonth.month);
179 0 : DateTime result = DateTime(curMonth.year, curMonth.month, daysInCurMonth);
180 :
181 : if (showStartNextMonth) {
182 0 : int firstDayOffset = computeFirstDayOffset(
183 0 : curMonth.year, curMonth.month, firstDayOfWeekFromSunday);
184 :
185 0 : int totalDays = firstDayOffset + daysInCurMonth;
186 0 : int trailingDaysCount = 7 - totalDays % 7;
187 0 : bool fullWeekTrailing = trailingDaysCount == 7;
188 : if (fullWeekTrailing) return result;
189 :
190 0 : result = DateTime(curMonth.year, curMonth.month + 1, trailingDaysCount);
191 : }
192 :
193 : return result;
194 : }
195 :
196 : /// Computes the offset from the first day of week that the first day of the
197 : /// [month] falls on.
198 : ///
199 : /// For example, September 1, 2017 falls on a Friday, which in the calendar
200 : /// localized for United States English appears as:
201 : ///
202 : /// ```
203 : /// S M T W T F S
204 : /// _ _ _ _ _ 1 2
205 : /// ```
206 : ///
207 : /// The offset for the first day of the months is the number of leading blanks
208 : /// in the calendar, i.e. 5.
209 : ///
210 : /// The same date localized for the Russian calendar has a different offset,
211 : /// because the first day of week is Monday rather than Sunday:
212 : ///
213 : /// ```
214 : /// M T W T F S S
215 : /// _ _ _ _ 1 2 3
216 : /// ```
217 : ///
218 : /// So the offset is 4, rather than 5.
219 : ///
220 : /// This code consolidates the following:
221 : ///
222 : /// - [DateTime.weekday] provides a 1-based index into days of week, with 1
223 : /// falling on Monday.
224 : /// - MaterialLocalizations.firstDayOfWeekIndex provides a 0-based index
225 : /// into the MaterialLocalizations.narrowWeekdays list.
226 : /// - MaterialLocalizations.narrowWeekdays list provides localized names of
227 : /// days of week, always starting with Sunday and ending with Saturday.
228 0 : static int computeFirstDayOffset(
229 : int year, int month, int firstDayOfWeekFromSunday) {
230 : // 0-based day of week, with 0 representing Monday.
231 0 : final int weekdayFromMonday = DateTime(year, month).weekday - 1;
232 : // firstDayOfWeekFromSunday recomputed to be Monday-based
233 0 : final int firstDayOfWeekFromMonday = (firstDayOfWeekFromSunday - 1) % 7;
234 : // Number of days between the first day of week appearing on the calendar,
235 : // and the day corresponding to the 1-st of the month.
236 0 : return (weekdayFromMonday - firstDayOfWeekFromMonday) % 7;
237 : }
238 :
239 : /// Returns earliest [DateTime] from the list.
240 : ///
241 : /// [dates] must not be null.
242 : /// In case it is null, [ArgumentError] will be thrown.
243 0 : static DateTime getEarliestFromList(List<DateTime> dates) {
244 0 : ArgumentError.checkNotNull(dates, "dates");
245 :
246 0 : return dates.fold(dates[0], getEarliest);
247 : }
248 :
249 : /// Returns latest [DateTime] from the list.
250 : ///
251 : /// [dates] must not be null.
252 : /// In case it is null, [ArgumentError] will be thrown.
253 0 : static DateTime getLatestFromList(List<DateTime> dates) {
254 0 : ArgumentError.checkNotNull(dates, "dates");
255 :
256 0 : return dates.fold(dates[0], getLatest);
257 : }
258 :
259 : /// Returns earliest [DateTime] from two.
260 : ///
261 : /// If two [DateTime]s is the same moment first ([a]) will be return.
262 0 : static DateTime getEarliest(DateTime a, DateTime b) => a.isBefore(b) ? a : b;
263 :
264 : /// Returns latest [DateTime] from two.
265 : ///
266 : /// If two [DateTime]s is the same moment first ([a]) will be return.
267 0 : static DateTime getLatest(DateTime a, DateTime b) => a.isAfter(b) ? a : b;
268 : }
|