Use alwaysUse24HourFormat when formatting time of day (#12517)

* alwaysUse24HourFormat in MediaQuery and time picker

* docs; dead code

* address some comments

* MaterialLocalizations.timeOfDayFormat is the single source of 24-hour-formattedness

* Make TimePickerDialog private again

* wire up MediaQueryData.fromWindow to Window
diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart
index a276745..0b994a8 100644
--- a/packages/flutter/lib/src/material/material_localizations.dart
+++ b/packages/flutter/lib/src/material/material_localizations.dart
@@ -95,7 +95,7 @@
   ///
   /// The documentation for [TimeOfDayFormat] enum values provides details on
   /// each supported layout.
-  TimeOfDayFormat get timeOfDayFormat;
+  TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false });
 
   /// Provides geometric text preferences for the current locale.
   ///
@@ -120,14 +120,22 @@
 
   /// Formats [TimeOfDay.hour] in the given time of day according to the value
   /// of [timeOfDayFormat].
-  String formatHour(TimeOfDay timeOfDay);
+  ///
+  /// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
+  /// rather than the default for the current locale.
+  String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false });
 
   /// Formats [TimeOfDay.minute] in the given time of day according to the value
   /// of [timeOfDayFormat].
   String formatMinute(TimeOfDay timeOfDay);
 
   /// Formats [timeOfDay] according to the value of [timeOfDayFormat].
-  String formatTimeOfDay(TimeOfDay timeOfDay);
+  ///
+  /// If [alwaysUse24HourFormat] is true, formats hour using [HourFormat.HH]
+  /// rather than the default for the current locale. This value is usually
+  /// passed from [MediaQueryData.alwaysUse24HourFormat], which has platform-
+  /// specific behavior.
+  String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false });
 
   /// Full unabbreviated year format, e.g. 2017 rather than 17.
   String formatYear(DateTime date);
@@ -274,9 +282,27 @@
   ];
 
   @override
-  String formatHour(TimeOfDay timeOfDay) {
-    assert(hourFormat(of: timeOfDayFormat) == HourFormat.h);
-    return formatDecimal(timeOfDay.hour);
+  String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
+    final TimeOfDayFormat format = timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat);
+    switch (format) {
+      case TimeOfDayFormat.h_colon_mm_space_a:
+        return formatDecimal(timeOfDay.hourOfPeriod == 0 ? 12 : timeOfDay.hourOfPeriod);
+      case TimeOfDayFormat.HH_colon_mm:
+        return _formatTwoDigitZeroPad(timeOfDay.hour);
+      default:
+        throw new AssertionError('$runtimeType does not support $format.');
+    }
+  }
+
+  /// Formats [number] using two digits, assuming it's in the 0-99 inclusive
+  /// range. Not designed to format values outside this range.
+  String _formatTwoDigitZeroPad(int number) {
+    assert(0 <= number && number < 100);
+
+    if (number < 10)
+      return '0$number';
+
+    return '$number';
   }
 
   @override
@@ -335,8 +361,7 @@
   }
 
   @override
-  String formatTimeOfDay(TimeOfDay timeOfDay) {
-    assert(timeOfDayFormat == TimeOfDayFormat.h_colon_mm_space_a);
+  String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
     // Not using intl.DateFormat for two reasons:
     //
     // - DateFormat supports more formats than our material time picker does,
@@ -345,7 +370,24 @@
     // - DateFormat operates on DateTime, which is sensitive to time eras and
     //   time zones, while here we want to format hour and minute within one day
     //   no matter what date the day falls on.
-    return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
+    final StringBuffer buffer = new StringBuffer();
+
+    // Add hour:minute.
+    buffer
+      ..write(formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat))
+      ..write(':')
+      ..write(formatMinute(timeOfDay));
+
+    if (alwaysUse24HourFormat) {
+      // There's no AM/PM indicator in 24-hour format.
+      return '$buffer';
+    }
+
+    // Add AM/PM indicator.
+    buffer
+      ..write(' ')
+      ..write(_formatDayPeriod(timeOfDay));
+    return '$buffer';
   }
 
   @override
@@ -434,7 +476,11 @@
   String get postMeridiemAbbreviation => 'PM';
 
   @override
-  TimeOfDayFormat get timeOfDayFormat => TimeOfDayFormat.h_colon_mm_space_a;
+  TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) {
+    return alwaysUse24HourFormat
+      ? TimeOfDayFormat.HH_colon_mm
+      : TimeOfDayFormat.h_colon_mm_space_a;
+  }
 
   /// Looks up text geometry defined in [MaterialTextGeometry].
   @override
diff --git a/packages/flutter/lib/src/material/time.dart b/packages/flutter/lib/src/material/time.dart
index 375e39f..0d2da35 100644
--- a/packages/flutter/lib/src/material/time.dart
+++ b/packages/flutter/lib/src/material/time.dart
@@ -84,8 +84,12 @@
   ///
   /// This is a shortcut for [MaterialLocalizations.formatTimeOfDay].
   String format(BuildContext context) {
+    debugCheckHasMediaQuery(context);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    return localizations.formatTimeOfDay(this);
+    return localizations.formatTimeOfDay(
+      this,
+      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
+    );
   }
 
   @override
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index ebebe37..7760f18 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -224,14 +224,13 @@
 class _HourControl extends StatelessWidget {
   const _HourControl({
     @required this.fragmentContext,
-    @required this.hourFormat,
   });
 
   final _TimePickerFragmentContext fragmentContext;
-  final HourFormat hourFormat;
 
   @override
   Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     final TextStyle hourStyle = fragmentContext.mode == _TimePickerMode.hour
         ? fragmentContext.activeStyle
@@ -239,7 +238,10 @@
 
     return new GestureDetector(
       onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context),
-      child: new Text(localizations.formatHour(fragmentContext.selectedTime), style: hourStyle),
+      child: new Text(localizations.formatHour(
+        fragmentContext.selectedTime,
+        alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
+      ), style: hourStyle),
     );
   }
 }
@@ -285,15 +287,16 @@
 }
 
 /// Provides time picker header layout configuration for the given
-/// [timeOfDayFormat] passing [context] to each widget in the configuration.
+/// [timeOfDayFormat] passing [context] to each widget in the
+/// configuration.
 ///
 /// The [timeOfDayFormat] and [context] arguments must not be null.
 _TimePickerHeaderFormat _buildHeaderFormat(TimeOfDayFormat timeOfDayFormat, _TimePickerFragmentContext context) {
   // Creates an hour fragment.
-  _TimePickerHeaderFragment hour(HourFormat hourFormat) {
+  _TimePickerHeaderFragment hour() {
     return new _TimePickerHeaderFragment(
       layoutId: _TimePickerHeaderId.hour,
-      widget: new _HourControl(fragmentContext: context, hourFormat: hourFormat),
+      widget: new _HourControl(fragmentContext: context),
       startMargin: _kPeriodGap,
     );
   }
@@ -327,7 +330,7 @@
   }
 
   // Convenience function for creating a time header format with up to two pieces.
-  _TimePickerHeaderFormat format(int centrepieceIndex, _TimePickerHeaderPiece piece1,
+  _TimePickerHeaderFormat format(_TimePickerHeaderPiece piece1,
       [ _TimePickerHeaderPiece piece2 ]) {
     final List<_TimePickerHeaderPiece> pieces = <_TimePickerHeaderPiece>[];
     switch (context.textDirection) {
@@ -340,9 +343,15 @@
         if (piece2 != null)
           pieces.add(piece2);
         pieces.add(piece1);
-        centrepieceIndex = pieces.length - centrepieceIndex - 1;
         break;
     }
+    int centrepieceIndex;
+    for (int i = 0; i < pieces.length; i += 1) {
+      if (pieces[i].pivotIndex >= 0) {
+        centrepieceIndex = i;
+      }
+    }
+    assert(centrepieceIndex != null);
     return new _TimePickerHeaderFormat(centrepieceIndex, pieces);
   }
 
@@ -361,10 +370,9 @@
   switch (timeOfDayFormat) {
     case TimeOfDayFormat.h_colon_mm_space_a:
       return format(
-        0,
         piece(
           pivotIndex: 1,
-          fragment1: hour(HourFormat.h),
+          fragment1: hour(),
           fragment2: string(_TimePickerHeaderId.colon, ':'),
           fragment3: minute(),
         ),
@@ -374,44 +382,43 @@
         ),
       );
     case TimeOfDayFormat.H_colon_mm:
-      return format(0, piece(
+      return format(piece(
         pivotIndex: 1,
-        fragment1: hour(HourFormat.H),
+        fragment1: hour(),
         fragment2: string(_TimePickerHeaderId.colon, ':'),
         fragment3: minute(),
       ));
     case TimeOfDayFormat.HH_dot_mm:
-      return format(0, piece(
+      return format(piece(
         pivotIndex: 1,
-        fragment1: hour(HourFormat.HH),
+        fragment1: hour(),
         fragment2: string(_TimePickerHeaderId.dot, '.'),
         fragment3: minute(),
       ));
     case TimeOfDayFormat.a_space_h_colon_mm:
       return format(
-        1,
         piece(
           bottomMargin: _kVerticalGap,
           fragment1: dayPeriod(),
         ),
         piece(
           pivotIndex: 1,
-          fragment1: hour(HourFormat.h),
+          fragment1: hour(),
           fragment2: string(_TimePickerHeaderId.colon, ':'),
           fragment3: minute(),
         ),
       );
     case TimeOfDayFormat.frenchCanadian:
-      return format(0, piece(
+      return format(piece(
         pivotIndex: 1,
-        fragment1: hour(HourFormat.HH),
+        fragment1: hour(),
         fragment2: string(_TimePickerHeaderId.hString, 'h'),
         fragment3: minute(),
       ));
     case TimeOfDayFormat.HH_colon_mm:
-      return format(0, piece(
+      return format(piece(
         pivotIndex: 1,
-        fragment1: hour(HourFormat.HH),
+        fragment1: hour(),
         fragment2: string(_TimePickerHeaderId.colon, ':'),
         fragment3: minute(),
       ));
@@ -571,8 +578,11 @@
 
   @override
   Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
     final ThemeData themeData = Theme.of(context);
-    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat;
+    final MediaQueryData media = MediaQuery.of(context);
+    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context)
+        .timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
 
     EdgeInsets padding;
     double height;
@@ -651,7 +661,7 @@
   }
 }
 
-List<TextPainter> _initPainters(TextTheme textTheme, List<String> labels) {
+List<TextPainter> _buildPainters(TextTheme textTheme, List<String> labels) {
   final TextStyle style = textTheme.subhead;
   final List<TextPainter> painters = new List<TextPainter>(labels.length);
   for (int i = 0; i < painters.length; ++i) {
@@ -671,67 +681,6 @@
   inner,
 }
 
-const List<TimeOfDay> _amHours = const <TimeOfDay>[
-  const TimeOfDay(hour: 0, minute: 0),
-  const TimeOfDay(hour: 1, minute: 0),
-  const TimeOfDay(hour: 2, minute: 0),
-  const TimeOfDay(hour: 3, minute: 0),
-  const TimeOfDay(hour: 4, minute: 0),
-  const TimeOfDay(hour: 5, minute: 0),
-  const TimeOfDay(hour: 6, minute: 0),
-  const TimeOfDay(hour: 7, minute: 0),
-  const TimeOfDay(hour: 8, minute: 0),
-  const TimeOfDay(hour: 9, minute: 0),
-  const TimeOfDay(hour: 10, minute: 0),
-  const TimeOfDay(hour: 11, minute: 0),
-];
-
-const List<TimeOfDay> _pmHours = const <TimeOfDay>[
-  const TimeOfDay(hour: 12, minute: 0),
-  const TimeOfDay(hour: 13, minute: 0),
-  const TimeOfDay(hour: 14, minute: 0),
-  const TimeOfDay(hour: 15, minute: 0),
-  const TimeOfDay(hour: 16, minute: 0),
-  const TimeOfDay(hour: 17, minute: 0),
-  const TimeOfDay(hour: 18, minute: 0),
-  const TimeOfDay(hour: 19, minute: 0),
-  const TimeOfDay(hour: 20, minute: 0),
-  const TimeOfDay(hour: 21, minute: 0),
-  const TimeOfDay(hour: 22, minute: 0),
-  const TimeOfDay(hour: 23, minute: 0),
-];
-
-List<TextPainter> _init24HourInnerRing(TextTheme textTheme, MaterialLocalizations localizations) {
-  return _initPainters(textTheme, _amHours.map(localizations.formatHour).toList());
-}
-
-List<TextPainter> _init24HourOuterRing(TextTheme textTheme, MaterialLocalizations localizations) {
-  return _initPainters(textTheme, _pmHours.map(localizations.formatHour).toList());
-}
-
-List<TextPainter> _init12HourOuterRing(TextTheme textTheme, MaterialLocalizations localizations) {
-  return _initPainters(textTheme, _amHours.map(localizations.formatHour).toList());
-}
-
-const List<TimeOfDay> _minuteMarkerValues = const <TimeOfDay>[
-  const TimeOfDay(hour: 0, minute: 0),
-  const TimeOfDay(hour: 0, minute: 5),
-  const TimeOfDay(hour: 0, minute: 10),
-  const TimeOfDay(hour: 0, minute: 15),
-  const TimeOfDay(hour: 0, minute: 20),
-  const TimeOfDay(hour: 0, minute: 25),
-  const TimeOfDay(hour: 0, minute: 30),
-  const TimeOfDay(hour: 0, minute: 35),
-  const TimeOfDay(hour: 0, minute: 40),
-  const TimeOfDay(hour: 0, minute: 45),
-  const TimeOfDay(hour: 0, minute: 50),
-  const TimeOfDay(hour: 0, minute: 55),
-];
-
-List<TextPainter> _initMinutes(TextTheme textTheme, MaterialLocalizations localizations) {
-  return _initPainters(textTheme, _minuteMarkerValues.map(localizations.formatMinute).toList());
-}
-
 class _DialPainter extends CustomPainter {
   const _DialPainter({
     @required this.primaryOuterLabels,
@@ -830,13 +779,13 @@
   const _Dial({
     @required this.selectedTime,
     @required this.mode,
-    @required this.is24h,
+    @required this.use24HourDials,
     @required this.onChanged
   }) : assert(selectedTime != null);
 
   final TimeOfDay selectedTime;
   final _TimePickerMode mode;
-  final bool is24h;
+  final bool use24HourDials;
   final ValueChanged<TimeOfDay> onChanged;
 
   @override
@@ -858,6 +807,19 @@
     ))..addListener(() => setState(() { }));
   }
 
+  ThemeData themeData;
+  MaterialLocalizations localizations;
+  MediaQueryData media;
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    assert(debugCheckHasMediaQuery(context));
+    themeData = Theme.of(context);
+    localizations = MaterialLocalizations.of(context);
+    media = MediaQuery.of(context);
+  }
+
   @override
   void didUpdateWidget(_Dial oldWidget) {
     super.didUpdateWidget(oldWidget);
@@ -865,7 +827,7 @@
       if (!_dragging)
         _animateTo(_getThetaForTime(widget.selectedTime));
     }
-    if (widget.mode == _TimePickerMode.hour && widget.is24h && widget.selectedTime.period == DayPeriod.am) {
+    if (widget.mode == _TimePickerMode.hour && widget.use24HourDials && widget.selectedTime.period == DayPeriod.am) {
       _activeRing = _DialRing.inner;
     } else {
       _activeRing = _DialRing.outer;
@@ -910,7 +872,7 @@
     final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
     if (widget.mode == _TimePickerMode.hour) {
       int newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
-      if (widget.is24h) {
+      if (widget.use24HourDials) {
         if (_activeRing == _DialRing.outer) {
           if (newHour != 0)
             newHour = (newHour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
@@ -945,7 +907,7 @@
         ..end = angle; // The controller doesn't animate during the pan gesture.
       final RenderBox box = context.findRenderObject();
       final double radius = box.size.shortestSide / 2.0;
-      if (widget.mode == _TimePickerMode.hour && widget.is24h) {
+      if (widget.mode == _TimePickerMode.hour && widget.use24HourDials) {
         if (offset.distance * 1.5 < radius)
           _activeRing = _DialRing.inner;
         else
@@ -982,11 +944,81 @@
     _animateTo(_getThetaForTime(widget.selectedTime));
   }
 
+  static const List<TimeOfDay> _amHours = const <TimeOfDay>[
+    const TimeOfDay(hour: 12, minute: 0),
+    const TimeOfDay(hour: 1, minute: 0),
+    const TimeOfDay(hour: 2, minute: 0),
+    const TimeOfDay(hour: 3, minute: 0),
+    const TimeOfDay(hour: 4, minute: 0),
+    const TimeOfDay(hour: 5, minute: 0),
+    const TimeOfDay(hour: 6, minute: 0),
+    const TimeOfDay(hour: 7, minute: 0),
+    const TimeOfDay(hour: 8, minute: 0),
+    const TimeOfDay(hour: 9, minute: 0),
+    const TimeOfDay(hour: 10, minute: 0),
+    const TimeOfDay(hour: 11, minute: 0),
+  ];
+
+  static const List<TimeOfDay> _pmHours = const <TimeOfDay>[
+    const TimeOfDay(hour: 0, minute: 0),
+    const TimeOfDay(hour: 13, minute: 0),
+    const TimeOfDay(hour: 14, minute: 0),
+    const TimeOfDay(hour: 15, minute: 0),
+    const TimeOfDay(hour: 16, minute: 0),
+    const TimeOfDay(hour: 17, minute: 0),
+    const TimeOfDay(hour: 18, minute: 0),
+    const TimeOfDay(hour: 19, minute: 0),
+    const TimeOfDay(hour: 20, minute: 0),
+    const TimeOfDay(hour: 21, minute: 0),
+    const TimeOfDay(hour: 22, minute: 0),
+    const TimeOfDay(hour: 23, minute: 0),
+  ];
+
+  List<TextPainter> _build24HourInnerRing(TextTheme textTheme) {
+    return _buildPainters(textTheme, _amHours
+        .map((TimeOfDay timeOfDay) {
+      return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    })
+        .toList());
+  }
+
+  List<TextPainter> _build24HourOuterRing(TextTheme textTheme) {
+    return _buildPainters(textTheme, _pmHours
+        .map((TimeOfDay timeOfDay) {
+      return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    })
+        .toList());
+  }
+
+  List<TextPainter> _build12HourOuterRing(TextTheme textTheme) {
+    return _buildPainters(textTheme, _amHours
+        .map((TimeOfDay timeOfDay) {
+      return localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    })
+        .toList());
+  }
+
+  List<TextPainter> _buildMinutes(TextTheme textTheme) {
+    const List<TimeOfDay> _minuteMarkerValues = const <TimeOfDay>[
+      const TimeOfDay(hour: 0, minute: 0),
+      const TimeOfDay(hour: 0, minute: 5),
+      const TimeOfDay(hour: 0, minute: 10),
+      const TimeOfDay(hour: 0, minute: 15),
+      const TimeOfDay(hour: 0, minute: 20),
+      const TimeOfDay(hour: 0, minute: 25),
+      const TimeOfDay(hour: 0, minute: 30),
+      const TimeOfDay(hour: 0, minute: 35),
+      const TimeOfDay(hour: 0, minute: 40),
+      const TimeOfDay(hour: 0, minute: 45),
+      const TimeOfDay(hour: 0, minute: 50),
+      const TimeOfDay(hour: 0, minute: 55),
+    ];
+
+    return _buildPainters(textTheme, _minuteMarkerValues.map(localizations.formatMinute).toList());
+  }
+
   @override
   Widget build(BuildContext context) {
-    final ThemeData themeData = Theme.of(context);
-    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-
     Color backgroundColor;
     switch (themeData.brightness) {
       case Brightness.light:
@@ -1004,20 +1036,20 @@
     List<TextPainter> secondaryInnerLabels;
     switch (widget.mode) {
       case _TimePickerMode.hour:
-        if (widget.is24h) {
-          primaryOuterLabels = _init24HourOuterRing(theme.textTheme, localizations);
-          secondaryOuterLabels = _init24HourOuterRing(theme.accentTextTheme, localizations);
-          primaryInnerLabels = _init24HourInnerRing(theme.textTheme, localizations);
-          secondaryInnerLabels = _init24HourInnerRing(theme.accentTextTheme, localizations);
+        if (widget.use24HourDials) {
+          primaryOuterLabels = _build24HourOuterRing(theme.textTheme);
+          secondaryOuterLabels = _build24HourOuterRing(theme.accentTextTheme);
+          primaryInnerLabels = _build24HourInnerRing(theme.textTheme);
+          secondaryInnerLabels = _build24HourInnerRing(theme.accentTextTheme);
         } else {
-          primaryOuterLabels = _init12HourOuterRing(theme.textTheme, localizations);
-          secondaryOuterLabels = _init12HourOuterRing(theme.accentTextTheme, localizations);
+          primaryOuterLabels = _build12HourOuterRing(theme.textTheme);
+          secondaryOuterLabels = _build12HourOuterRing(theme.accentTextTheme);
         }
         break;
       case _TimePickerMode.minute:
-        primaryOuterLabels = _initMinutes(theme.textTheme, localizations);
+        primaryOuterLabels = _buildMinutes(theme.textTheme);
         primaryInnerLabels = null;
-        secondaryOuterLabels = _initMinutes(theme.accentTextTheme, localizations);
+        secondaryOuterLabels = _buildMinutes(theme.accentTextTheme);
         secondaryInnerLabels = null;
         break;
     }
@@ -1043,13 +1075,23 @@
   }
 }
 
+/// A material design time picker designed to appear inside a popup dialog.
+///
+/// Pass this widget to [showDialog]. The value returned by [showDialog] is the
+/// selected [TimeOfDay] if the user taps the "OK" button, or null if the user
+/// taps the "CANCEL" button. The selected time is reported by calling
+/// [Navigator.pop].
 class _TimePickerDialog extends StatefulWidget {
+  /// Creates a material time picker.
+  ///
+  /// [initialTime] must not be null.
   const _TimePickerDialog({
     Key key,
     @required this.initialTime
   }) : assert(initialTime != null),
        super(key: key);
 
+  /// The time initially selected when the dialog is shown.
   final TimeOfDay initialTime;
 
   @override
@@ -1106,8 +1148,10 @@
 
   @override
   Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat;
+    final MediaQueryData media = MediaQuery.of(context);
+    final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
 
     final Widget picker = new Padding(
       padding: const EdgeInsets.all(16.0),
@@ -1115,7 +1159,7 @@
         aspectRatio: 1.0,
         child: new _Dial(
           mode: _mode,
-          is24h: hourFormat(of: timeOfDayFormat) != HourFormat.h,
+          use24HourDials: hourFormat(of: timeOfDayFormat) != HourFormat.h,
           selectedTime: _selectedTime,
           onChanged: _handleTimeChanged,
         )
@@ -1222,7 +1266,7 @@
 }) async {
   assert(context != null);
   assert(initialTime != null);
-  return await showDialog(
+  return await showDialog<TimeOfDay>(
     context: context,
     child: new _TimePickerDialog(initialTime: initialTime),
   );
diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart
index f29e814..35474b9 100644
--- a/packages/flutter/lib/src/widgets/media_query.dart
+++ b/packages/flutter/lib/src/widgets/media_query.dart
@@ -40,7 +40,8 @@
     this.size: Size.zero,
     this.devicePixelRatio: 1.0,
     this.textScaleFactor: 1.0,
-    this.padding: EdgeInsets.zero
+    this.padding: EdgeInsets.zero,
+    this.alwaysUse24HourFormat: false,
   });
 
   /// Creates data for a media query based on the given window.
@@ -53,7 +54,8 @@
     : size = window.physicalSize / window.devicePixelRatio,
       devicePixelRatio = window.devicePixelRatio,
       textScaleFactor = window.textScaleFactor,
-      padding = new EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio);
+      padding = new EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
+      alwaysUse24HourFormat = window.alwaysUse24HourFormat;
 
   /// The size of the media in logical pixel (e.g, the size of the screen).
   ///
@@ -77,6 +79,19 @@
   /// The padding around the edges of the media (e.g., the screen).
   final EdgeInsets padding;
 
+  /// Whether to use 24-hour format when formatting time.
+  ///
+  /// The behavior of this flag is different across platforms:
+  ///
+  /// - On Android this flag is reported directly from the user settings called
+  ///   "Use 24-hour format". It applies to any locale used by the application,
+  ///   whether it is the system-wide locale, or the custom locale set by the
+  ///   application.
+  /// - On iOS this flag is set to true when the user setting called "24-Hour
+  ///   Time" is set or the system-wide locale's default uses 24-hour
+  ///   formatting.
+  final bool alwaysUse24HourFormat;
+
   /// The orientation of the media (e.g., whether the device is in landscape or portrait mode).
   Orientation get orientation {
     return size.width > size.height ? Orientation.landscape : Orientation.portrait;
diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart
index 863dcef..7e7ad2e 100644
--- a/packages/flutter/test/material/time_picker_test.dart
+++ b/packages/flutter/test/material/time_picker_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/rendering.dart';
@@ -29,7 +31,7 @@
                 onPressed: () async {
                   onChanged(await showTimePicker(
                     context: context,
-                    initialTime: const TimeOfDay(hour: 7, minute: 0)
+                    initialTime: const TimeOfDay(hour: 7, minute: 0),
                   ));
                 }
               );
@@ -41,17 +43,15 @@
   }
 }
 
-Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged,
-    { Locale locale: const Locale('en', 'US') }) async {
-  await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: locale,));
+Future<Offset> startPicker(WidgetTester tester, ValueChanged<TimeOfDay> onChanged) async {
+  await tester.pumpWidget(new _TimePickerLauncher(onChanged: onChanged, locale: const Locale('en', 'US')));
   await tester.tap(find.text('X'));
   await tester.pumpAndSettle(const Duration(seconds: 1));
   return tester.getCenter(find.byKey(const Key('time-picker-dial')));
 }
 
 Future<Null> finishPicker(WidgetTester tester) async {
-  final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
-  final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
+  final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(RaisedButton)));
   await tester.tap(find.text(materialLocalizations.okButtonLabel));
   await tester.pumpAndSettle(const Duration(seconds: 1));
 }
@@ -205,4 +205,72 @@
       expect(feedback.hapticCount, 3);
     });
   });
+
+  const List<String> labels12To11 = const <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
+  const List<String> labels12To11TwoDigit = const <String>['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11'];
+  const List<String> labels00To23 = const <String>['00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
+
+  Future<Null> mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async {
+    await tester.pumpWidget(
+      new Localizations(
+        locale: const Locale('en', 'US'),
+        delegates: <LocalizationsDelegate<dynamic>>[
+          DefaultMaterialLocalizations.delegate,
+          DefaultWidgetsLocalizations.delegate,
+        ],
+        child: new MediaQuery(
+          data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
+          child: new Directionality(
+            textDirection: TextDirection.ltr,
+            child: new Navigator(
+              onGenerateRoute: (RouteSettings settings) {
+                return new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
+                  showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0));
+                  return new Container();
+                });
+              },
+            ),
+          ),
+        ),
+      ),
+    );
+    // Pump once, because the dialog shows up asynchronously.
+    await tester.pump();
+  }
+
+  testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
+    await mediaQueryBoilerplate(tester, false);
+
+    final CustomPaint dialPaint = tester.widget(find.descendant(
+      of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
+      matching: find.byType(CustomPaint),
+    ));
+    final dynamic dialPainter = dialPaint.painter;
+    final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
+    expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
+    expect(dialPainter.primaryInnerLabels, null);
+
+    final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
+    expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
+    expect(dialPainter.secondaryInnerLabels, null);
+  });
+
+  testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
+    await mediaQueryBoilerplate(tester, true);
+
+    final CustomPaint dialPaint = tester.widget(find.descendant(
+      of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
+      matching: find.byType(CustomPaint),
+    ));
+    final dynamic dialPainter = dialPaint.painter;
+    final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
+    expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
+    final List<TextPainter> primaryInnerLabels = dialPainter.primaryInnerLabels;
+    expect(primaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
+
+    final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
+    expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
+    final List<TextPainter> secondaryInnerLabels = dialPainter.secondaryInnerLabels;
+    expect(secondaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
+  });
 }
diff --git a/packages/flutter/test/material/time_test.dart b/packages/flutter/test/material/time_test.dart
new file mode 100644
index 0000000..25bf7fe
--- /dev/null
+++ b/packages/flutter/test/material/time_test.dart
@@ -0,0 +1,29 @@
+// Copyright 2017 The Chromium 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/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('TimeOfDay.format', () {
+    testWidgets('respects alwaysUse24HourFormat option', (WidgetTester tester) async {
+      Future<String> pumpTest(bool alwaysUse24HourFormat) async {
+        String formattedValue;
+        await tester.pumpWidget(new MaterialApp(
+          home: new MediaQuery(
+            data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
+            child: new Builder(builder: (BuildContext context) {
+              formattedValue = const TimeOfDay(hour: 7, minute: 0).format(context);
+              return new Container();
+            }),
+          ),
+        ));
+        return formattedValue;
+      }
+
+      expect(await pumpTest(false), '7:00 AM');
+      expect(await pumpTest(true), '07:00');
+    });
+  });
+}
diff --git a/packages/flutter_localizations/lib/src/material_localizations.dart b/packages/flutter_localizations/lib/src/material_localizations.dart
index 4825451..e1fb7d2 100644
--- a/packages/flutter_localizations/lib/src/material_localizations.dart
+++ b/packages/flutter_localizations/lib/src/material_localizations.dart
@@ -141,8 +141,8 @@
   }
 
   @override
-  String formatHour(TimeOfDay timeOfDay) {
-    switch (hourFormat(of: timeOfDayFormat)) {
+  String formatHour(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
+    switch (hourFormat(of: timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat))) {
       case HourFormat.HH:
         return _twoDigitZeroPaddedFormat.format(timeOfDay.hour);
       case HourFormat.H:
@@ -188,7 +188,7 @@
   }
 
   @override
-  String formatTimeOfDay(TimeOfDay timeOfDay) {
+  String formatTimeOfDay(TimeOfDay timeOfDay, { bool alwaysUse24HourFormat: false }) {
     // Not using intl.DateFormat for two reasons:
     //
     // - DateFormat supports more formats than our material time picker does,
@@ -197,18 +197,20 @@
     // - DateFormat operates on DateTime, which is sensitive to time eras and
     //   time zones, while here we want to format hour and minute within one day
     //   no matter what date the day falls on.
-    switch (timeOfDayFormat) {
+    final String hour = formatHour(timeOfDay, alwaysUse24HourFormat: alwaysUse24HourFormat);
+    final String minute = formatMinute(timeOfDay);
+    switch (timeOfDayFormat(alwaysUse24HourFormat: alwaysUse24HourFormat)) {
       case TimeOfDayFormat.h_colon_mm_space_a:
-        return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)} ${_formatDayPeriod(timeOfDay)}';
+        return '$hour:$minute ${_formatDayPeriod(timeOfDay)}';
       case TimeOfDayFormat.H_colon_mm:
       case TimeOfDayFormat.HH_colon_mm:
-        return '${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
+        return '$hour:$minute';
       case TimeOfDayFormat.HH_dot_mm:
-        return '${formatHour(timeOfDay)}.${formatMinute(timeOfDay)}';
+        return '$hour.$minute';
       case TimeOfDayFormat.a_space_h_colon_mm:
-        return '${_formatDayPeriod(timeOfDay)} ${formatHour(timeOfDay)}:${formatMinute(timeOfDay)}';
+        return '${_formatDayPeriod(timeOfDay)} $hour:$minute';
       case TimeOfDayFormat.frenchCanadian:
-        return '${formatHour(timeOfDay)} h ${formatMinute(timeOfDay)}';
+        return '$hour h $minute';
     }
 
     return null;
@@ -328,7 +330,7 @@
   ///  * http://demo.icu-project.org/icu-bin/locexp?d_=en&_=en_US shows the
   ///    short time pattern used in locale en_US
   @override
-  TimeOfDayFormat get timeOfDayFormat {
+  TimeOfDayFormat timeOfDayFormat({ bool alwaysUse24HourFormat: false }) {
     final String icuShortTimePattern = _nameToValue['timeOfDayFormat'];
 
     assert(() {
@@ -343,7 +345,12 @@
       return true;
     }());
 
-    return _icuTimeOfDayToEnum[icuShortTimePattern];
+    final TimeOfDayFormat icuFormat = _icuTimeOfDayToEnum[icuShortTimePattern];
+
+    if (alwaysUse24HourFormat)
+      return _get24HourVersionOf(icuFormat);
+
+    return icuFormat;
   }
 
   /// Looks up text geometry defined in [MaterialTextGeometry].
@@ -403,6 +410,23 @@
   'ah:mm': TimeOfDayFormat.a_space_h_colon_mm,
 };
 
+/// Finds the [TimeOfDayFormat] to use instead of the `original` when the
+/// `original` uses 12-hour format and [MediaQueryData.alwaysUse24HourFormat]
+/// is true.
+TimeOfDayFormat _get24HourVersionOf(TimeOfDayFormat original) {
+  switch (original) {
+    case TimeOfDayFormat.HH_colon_mm:
+    case TimeOfDayFormat.HH_dot_mm:
+    case TimeOfDayFormat.frenchCanadian:
+    case TimeOfDayFormat.H_colon_mm:
+      return original;
+    case TimeOfDayFormat.h_colon_mm_space_a:
+    case TimeOfDayFormat.a_space_h_colon_mm:
+      return TimeOfDayFormat.HH_colon_mm;
+  }
+  return TimeOfDayFormat.HH_colon_mm;
+}
+
 /// Tracks if date i18n data has been loaded.
 bool _dateIntlDataInitialized = false;
 
diff --git a/packages/flutter_localizations/test/date_time_test.dart b/packages/flutter_localizations/test/date_time_test.dart
index a1caf27..4f282b1 100644
--- a/packages/flutter_localizations/test/date_time_test.dart
+++ b/packages/flutter_localizations/test/date_time_test.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
+
 import 'package:flutter/material.dart';
 import 'package:flutter_localizations/flutter_localizations.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -24,40 +26,44 @@
     });
 
     group('formatHour', () {
-      test('formats h', () {
-        GlobalMaterialLocalizations localizations;
+      Future<String> formatHour(WidgetTester tester, Locale locale, TimeOfDay timeOfDay) async {
+        final Completer<String> completer = new Completer<String>();
+        await tester.pumpWidget(new MaterialApp(
+          supportedLocales: <Locale>[locale],
+          locale: locale,
+          localizationsDelegates: <LocalizationsDelegate<dynamic>>[
+            GlobalMaterialLocalizations.delegate,
+          ],
+          home: new Builder(builder: (BuildContext context) {
+            completer.complete(MaterialLocalizations.of(context).formatHour(timeOfDay));
+            return new Container();
+          }),
+        ));
+        return completer.future;
+      }
 
-        localizations = new GlobalMaterialLocalizations(const Locale('en', 'US'));
-        expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '10');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '8');
+      testWidgets('formats h', (WidgetTester tester) async {
+        expect(await formatHour(tester, const Locale('en', 'US'), const TimeOfDay(hour: 10, minute: 0)), '10');
+        expect(await formatHour(tester, const Locale('en', 'US'), const TimeOfDay(hour: 20, minute: 0)), '8');
 
-        localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
-        expect(localizations.formatHour(const TimeOfDay(hour: 10, minute: 0)), '١٠');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '٨');
+        expect(await formatHour(tester, const Locale('ar', ''), const TimeOfDay(hour: 10, minute: 0)), '١٠');
+        expect(await formatHour(tester, const Locale('ar', ''), const TimeOfDay(hour: 20, minute: 0)), '٨');
       });
 
-      test('formats HH', () {
-        GlobalMaterialLocalizations localizations;
+      testWidgets('formats HH', (WidgetTester tester) async {
+        expect(await formatHour(tester, const Locale('de', ''), const TimeOfDay(hour: 9, minute: 0)), '09');
+        expect(await formatHour(tester, const Locale('de', ''), const TimeOfDay(hour: 20, minute: 0)), '20');
 
-        localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
-        expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
-
-        localizations = new GlobalMaterialLocalizations(const Locale('en', 'GB'));
-        expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '09');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
+        expect(await formatHour(tester, const Locale('en', 'GB'), const TimeOfDay(hour: 9, minute: 0)), '09');
+        expect(await formatHour(tester, const Locale('en', 'GB'), const TimeOfDay(hour: 20, minute: 0)), '20');
       });
 
-      test('formats H', () {
-        GlobalMaterialLocalizations localizations;
+      testWidgets('formats H', (WidgetTester tester) async {
+        expect(await formatHour(tester, const Locale('es', ''), const TimeOfDay(hour: 9, minute: 0)), '9');
+        expect(await formatHour(tester, const Locale('es', ''), const TimeOfDay(hour: 20, minute: 0)), '20');
 
-        localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
-        expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '9');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '20');
-
-        localizations = new GlobalMaterialLocalizations(const Locale('fa', ''));
-        expect(localizations.formatHour(const TimeOfDay(hour: 9, minute: 0)), '۹');
-        expect(localizations.formatHour(const TimeOfDay(hour: 20, minute: 0)), '۲۰');
+        expect(await formatHour(tester, const Locale('fa', ''), const TimeOfDay(hour: 9, minute: 0)), '۹');
+        expect(await formatHour(tester, const Locale('fa', ''), const TimeOfDay(hour: 20, minute: 0)), '۲۰');
       });
     });
 
@@ -74,48 +80,49 @@
     });
 
     group('formatTimeOfDay', () {
-      test('formats ${TimeOfDayFormat.h_colon_mm_space_a}', () {
-        GlobalMaterialLocalizations localizations;
+      Future<String> formatTimeOfDay(WidgetTester tester, Locale locale, TimeOfDay timeOfDay) async {
+        final Completer<String> completer = new Completer<String>();
+        await tester.pumpWidget(new MaterialApp(
+          supportedLocales: <Locale>[locale],
+          locale: locale,
+          localizationsDelegates: <LocalizationsDelegate<dynamic>>[
+            GlobalMaterialLocalizations.delegate,
+          ],
+          home: new Builder(builder: (BuildContext context) {
+            completer.complete(MaterialLocalizations.of(context).formatTimeOfDay(timeOfDay));
+            return new Container();
+          }),
+        ));
+        return completer.future;
+      }
 
-        localizations = new GlobalMaterialLocalizations(const Locale('ar', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
+      testWidgets('formats ${TimeOfDayFormat.h_colon_mm_space_a}', (WidgetTester tester) async {
+        expect(await formatTimeOfDay(tester, const Locale('ar', ''), const TimeOfDay(hour: 9, minute: 32)), '٩:٣٢ ص');
+        expect(await formatTimeOfDay(tester, const Locale('ar', ''), const TimeOfDay(hour: 20, minute: 32)), '٨:٣٢ م');
 
-        localizations = new GlobalMaterialLocalizations(const Locale('en', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
+        expect(await formatTimeOfDay(tester, const Locale('en', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32 AM');
+        expect(await formatTimeOfDay(tester, const Locale('en', ''), const TimeOfDay(hour: 20, minute: 32)), '8:32 PM');
       });
 
-      test('formats ${TimeOfDayFormat.HH_colon_mm}', () {
-        GlobalMaterialLocalizations localizations;
-
-        localizations = new GlobalMaterialLocalizations(const Locale('de', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
-
-        localizations = new GlobalMaterialLocalizations(const Locale('en', 'ZA'));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09:32');
+      testWidgets('formats ${TimeOfDayFormat.HH_colon_mm}', (WidgetTester tester) async {
+        expect(await formatTimeOfDay(tester, const Locale('de', ''), const TimeOfDay(hour: 9, minute: 32)), '09:32');
+        expect(await formatTimeOfDay(tester, const Locale('en', 'ZA'), const TimeOfDay(hour: 9, minute: 32)), '09:32');
       });
 
-      test('formats ${TimeOfDayFormat.H_colon_mm}', () {
-        GlobalMaterialLocalizations localizations;
+      testWidgets('formats ${TimeOfDayFormat.H_colon_mm}', (WidgetTester tester) async {
+        expect(await formatTimeOfDay(tester, const Locale('es', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32');
+        expect(await formatTimeOfDay(tester, const Locale('es', ''), const TimeOfDay(hour: 20, minute: 32)), '20:32');
 
-        localizations = new GlobalMaterialLocalizations(const Locale('es', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
-
-        localizations = new GlobalMaterialLocalizations(const Locale('ja', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '9:32');
+        expect(await formatTimeOfDay(tester, const Locale('ja', ''), const TimeOfDay(hour: 9, minute: 32)), '9:32');
+        expect(await formatTimeOfDay(tester, const Locale('ja', ''), const TimeOfDay(hour: 20, minute: 32)), '20:32');
       });
 
-      test('formats ${TimeOfDayFormat.frenchCanadian}', () {
-        GlobalMaterialLocalizations localizations;
-
-        localizations = new GlobalMaterialLocalizations(const Locale('fr', 'CA'));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
+      testWidgets('formats ${TimeOfDayFormat.frenchCanadian}', (WidgetTester tester) async {
+        expect(await formatTimeOfDay(tester, const Locale('fr', 'CA'), const TimeOfDay(hour: 9, minute: 32)), '09 h 32');
       });
 
-      test('formats ${TimeOfDayFormat.a_space_h_colon_mm}', () {
-        GlobalMaterialLocalizations localizations;
-
-        localizations = new GlobalMaterialLocalizations(const Locale('zh', ''));
-        expect(localizations.formatTimeOfDay(const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
+      testWidgets('formats ${TimeOfDayFormat.a_space_h_colon_mm}', (WidgetTester tester) async {
+        expect(await formatTimeOfDay(tester, const Locale('zh', ''), const TimeOfDay(hour: 9, minute: 32)), '上午 9:32');
       });
     });
   });
diff --git a/packages/flutter_localizations/test/time_picker_test.dart b/packages/flutter_localizations/test/time_picker_test.dart
index 8e770d2..8b70e49 100644
--- a/packages/flutter_localizations/test/time_picker_test.dart
+++ b/packages/flutter_localizations/test/time_picker_test.dart
@@ -48,8 +48,7 @@
 }
 
 Future<Null> finishPicker(WidgetTester tester) async {
-  final Element timePickerElement = tester.element(find.byElementPredicate((Element element) => element.widget.runtimeType.toString() == '_TimePickerDialog'));
-  final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(timePickerElement);
+  final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(RaisedButton)));
   await tester.tap(find.text(materialLocalizations.okButtonLabel));
   await tester.pumpAndSettle(const Duration(seconds: 1));
 }
@@ -58,11 +57,11 @@
   testWidgets('can localize the header in all known formats', (WidgetTester tester) async {
     // TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
     final Map<Locale, List<String>> locales = <Locale, List<String>>{
-      const Locale('en', 'US'): const <String>['hour h', 'string :', 'minute', 'period'], //'h:mm a'
-      const Locale('en', 'GB'): const <String>['hour HH', 'string :', 'minute'], //'HH:mm'
-      const Locale('es', 'ES'): const <String>['hour H', 'string :', 'minute'], //'H:mm'
-      const Locale('fr', 'CA'): const <String>['hour HH', 'string h', 'minute'], //'HH \'h\' mm'
-      const Locale('zh', 'ZH'): const <String>['period', 'hour h', 'string :', 'minute'], //'ah:mm'
+      const Locale('en', 'US'): const <String>['hour', 'string :', 'minute', 'period'], //'h:mm a'
+      const Locale('en', 'GB'): const <String>['hour', 'string :', 'minute'], //'HH:mm'
+      const Locale('es', 'ES'): const <String>['hour', 'string :', 'minute'], //'H:mm'
+      const Locale('fr', 'CA'): const <String>['hour', 'string h', 'minute'], //'HH \'h\' mm'
+      const Locale('zh', 'ZH'): const <String>['period', 'hour', 'string :', 'minute'], //'ah:mm'
     };
 
     for (Locale locale in locales.keys) {
@@ -77,7 +76,7 @@
         } else if (fragmentType == '_DayPeriodControl') {
           actual.add('period');
         } else if (fragmentType == '_HourControl') {
-          actual.add('hour ${widget.hourFormat.toString().split('.').last}');
+          actual.add('hour');
         } else if (fragmentType == '_StringFragment') {
           actual.add('string ${widget.value}');
         } else {
@@ -126,4 +125,72 @@
       }
     }
   });
+
+  const List<String> labels12To11 = const <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
+  const List<String> labels12To11TwoDigit = const <String>['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11'];
+  const List<String> labels00To23 = const <String>['00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
+
+  Future<Null> mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async {
+    await tester.pumpWidget(
+      new Localizations(
+        locale: const Locale('en', 'US'),
+        delegates: <LocalizationsDelegate<dynamic>>[
+          GlobalMaterialLocalizations.delegate,
+          DefaultWidgetsLocalizations.delegate,
+        ],
+        child: new MediaQuery(
+          data: new MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
+          child: new Directionality(
+            textDirection: TextDirection.ltr,
+            child: new Navigator(
+              onGenerateRoute: (RouteSettings settings) {
+                return new MaterialPageRoute<dynamic>(builder: (BuildContext context) {
+                  showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0));
+                  return new Container();
+                });
+              },
+            ),
+          ),
+        ),
+      ),
+    );
+    // Pump once, because the dialog shows up asynchronously.
+    await tester.pump();
+  }
+
+  testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
+    await mediaQueryBoilerplate(tester, false);
+
+    final CustomPaint dialPaint = tester.widget(find.descendant(
+      of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
+      matching: find.byType(CustomPaint),
+    ));
+    final dynamic dialPainter = dialPaint.painter;
+    final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
+    expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
+    expect(dialPainter.primaryInnerLabels, null);
+
+    final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
+    expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels12To11);
+    expect(dialPainter.secondaryInnerLabels, null);
+  });
+
+  testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
+    await mediaQueryBoilerplate(tester, true);
+
+    final CustomPaint dialPaint = tester.widget(find.descendant(
+      of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
+      matching: find.byType(CustomPaint),
+    ));
+    final dynamic dialPainter = dialPaint.painter;
+    final List<TextPainter> primaryOuterLabels = dialPainter.primaryOuterLabels;
+    expect(primaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
+    final List<TextPainter> primaryInnerLabels = dialPainter.primaryInnerLabels;
+    expect(primaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
+
+    final List<TextPainter> secondaryOuterLabels = dialPainter.secondaryOuterLabels;
+    expect(secondaryOuterLabels.map((TextPainter tp) => tp.text.text), labels00To23);
+    final List<TextPainter> secondaryInnerLabels = dialPainter.secondaryInnerLabels;
+    expect(secondaryInnerLabels.map((TextPainter tp) => tp.text.text), labels12To11TwoDigit);
+  });
 }