utils.dart 9.0 KB

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