Jump to content
Chris Pim

TTimeEdit picker time format

Recommended Posts

Hi everyone

 

Does anyone know if it's possible to force the time picker of TTimeEdit to be 12 or 24 hours?

I mean the actual picker in iOS itself, not the editor box (which you can manipulate using the "Format" property).

 

By default, the picker matches the time format set in the phone settings, but I have a separate time format setting in my app which determines whether we show am/pm or 24 hour format in the app and I'm trying to make the experience consistent for my users.

 

Thanks

Share this post


Link to post
7 hours ago, Chris Pim said:

Does anyone know if it's possible to force the time picker of TTimeEdit to be 12 or 24 hours?

Since you mention iOS:

uses
  Macapi.ObjCRuntime, Macapi.Helpers,
  iOSapi.UIKit, iOSapi.Helpers, iOSapi.Foundation;

function FindUIDatePicker(const AView: UIView; out ADatePicker: UIDatePicker): Boolean;
var
  I: Integer;
  LSubView: UIView;
begin
  Result := False;
  if AView.subviews.count > 0 then
  begin
    for I := 0 to AView.subviews.count - 1 do
    begin
      LSubView := TUIView.Wrap(AView.subviews.objectAtIndex(I));
      if LSubView.isKindOfClass(objc_getClass('UIDatePicker')) then
      begin
        Result := True;
        ADatePicker := TUIDatePicker.Wrap(AView.subviews.objectAtIndex(I));
        Break;
      end
      else if FindUIDatePicker(LSubView, ADatePicker) then
      begin
        Result := True;
        Break;
      end;
    end;
  end;
end;

function GetLocale(const AIs24Hour: Boolean): NSLocale;
var
  LIdent: NSString;
begin
  if AIs24Hour then
    LIdent := StrToNSStr('en_GB') // I say, old chap
  else
    LIdent := StrToNSStr('en_US'); // Yeehaw!
  Result := TNSLocale.Wrap(TNSLocale.OCClass.localeWithLocaleIdentifier(LIdent));
end;

procedure TForm1.TimeEdit1OpenPicker(Sender: TObject);
var
  LDatePicker: UIDatePicker;
begin
  if FindUIDatePicker(TiOSHelper.SharedApplication.keyWindow.rootViewController.view, LDatePicker) then
    LDatePicker.setLocale(GetLocale(True)); // or False, for 12 hour
  // else it was not found
end;

FindUIDatePicker iterates the native view hierarchy looking for the UIDatePicker. The alternative would have been to "hack into" FMX.Pickers.iOS. 

 

Note: By default, the locale property of UIDatePicker is nil, which means it uses whatever the device setting is (Settings > General > Date & Time). If a locale is assigned, it uses the default time mode for that locale. As long as the default time mode does not change for the two used in the code, it should remain working.

 

 

Share this post


Link to post

That’s an interesting solution! Thanks Dave, I’ll give that a go for iOS.

For Android, the time picker native component supports a Boolean for the time format so I just need to check how I can get access without hacking the sources.

Share this post


Link to post
3 hours ago, Chris Pim said:

For Android, the time picker native component supports a Boolean for the time format so I just need to check how I can get access without hacking the sources.

Sadly, for Android you'd need to "reinvent" the TimePickerFragment class in the Java source (for fmx.jar) located in source\rtl\androiddex\java\fmx\src\com\embarcadero\firemonkey\pickers\defaults\TimePickerFragment.java.

 

In onCreateDialog, it returns a new instance of TimePickerDialog, passing DateFormat.is24HourFormat for the is24HourView flag. This value cannot be changed afterwards, so it'd be pointless to even find the TimePickerDialog instance at runtime (if it were possible), and it won't be possible to change what value is passed without altering the Java source, which means rebuilding fmx.jar (ugh).

 

If it were me, for Android I'd forget TTimeEdit, and implement my own time picker in Java.

 

EDIT:

 

Actually, I've since discovered there is a TimePicker involved, so it might be possible to traverse the view hierarchy as was done for iOS, to find it and call setIs24HourView.

Edited by Dave Nottage

Share this post


Link to post
1 hour ago, Dave Nottage said:

Actually, I've since discovered there is a TimePicker involved, so it might be possible to traverse the view hierarchy as was done for iOS, to find it and call setIs24HourView.

I was right, but it took a bit of hoop jumping:

uses
  Androidapi.JNIBridge, Androidapi.JNI.Widget, Androidapi.Helpers, Androidapi.JNI.GraphicsContentViewText, Androidapi.JNI.JavaTypes,
  Androidapi.JNI.App;

function FindTimePickerFragment(out AFragment: JDialogFragment): Boolean;
var
  LFragmentManager: JFragmentManager;
  I: Integer;
  LFragment: JFragment;
begin
  LFragmentManager := TAndroidHelper.Activity.getFragmentManager;
  for I := 0 to LFragmentManager.getFragments.size - 1 do
  begin
    LFragment := TJFragment.Wrap(LFragmentManager.getFragments.get(I));
    if JStringToString(LFragment.getClass.getName).EndsWith('.TimePickerFragment') then
    begin
      AFragment := TJDialogFragment.Wrap(LFragment);
      Result := True;
      Break;
    end;
  end;
end;

function FindTimePicker(const AView: JView; out ATimePicker: JTimePicker): Boolean;
var
  I: Integer;
  LSubView: JView;
  LViewGroup: JViewGroup;
begin
  Result := False;
  if TJNIResolver.IsInstanceOf(AView, TJViewGroup.GetClsID) then
  begin
    LViewGroup := TJViewGroup.Wrap(AView);
    for I := 0 to LViewGroup.getChildCount - 1 do
    begin
      LSubView := LViewGroup.getChildAt(I);
      if TJNIResolver.IsInstanceOf(LSubView, TJTimePicker.GetClsID) then
      begin
        Result := True;
        ATimePicker := TJTimePicker.Wrap(LSubView);
        Break;
      end
      else if FindTimePicker(LSubView, ATimePicker) then
      begin
        Result := True;
        Break;
      end;
    end;
  end;
end;

procedure TForm1.TimeEdit1OpenPicker(Sender: TObject);
var
  LTimePicker: JTimePicker;
  LFragment: JDialogFragment;
begin
  if FindTimePickerFragment(LFragment) and FindTimePicker(LFragment.getDialog.getWindow.getDecorView, LTimePicker) then
    LTimePicker.setIs24HourView(TJBoolean.JavaClass.init(True)); // or False
end;

 

Share this post


Link to post

I've just put your code into practice and it seems to work very well. Thanks again Dave.

The only oddity I noticed, is that if the phone (Android) is set to 24 hour format, and I use the above to show my picker in 12 hour format, the AM/PM toggle doesn't appear in the picker dialog, even though the dialog is in 12 hours format.

If the phone is set to 12 hour before showing the dialog, then it does show the AM/PM toggle correctly.

Weird!

 

I suspect this is a bug in Android as there aren't any APIs to directly impact the AM/PM option so just noting here in case anyone else needs this functionality.

 

Thanks again Dave

  • Thanks 1

Share this post


Link to post
12 hours ago, Chris Pim said:

I suspect this is a bug in Android as there aren't any APIs to directly impact the AM/PM option so just noting here in case anyone else needs this functionality.

Sounds like it. Thanks for the heads up!

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×