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 'time_picker.dart'; |
6 | library; |
7 | |
8 | import 'package:flutter/services.dart'; |
9 | import 'package:flutter/widgets.dart'; |
10 | |
11 | import 'debug.dart'; |
12 | import 'material_localizations.dart'; |
13 | |
14 | /// Whether the [TimeOfDay] is before or after noon. |
15 | enum DayPeriod { |
16 | /// Ante meridiem (before noon). |
17 | am, |
18 | |
19 | /// Post meridiem (after noon). |
20 | pm, |
21 | } |
22 | |
23 | /// A value representing a time during the day, independent of the date that |
24 | /// day might fall on or the time zone. |
25 | /// |
26 | /// The time is represented by [hour] and [minute] pair. Once created, both |
27 | /// values cannot be changed. |
28 | /// |
29 | /// You can create TimeOfDay using the constructor which requires both hour and |
30 | /// minute or using [DateTime] object. |
31 | /// Hours are specified between 0 and 23, as in a 24-hour clock. |
32 | /// |
33 | /// {@tool snippet} |
34 | /// |
35 | /// ```dart |
36 | /// TimeOfDay now = TimeOfDay.now(); |
37 | /// const TimeOfDay releaseTime = TimeOfDay(hour: 15, minute: 0); // 3:00pm |
38 | /// TimeOfDay roomBooked = TimeOfDay.fromDateTime(DateTime.parse('2018-10-20 16:30:04Z')); // 4:30pm |
39 | /// ``` |
40 | /// {@end-tool} |
41 | /// |
42 | /// See also: |
43 | /// |
44 | /// * [showTimePicker], which returns this type. |
45 | /// * [MaterialLocalizations], which provides methods for formatting values of |
46 | /// this type according to the chosen [Locale]. |
47 | /// * [DateTime], which represents date and time, and is subject to eras and |
48 | /// time zones. |
49 | @immutable |
50 | class TimeOfDay implements Comparable<TimeOfDay> { |
51 | /// Creates a time of day. |
52 | /// |
53 | /// The [hour] argument must be between 0 and 23, inclusive. The [minute] |
54 | /// argument must be between 0 and 59, inclusive. |
55 | const TimeOfDay({required this.hour, required this.minute}); |
56 | |
57 | /// Creates a time of day based on the given time. |
58 | /// |
59 | /// The [hour] is set to the time's hour and the [minute] is set to the time's |
60 | /// minute in the timezone of the given [DateTime]. |
61 | TimeOfDay.fromDateTime(DateTime time) : hour = time.hour, minute = time.minute; |
62 | |
63 | /// Creates a time of day based on the current time. |
64 | /// |
65 | /// The [hour] is set to the current hour and the [minute] is set to the |
66 | /// current minute in the local time zone. |
67 | TimeOfDay.now() : this.fromDateTime(DateTime.now()); |
68 | |
69 | /// The number of hours in one day, i.e. 24. |
70 | static const int hoursPerDay = 24; |
71 | |
72 | /// The number of hours in one day period (see also [DayPeriod]), i.e. 12. |
73 | static const int hoursPerPeriod = 12; |
74 | |
75 | /// The number of minutes in one hour, i.e. 60. |
76 | static const int minutesPerHour = 60; |
77 | |
78 | /// Returns a new TimeOfDay with the hour and/or minute replaced. |
79 | TimeOfDay replacing({int? hour, int? minute}) { |
80 | assert(hour == null || (hour >= 0 && hour < hoursPerDay)); |
81 | assert(minute == null || (minute >= 0 && minute < minutesPerHour)); |
82 | return TimeOfDay(hour: hour ?? this.hour, minute: minute ?? this.minute); |
83 | } |
84 | |
85 | /// The selected hour, in 24 hour time from 0..23. |
86 | final int hour; |
87 | |
88 | /// The selected minute. |
89 | final int minute; |
90 | |
91 | /// Whether this time of day is before or after noon. |
92 | DayPeriod get period => hour < hoursPerPeriod ? DayPeriod.am : DayPeriod.pm; |
93 | |
94 | /// Which hour of the current period (e.g., am or pm) this time is. |
95 | /// |
96 | /// For 12AM (midnight) and 12PM (noon) this returns 12. |
97 | int get hourOfPeriod => hour == 0 || hour == 12 ? 12 : hour - periodOffset; |
98 | |
99 | /// The hour at which the current period starts. |
100 | int get periodOffset => period == DayPeriod.am ? 0 : hoursPerPeriod; |
101 | |
102 | /// Returns the localized string representation of this time of day. |
103 | /// |
104 | /// This is a shortcut for [MaterialLocalizations.formatTimeOfDay]. |
105 | String format(BuildContext context) { |
106 | assert(debugCheckHasMediaQuery(context)); |
107 | assert(debugCheckHasMaterialLocalizations(context)); |
108 | final MaterialLocalizations localizations = MaterialLocalizations.of(context); |
109 | return localizations.formatTimeOfDay( |
110 | this, |
111 | alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context), |
112 | ); |
113 | } |
114 | |
115 | /// Whether this [TimeOfDay] occurs earlier than [other]. |
116 | /// |
117 | /// Does not account for day or sub-minute differences. This means |
118 | /// that "00:00" of the next day is still before "23:00" of this day. |
119 | bool isBefore(TimeOfDay other) => compareTo(other) < 0; |
120 | |
121 | /// Whether this [TimeOfDay] occurs later than [other]. |
122 | /// |
123 | /// Does not account for day or sub-minute differences. This means |
124 | /// that "00:00" of the next day is still before "23:00" of this day. |
125 | bool isAfter(TimeOfDay other) => compareTo(other) > 0; |
126 | |
127 | /// Whether this [TimeOfDay] occurs at the same time as [other]. |
128 | /// |
129 | /// Does not account for day or sub-minute differences. This means |
130 | /// that "00:00" of the next day is still before "23:00" of this day. |
131 | bool isAtSameTimeAs(TimeOfDay other) => compareTo(other) == 0; |
132 | |
133 | /// Compares this [TimeOfDay] object to [other] independent of date. |
134 | /// |
135 | /// Does not account for day or sub-minute differences. This means |
136 | /// that "00:00" of the next day is still before "23:00" of this day. |
137 | /// |
138 | /// A [compareTo] function returns: |
139 | /// * a negative value if this TimeOfDay [isBefore] [other]. |
140 | /// * `0` if this DateTime [isAtSameTimeAs] [other], and |
141 | /// * a positive value otherwise (when this TimeOfDay [isAfter] [other]). |
142 | @override |
143 | int compareTo(TimeOfDay other) { |
144 | final int hourComparison = hour.compareTo(other.hour); |
145 | return hourComparison == 0 ? minute.compareTo(other.minute) : hourComparison; |
146 | } |
147 | |
148 | @override |
149 | bool operator ==(Object other) { |
150 | return other is TimeOfDay && other.hour == hour && other.minute == minute; |
151 | } |
152 | |
153 | @override |
154 | int get hashCode => Object.hash(hour, minute); |
155 | |
156 | @override |
157 | String toString() { |
158 | String addLeadingZeroIfNeeded(int value) { |
159 | if (value < 10) { |
160 | return '0 $value' ; |
161 | } |
162 | return value.toString(); |
163 | } |
164 | |
165 | final String hourLabel = addLeadingZeroIfNeeded(hour); |
166 | final String minuteLabel = addLeadingZeroIfNeeded(minute); |
167 | |
168 | return ' $TimeOfDay( $hourLabel: $minuteLabel)' ; |
169 | } |
170 | } |
171 | |
172 | /// A [RestorableValue] that knows how to save and restore [TimeOfDay]. |
173 | /// |
174 | /// {@macro flutter.widgets.RestorableNum}. |
175 | class RestorableTimeOfDay extends RestorableValue<TimeOfDay> { |
176 | /// Creates a [RestorableTimeOfDay]. |
177 | /// |
178 | /// {@macro flutter.widgets.RestorableNum.constructor} |
179 | RestorableTimeOfDay(TimeOfDay defaultValue) : _defaultValue = defaultValue; |
180 | |
181 | final TimeOfDay _defaultValue; |
182 | |
183 | @override |
184 | TimeOfDay createDefaultValue() => _defaultValue; |
185 | |
186 | @override |
187 | void didUpdateValue(TimeOfDay? oldValue) { |
188 | assert(debugIsSerializableForRestoration(value.hour)); |
189 | assert(debugIsSerializableForRestoration(value.minute)); |
190 | notifyListeners(); |
191 | } |
192 | |
193 | @override |
194 | TimeOfDay fromPrimitives(Object? data) { |
195 | final List<Object?> timeData = data! as List<Object?>; |
196 | return TimeOfDay(minute: timeData[0]! as int, hour: timeData[1]! as int); |
197 | } |
198 | |
199 | @override |
200 | Object? toPrimitives() => <int>[value.minute, value.hour]; |
201 | } |
202 | |
203 | /// Determines how the time picker invoked using [showTimePicker] formats and |
204 | /// lays out the time controls. |
205 | /// |
206 | /// The time picker provides layout configurations optimized for each of the |
207 | /// enum values. |
208 | enum TimeOfDayFormat { |
209 | /// Corresponds to the ICU 'HH:mm' pattern. |
210 | /// |
211 | /// This format uses 24-hour two-digit zero-padded hours. Controls are always |
212 | /// laid out horizontally. Hours are separated from minutes by one colon |
213 | /// character. |
214 | HH_colon_mm, |
215 | |
216 | /// Corresponds to the ICU 'HH.mm' pattern. |
217 | /// |
218 | /// This format uses 24-hour two-digit zero-padded hours. Controls are always |
219 | /// laid out horizontally. Hours are separated from minutes by one dot |
220 | /// character. |
221 | HH_dot_mm, |
222 | |
223 | /// Corresponds to the ICU "HH 'h' mm" pattern used in Canadian French. |
224 | /// |
225 | /// This format uses 24-hour two-digit zero-padded hours. Controls are always |
226 | /// laid out horizontally. Hours are separated from minutes by letter 'h'. |
227 | frenchCanadian, |
228 | |
229 | /// Corresponds to the ICU 'H:mm' pattern. |
230 | /// |
231 | /// This format uses 24-hour non-padded variable-length hours. Controls are |
232 | /// always laid out horizontally. Hours are separated from minutes by one |
233 | /// colon character. |
234 | H_colon_mm, |
235 | |
236 | /// Corresponds to the ICU 'h:mm a' pattern. |
237 | /// |
238 | /// This format uses 12-hour non-padded variable-length hours with a day |
239 | /// period. Controls are laid out horizontally in portrait mode. In landscape |
240 | /// mode, the day period appears vertically after (consistent with the ambient |
241 | /// [TextDirection]) hour-minute indicator. Hours are separated from minutes |
242 | /// by one colon character. |
243 | h_colon_mm_space_a, |
244 | |
245 | /// Corresponds to the ICU 'a h:mm' pattern. |
246 | /// |
247 | /// This format uses 12-hour non-padded variable-length hours with a day |
248 | /// period. Controls are laid out horizontally in portrait mode. In landscape |
249 | /// mode, the day period appears vertically before (consistent with the |
250 | /// ambient [TextDirection]) hour-minute indicator. Hours are separated from |
251 | /// minutes by one colon character. |
252 | a_space_h_colon_mm, |
253 | } |
254 | |
255 | /// Describes how hours are formatted. |
256 | enum HourFormat { |
257 | /// Zero-padded two-digit 24-hour format ranging from "00" to "23". |
258 | HH, |
259 | |
260 | /// Non-padded variable-length 24-hour format ranging from "0" to "23". |
261 | H, |
262 | |
263 | /// Non-padded variable-length hour in day period format ranging from "1" to |
264 | /// "12". |
265 | h, |
266 | } |
267 | |
268 | /// The [HourFormat] used for the given [TimeOfDayFormat]. |
269 | HourFormat hourFormat({required TimeOfDayFormat of}) { |
270 | switch (of) { |
271 | case TimeOfDayFormat.h_colon_mm_space_a: |
272 | case TimeOfDayFormat.a_space_h_colon_mm: |
273 | return HourFormat.h; |
274 | case TimeOfDayFormat.H_colon_mm: |
275 | return HourFormat.H; |
276 | case TimeOfDayFormat.HH_dot_mm: |
277 | case TimeOfDayFormat.HH_colon_mm: |
278 | case TimeOfDayFormat.frenchCanadian: |
279 | return HourFormat.HH; |
280 | } |
281 | } |
282 | |