I'm looking for a way to set the maximun and minimun time pickable on an Android TimePickerDialog, and to change the default minute interval from 1 min to 5 min,

I thought that was an easy one, but I can't find a way !

Solution 1

THIS ANSWER IS OUTDATED

It will not work on Android 5 or higher.


This is an extension to fiddlers answer. Somehow the onTimeChangedListener is not set by TimePickerDialog in some Android Versions:

ex. http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2.1_r1.2/android/app/TimePickerDialog.java

The onTimeChanged methode therefor is never called! Best solution for this issue currently, is propably to code a custom TimePickerDialog with the min/max logic from fiddler.

The more ugly work around is this. (Adding the listener using reflections) I also update the title to show the currently selected time.

package com.mysuger.android.dialogs;

import java.lang.reflect.Field;
import java.text.DateFormat;
import java.util.Calendar;

import android.app.TimePickerDialog;
import android.content.Context;
import android.widget.TimePicker;

/**
 * A time dialog that allows setting a min and max time.
 * 
 */
public class RangeTimePickerDialog extends TimePickerDialog {

private int minHour = -1;
private int minMinute = -1;

private int maxHour = 25;
private int maxMinute = 25;

private int currentHour = 0;
private int currentMinute = 0;

private Calendar calendar = Calendar.getInstance();
private DateFormat dateFormat;


public RangeTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) {
    super(context, callBack, hourOfDay, minute, is24HourView);
    currentHour = hourOfDay;
    currentMinute = minute;
    dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);

    try {
        Class<?> superclass = getClass().getSuperclass();
        Field mTimePickerField = superclass.getDeclaredField("mTimePicker");
        mTimePickerField.setAccessible(true);
        TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this);
        mTimePicker.setOnTimeChangedListener(this);
    } catch (NoSuchFieldException e) {
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    }
}

public void setMin(int hour, int minute) {
    minHour = hour;
    minMinute = minute;
}

public void setMax(int hour, int minute) {
    maxHour = hour;
    maxMinute = minute;
}

@Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {

    boolean validTime = true;
    if (hourOfDay < minHour || (hourOfDay == minHour && minute < minMinute)){
        validTime = false;
    }

    if (hourOfDay  > maxHour || (hourOfDay == maxHour && minute > maxMinute)){
        validTime = false;
    }

    if (validTime) {
        currentHour = hourOfDay;
        currentMinute = minute;
    }

    updateTime(currentHour, currentMinute);
    updateDialogTitle(view, currentHour, currentMinute);
}

private void updateDialogTitle(TimePicker timePicker, int hourOfDay, int minute) {
    calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
    calendar.set(Calendar.MINUTE, minute);
    String title = dateFormat.format(calendar.getTime());
    setTitle(title);
}
 }

Solution 2

For lolipop and higher version of android you can use this modified RangeTimePickerDialog class

(From lolipop, Timepicker uses clock mode (material design) as default so that the old customclass will not work.we can change the mode to spinner for latest version and can reuse the class)

public class RangeTimePickerDialog extends TimePickerDialog {

    private int minHour = -1;
    private int minMinute = -1;

    private int maxHour = 25;
    private int maxMinute = 25;

    private int currentHour = 0;
    private int currentMinute = 0;

    private Calendar calendar = Calendar.getInstance();
    private DateFormat dateFormat;


    public RangeTimePickerDialog(Context context, int dialogTheme, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) {
        super(context, callBack, hourOfDay, minute, is24HourView);
        currentHour = hourOfDay;
        currentMinute = minute;
        dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);
        fixSpinner(context, hourOfDay, minute, is24HourView);

        try {
            Class<?> superclass = getClass().getSuperclass();
            Field mTimePickerField = superclass.getDeclaredField("mTimePicker");
            mTimePickerField.setAccessible(true);
            TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this);
            mTimePicker.setOnTimeChangedListener(this);
        } catch (NoSuchFieldException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
    }

    public void setMin(int hour, int minute) {
        minHour = hour;
        minMinute = minute;
    }

    public void setMax(int hour, int minute) {
        maxHour = hour;
        maxMinute = minute;
    }

    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {

        boolean validTime = true;
        if (hourOfDay < minHour || (hourOfDay == minHour && minute < minMinute)){
            validTime = false;
        }

        if (hourOfDay  > maxHour || (hourOfDay == maxHour && minute > maxMinute)){
            validTime = false;
        }

        if (validTime) {
            currentHour = hourOfDay;
            currentMinute = minute;
        }

        updateTime(currentHour, currentMinute);
        updateDialogTitle(view, currentHour, currentMinute);
    }

    private void updateDialogTitle(TimePicker timePicker, int hourOfDay, int minute) {
        calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
        calendar.set(Calendar.MINUTE, minute);
        String title = dateFormat.format(calendar.getTime());
        setTitle(title);
    }


    private void fixSpinner(Context context, int hourOfDay, int minute, boolean is24HourView) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // android:timePickerMode spinner and clock began in Lollipop
            try {
                // Get the theme's android:timePickerMode
                //two modes are available clock mode and spinner mode ... selecting spinner mode for latest versions
                final int MODE_SPINNER = 2;
                Class<?> styleableClass = Class.forName("com.android.internal.R$styleable");
                Field timePickerStyleableField = styleableClass.getField("TimePicker");
                int[] timePickerStyleable = (int[]) timePickerStyleableField.get(null);
                final TypedArray a = context.obtainStyledAttributes(null, timePickerStyleable, android.R.attr.timePickerStyle, 0);
                Field timePickerModeStyleableField = styleableClass.getField("TimePicker_timePickerMode");
                int timePickerModeStyleable = timePickerModeStyleableField.getInt(null);
                final int mode = a.getInt(timePickerModeStyleable, MODE_SPINNER);
                a.recycle();
                if (mode == MODE_SPINNER) {
                    TimePicker timePicker = (TimePicker) findField(TimePickerDialog.class, TimePicker.class, "mTimePicker").get(this);
                    Class<?> delegateClass = Class.forName("android.widget.TimePicker$TimePickerDelegate");
                    Field delegateField = findField(TimePicker.class, delegateClass, "mDelegate");
                    Object delegate = delegateField.get(timePicker);
                    Class<?> spinnerDelegateClass;
                    if (Build.VERSION.SDK_INT != Build.VERSION_CODES.LOLLIPOP) {
                        spinnerDelegateClass = Class.forName("android.widget.TimePickerSpinnerDelegate");
                    } else {

                        spinnerDelegateClass = Class.forName("android.widget.TimePickerClockDelegate");
                    }
                    if (delegate.getClass() != spinnerDelegateClass) {
                        delegateField.set(timePicker, null); // throw out the TimePickerClockDelegate!
                        timePicker.removeAllViews(); // remove the TimePickerClockDelegate views
                        Constructor spinnerDelegateConstructor = spinnerDelegateClass.getConstructor(TimePicker.class, Context.class, AttributeSet.class, int.class, int.class);
                        spinnerDelegateConstructor.setAccessible(true);
                        // Instantiate a TimePickerSpinnerDelegate
                        delegate = spinnerDelegateConstructor.newInstance(timePicker, context, null, android.R.attr.timePickerStyle, 0);
                        delegateField.set(timePicker, delegate); // set the TimePicker.mDelegate to the spinner delegate
                        // Set up the TimePicker again, with the TimePickerSpinnerDelegate
                        timePicker.setIs24HourView(is24HourView);
                        timePicker.setCurrentHour(hourOfDay);
                        timePicker.setCurrentMinute(minute);
                        timePicker.setOnTimeChangedListener(this);
                    }
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    private static Field findField(Class objectClass, Class fieldClass, String expectedName) {
        try {
            Field field = objectClass.getDeclaredField(expectedName);
            field.setAccessible(true);
            return field;
        } catch (NoSuchFieldException e) {} // ignore
        // search for it if it wasn't found under the expected ivar name
        for (Field searchField : objectClass.getDeclaredFields()) {
            if (searchField.getType() == fieldClass) {
                searchField.setAccessible(true);
                return searchField;
            }
        }
        return null;
    }
}

Solution 3

You can use this as a starting point.

I extended TimePickerDialog and added 2 methods setMin and setMax.

In the onTimeChanged method I check that the new time is valid with respect to the min/max times.

It still needs some polishing though...

public class BoundTimePickerDialog extends TimePickerDialog {

    private int minHour = -1, minMinute = -1, maxHour = 100, maxMinute = 100;

    private int currentHour, currentMinute;

    public BoundTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) {
        super(context, callBack, hourOfDay, minute, is24HourView);
    }

    public void setMin(int hour, int minute) {
        minHour = hour;
        minMinute = minute;
    }

    public void setMax(int hour, int minute) {
        maxHour = hour;
        maxMinute = minute;
    }

    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        super.onTimeChanged(view, hourOfDay, minute);

        boolean validTime;
        if(hourOfDay < minHour) {
            validTime = false;
        }
        else if(hourOfDay == minHour) {
            validTime = minute >= minMinute;
        }
        else if(hourOfDay == maxHour) {
            validTime = minute <= maxMinute;
        }
        else {
            validTime = true;
        }

        if(validTime) {
            currentHour = hourOfDay;
            currentMinute = minute;
        }
        else {
            updateTime(currentHour, currentMinute);
        }
    }
}

Solution 4

After Re-factored code of fiddler :

public class RangeTimePickerDialog extends TimePickerDialog {

    private int mMinHour = -1;
    private int mMinMinute = -1;
    private int mMaxHour = 100;
    private int mMaxMinute = 100;
    private int mCurrentHour;
    private int mCurrentMinute;

    public RangeTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, 
        int minute, boolean is24HourView) {
        super(context, callBack, hourOfDay, minute, is24HourView);
        mCurrentHour = hourOfDay;
        mCurrentMinute = minute;
        // Somehow the onTimeChangedListener is not set by TimePickerDialog
        // in some Android Versions, so, Adding the listener using
        // reflections
        try {
            Class<?> superclass = getClass().getSuperclass();
            Field mTimePickerField = superclass.getDeclaredField("mTimePicker");
            mTimePickerField.setAccessible(true);
            TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this);
            mTimePicker.setOnTimeChangedListener(this);
        } catch (NoSuchFieldException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        }
    }

    public void setMin(int hour, int minute) {
        mMinHour = hour;
        mMinMinute = minute;
    }

    public void setMax(int hour, int minute) {
        mMaxHour = hour;
        mMaxMinute = minute;
    }

    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        super.onTimeChanged(view, hourOfDay, minute);
        boolean validTime;
        if (((hourOfDay < mMinHour ) || (hourOfDay == mMinHour && minute < mMinMinute)) 
                || ((hourOfDay > mMaxHour) || (hourOfDay == mMaxHour && minute > mMaxMinute))) {
            validTime = false;
        } else {
            validTime = true;
        }
        if (validTime) {
            mCurrentHour = hourOfDay;
            mCurrentMinute = minute;
        } else {
            updateTime(mCurrentHour, mCurrentMinute);
        }
    }
}

Solution 5

There are no native methods to set the max/min time, but perhaps it works if you extend onTimeChanged or updateTime you can prevent the time from being set to a value outside of your bounds.

Solution 6

You can just use a custom TimePicker with reflect to set the values.

 @Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    try {
        Class<?> classForid = Class.forName("com.android.internal.R$id");
        Field field = classForid.getField("minute");

        NumberPicker mMinuteSpinner = (NumberPicker) findViewById(field.getInt(null));
        mMinuteSpinner.setMinValue(5);
        mMinuteSpinner.setMaxValue((60 / timeInterval) - 1);

        List<String> displayedValues = new ArrayList<>();
        for (int i = 0; i < 60; i += timeInterval)
            displayedValues.add(String.format("%02d", i));
        mMinuteSpinner.setDisplayedValues(displayedValues.toArray(new String[0]));

        mMinuteSpinner.setWrapSelectorWheel(true);

    } catch (Exception e) {
        e.printStackTrace();
    }

}

So you can set what values you want

Solution 7

Hope this could helpful. I used Joda time library.

public class RangeTimePicker extends TimePicker implements
        TimePicker.OnTimeChangedListener {

    private int mMinHour = -1;

    private int mMinMinute = -1;

    private int mMaxHour = 25;

    private int mMaxMinute = 61;

    private int mCurrentHour;

    private int mCurrentMinute;


    public RangeTimePicker(Context context) {
        super(context);
        setOnTimeChangedListener(this);
    }

    public RangeTimePicker(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnTimeChangedListener(this);
    }

    public RangeTimePicker(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setOnTimeChangedListener(this);
    }

    public void setMaxTime(int hourIn24, int minute) {
        mMaxHour = hourIn24;
        mMaxMinute = minute;

        LocalTime currentTime = LocalTime.now();
        LocalTime maxTime = new LocalTime(mMaxHour, mMaxMinute);

        if (currentTime.isAfter(maxTime)) {
            this.setCurrentHour(mCurrentHour = currentTime.getHourOfDay());
            this.setCurrentMinute(
                    mCurrentMinute = currentTime.getMinuteOfHour());
        }

    }

    public void setMinTime(int hourIn24, int minute) {
        mMinHour = hourIn24;
        mMinMinute = minute;

        LocalTime currentTime = LocalTime.now();
        LocalTime minTime = new LocalTime(mMinHour, mMinMinute);

        if (currentTime.isBefore(minTime)) {
            this.setCurrentHour(mCurrentHour = minTime.getHourOfDay());
            this.setCurrentMinute(mCurrentMinute = minTime.getMinuteOfHour());
        }
    }


    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        boolean validTime = true;
        if (hourOfDay < mMinHour || (hourOfDay == mMinHour
                && minute < mMinMinute)) {
            validTime = false;
        }

        if (hourOfDay > mMaxHour || (hourOfDay == mMaxHour
                && minute > mMaxMinute)) {
            validTime = false;
        }

        if (validTime) {
            mCurrentHour = hourOfDay;
            mCurrentMinute = minute;
        }

        setCurrentHour(mCurrentHour);
        setCurrentMinute(mCurrentMinute);

    }
}

Solution 8

Hope this work, It works perfectly with me...

Set maximum time for time picker.

        timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() {
        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {

            if (!mIsOnTimeChanged) {
                mIsOnTimeChanged = true;

                Calendar c = Calendar.getInstance();

                //get the selected day if it is before today allow AM and PM trick
                //else apply rules


                int selected_year = datePicker.getYear();
                int selected_month = datePicker.getMonth();
                int selected_day = datePicker.getDayOfMonth();


                int today = c.get(Calendar.DAY_OF_MONTH);
                int year = c.get(Calendar.YEAR);
                int month = c.get(Calendar.MONTH);


                int hour = c.get(Calendar.HOUR);
                hour += 12;
                int min = c.get(Calendar.MINUTE);

                //Check if user enter hh | mm | more than current reset the timePicker with the current time

                //http://stackoverflow.com/questions/2659954/timepickerdialog-and-am-or-pm

                if ((hourOfDay == hour | hour < hourOfDay | hourOfDay > hour | hour > hourOfDay | minute > min) && (selected_month == month && selected_day == today && selected_year == year)) {

                    int AM_PM_Value = hourOfDay;

                    Calendar datetime = Calendar.getInstance();

                    int hour_without_24 = (hour - 12);
                    if ((datetime.get(Calendar.AM_PM) == Calendar.AM) && (hourOfDay > hour_without_24)) {
                        //set AM
                        timePicker.setCurrentHour(hour - 12);
                    }

                    if (hourOfDay == (hour - 12) && minute > min) {
                        timePicker.setCurrentMinute(min);

                    }

                    if (hourOfDay > hour && datetime.get(Calendar.AM_PM) != Calendar.AM) {
                        timePicker.setCurrentHour(hour);

                        if (minute > min) {
                            timePicker.setCurrentMinute(min);

                        }
                    }


                } else if (selected_month != month | selected_day != today | selected_year != year) {

                } else if (hourOfDay == hour && minute > min) {
                    timePicker.setCurrentMinute(min);
                } else if (hourOfDay > hour) {
                    timePicker.setCurrentHour(hour);

                    if (minute > min) {
                        timePicker.setCurrentMinute(min);

                    }
                }
                //
                isTimePickerChanged = true;
                String AM_PM;
                if (hourOfDay < 12) {
                    AM_PM = "AM";
                } else {
                    AM_PM = "PM";
                }

                //to set as 12 hour
                time_format = (hourOfDay + ":" + minute + ":" + "00");

                if (hourOfDay > 12) {
                    hourOfDay = (hourOfDay - 12);
                }

                st_time = (hourOfDay + ":" + pad(minute) + " " + AM_PM);

                mIsOnTimeChanged = false;

            }


        }
    });

Solution 9

I took @Zarooka 's solution as a starting point. I think I found a bug though. If you had a starting time which was already out-of-range, then you could not move it anymore, because any "onChanged" was being rejected. That's why I extended the setMax and setMin in order to go to a valid range when setting the mins/maxs.

package de.appdream.garooda.view;

import java.util.Calendar;
import java.util.Date;

import android.app.TimePickerDialog;
import android.content.Context;
import android.text.format.DateFormat;
import android.widget.TimePicker;

import java.lang.reflect.Field; 


public class GaroodaTimePickerDialog extends TimePickerDialog {

private int minHour = -1;
private int minMinute = -1;

private int maxHour = 25;
private int maxMinute = 61;

private int currentHour = 0;
private int currentMinute = 0;

    private Calendar calendar = Calendar.getInstance();
    //private DateFormat dateFormat;

public GaroodaTimePickerDialog(Context context, OnTimeSetListener callBack, int hour, int minute,
        boolean is24HourView) {
    super(context, callBack, hour, minute, is24HourView);

    currentHour = hour;
    currentMinute = minute;
    //dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT);

    try {
        Class<?> superclass = getClass().getSuperclass();
        Field mTimePickerField = superclass.getDeclaredField("mTimePicker");
        mTimePickerField.setAccessible(true);
        TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this);
        mTimePicker.setOnTimeChangedListener(this);
    } catch (NoSuchFieldException e) {
    } catch (IllegalArgumentException e) {
    } catch (IllegalAccessException e) {
    }
}







public void setMin(int hour, int minute) {
    minHour = hour;
    minMinute = minute;

    Calendar min = Calendar.getInstance();
    Calendar existing = Calendar.getInstance();

    min.set(Calendar.HOUR_OF_DAY, minHour);
    min.set(Calendar.MINUTE, minMinute);

    existing.set(Calendar.HOUR_OF_DAY, currentHour);
    existing.set(Calendar.MINUTE, currentMinute);

    if (existing.before(min)) {
        currentHour = minHour;
        currentMinute = minMinute;
        updateTime(currentHour, currentMinute);
    }
}

public void setMax(int hour, int minute) {
    maxHour = hour;
    maxMinute = minute;

    Calendar max = Calendar.getInstance();
    Calendar existing = Calendar.getInstance();

    max.set(Calendar.HOUR_OF_DAY, maxHour);
    max.set(Calendar.MINUTE, maxMinute);

    existing.set(Calendar.HOUR_OF_DAY, currentHour);
    existing.set(Calendar.MINUTE, currentMinute);

    if (existing.after(max)) {
        currentHour = maxHour;
        currentMinute = maxMinute;
        updateTime(currentHour, currentMinute);
    }

}

@Override
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {

    boolean validTime = true;
    if (hourOfDay < minHour || (hourOfDay == minHour && minute < minMinute)){
        validTime = false;
    }

    if (hourOfDay  > maxHour || (hourOfDay == maxHour && minute > maxMinute)){
        validTime = false;
    }

    if (validTime) {
        currentHour = hourOfDay;
        currentMinute = minute;
    }

    updateTime(currentHour, currentMinute);
    updateDialogTitle(view, currentHour, currentMinute);
}

private void updateDialogTitle(TimePicker timePicker, int hourOfDay, int minute) {
    calendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
    calendar.set(Calendar.MINUTE, minute);
    //String title = dateFormat.format(calendar.getTime());
    //setTitle(title);
}
}