oyeraghib
oyeraghib

Reputation: 1073

Index out bounds exception when storing time in milliseconds [Android]

I am storing time in Milliseconds using a Material Time Picker Dialog. The problem is if I choose a time instance which is like a single valued hour (between 1-9) or single valued minute(1-9) then I am able to get the value in milliseconds. But if I set the bigger values of time I get an exception java.lang.ArrayIndexOutOfBoundsException: length=17; index=20

What am I doing wrong here? Or what is the better approach to do this.

Log for reference :

Image

My Code (Not the complete code but what is required for this question) :

 //Time Picker
    var picker: MaterialTimePicker = MaterialTimePicker()

    //Long values to be stored in database
    val timeStart: Long = 0


 //Setting Start Time
        binding.timeStart.setOnClickListener {
            openTimePicker()
            picker.addOnPositiveButtonClickListener {
                val h = picker.hour
                val m = picker.minute
                binding.timeStart.text = "$h:$m"
                Timber.d("Start Time - h: $h m: $m")
                try {
                    val calendar = Calendar.getInstance()
                    calendar.get(h)
                    calendar.get(m)
                    val timeInMilliSeconds = calendar.timeInMillis
                    Timber.d("$timeInMilliSeconds")
                } catch (e: Exception) {
                    Timber.d("$e")
                }
            }
        }

 private fun openTimePicker() {
        val isSystem24Hour = is24HourFormat(requireContext())
        val clockFormat = if (isSystem24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H

        picker = MaterialTimePicker.Builder()
            .setTimeFormat(clockFormat)
            .setHour(12)
            .setMinute(10)
            .setTitleText("Set Start Time")
            .build()
        picker.show(childFragmentManager, "TAG")
    }

Upvotes: 0

Views: 185

Answers (2)

cactustictacs
cactustictacs

Reputation: 19602

It's happening here:

val h = picker.hour
val m = picker.minute
binding.timeStart.text = "$h:$m"
Timber.d("Start Time - h: $h m: $m")
try {
    val calendar = Calendar.getInstance()
    calendar.get(h)
    calendar.get(m)
    val timeInMilliSeconds = calendar.timeInMillis
    Timber.d("$timeInMilliSeconds")
}

Specifically when you call calendar.get()

public int get (int field)

Returns the value of the given calendar field Throws ArrayIndexOutOfBoundsException if the specified field is out of range (field < 0 || field >= FIELD_COUNT)

public static final int FIELD_COUNT

The number of distinct fields recognized by get and set. Field numbers range from 0..FIELD_COUNT-1

Constant Value: 17

That's why your exception is complaining that the array has 17 items and you're trying to access index 20, because you're passing values representing hours and minutes instead of field constants.

I'm not sure what you want to do exactly, but look at the set or add methods in the Calendar class, if you want to specify an exact time or you want to add x hours and y minutes to the current time. You'll need to use the field constants to specify which value you're changing, e.g. HOUR_OF_DAY


edit Ok, so I think there's a bit of confusion about what Calendar does, and you've explained what you're trying to do, so I'll show you a few options you have.

I'm not sure which of these you want to do though - when the user enters a time on the clock, do you:

  • treat them as a number of hours and minutes, total them up and convert to milliseconds
  • treat them as a time, and work out how many milliseconds in the future that is relative to the current time

Here's how you can handle both of those things:

Total hours + mins, in millis

// basic calculation - 1000s in 1ms, 60s in 1m, 60m in 1h
val minsAsMillis = m * 60 * 1000
val hoursAsMillis = h * 60 * 60 * 1000
val totalMillis = minsAsMillis + hoursAsMillis

// or use a utility class like Duration, TimeUnit etc
import java.util.concurrent.TimeUnit
val millis = TimeUnit.HOURS.toMillis(hours)
    + TimeUnit.MINUTES.toMillis(mins)

// the java.time classes require desugaring on APIs earlier than 26 I think - see below
import java.time.Duration
val millis = Duration.ofMinutes(m.toLong())
    .plusHours(hours.toLong())
    .toMillis()

Future time, calculate millis from now til then

This one's trickier, because it involves getting now as a clock time, which relates to things like the user's current locale, the time of year (for things like daylight savings changes) and so on. This is what Calendar is for, working with times and dates.

There are a lot of utility classes around time, and I'm not super-familiar with it all, so this might not be the best way to do this - but here's something that should work:

// implies the system default locale and time-zone - you can provide those if necessary
val now = Calendar.getInstance()
val future = Calendar.getInstance()
// set the future time
future.set(Calendar.HOUR_OF_DAY, h)
future.set(Calendar.MINUTE, m)
// need to clear the millis field (since it's based off the current time)
future.set(Calendar.MILLIS, 0)

// since the user's setting a clock time, that could be -earlier- than the
// current time (e.g. it's 5pm and they've chosen 10:50am). Assuming that
// should be treated as -tomorrow-, we might need to add a day

// using !after so it also matches -right now-, just in case
if (!future.after(now)) future.add(Calendar.HOUR_OF_DAY, 24)

Now you have your two Calendars (which represent a specific date and time remember) you can work out the difference between them. You were calling getTimeInMillis() before - because a Calendar really just represents a point in time, that method gives you the number of milliseconds since the epoch, i.e. the start of the year 1970. Probably a bigger number than you were expecting!

But that's a useful number to have (e.g. the current time is System.currentTimeInMillis()) because when you have two of them, you can compare the distance between them. So we can do this:

val durationInMillis = future.getTimeInMillis() - now.getTimeInMillis()

There are some other classes like LocalDateTime, Instant, Duration etc that might be useful (and even preferable, this seems like a fairly simple use-case though) but you might have to enable desugaring to get them on earlier APIs since they're Java 8+ features. Info here if you want to look into that

Upvotes: 1

oyeraghib
oyeraghib

Reputation: 1073

A better approach to get the time in milliseconds using TimeUnit instead of Calendar. I used TimeUnit.HOURS.toMillis(myHours) and TimeUnit.MINUTES.toMillis(myMinutes) to get my hours and minutes in milliseconds and then add them to get the time in milliseconds.

Code :

binding.timeStart.setOnClickListener {
            openTimePicker()
            picker.addOnPositiveButtonClickListener {
                val h = picker.hour
                val m = picker.minute
                binding.timeStart.text = "$h:$m"
                Timber.d("Start Time - $h: $m")
                try {
                    val hour = TimeUnit.HOURS.toMillis(h.toLong())
                    val minute = TimeUnit.MINUTES.toMillis(m.toLong())
                    val totalTime = hour + minute

                    Timber.d("Hour - $hour, Minute - $minute, Total = $totalTime")
                    timeStart = totalTime

                } catch (e: Exception) {
                    Timber.d("$e")
                }
            }
        }

    private fun openTimePicker() {
        picker = MaterialTimePicker.Builder()
            .setTimeFormat(TimeFormat.CLOCK_12H)
            .setHour(12)
            .setMinute(10)
            .setTitleText("Set Start Time")
            .build()
        picker.show(childFragmentManager, "TAG")
    }

Upvotes: 0

Related Questions