3

I want to convert a time in the below format into hours.

My input which is a time format could be anything like

1 hour 30 mins 20 secs 
2 hrs 10 mins 
45 mins 

whereas my output will be:

1.505
2.167
0.75

These are the hours.

Currently I am doing it manually by parsing the input string 1 hour 30 mins 20 secs, finding whether the word hour/hours/hrs/hr is present, then taking the preceding digits--the same for mins and secs. I use the formula to convert mins and secs to hours manually.

Is there any built-in provision that I can use in Java?

Tiya
  • 553
  • 8
  • 26
  • There is a thrird party library **JNLP** for natural language processing but that would be overkill. – Joop Eggen Jun 05 '17 at 20:31
  • Have you checked Joda-time? It has period parsing capabilities that include this possibility. – RealSkeptic Jun 05 '17 at 20:36
  • 1
    Using decimals to represent a span of time in hours is unwise. I suggest you look at the [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601#Durations) standard formats for durations, and look at the java.time class [`Duration`](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html). `java.time.Duration.parse( "PT1H30M20S" )` – Basil Bourque Jun 06 '17 at 01:22

3 Answers3

3

An alternative way would be to parse it as a Duration, but you will need to change the format a bit first. Something like:

String adjusted = input.replaceAll("\\s+(hour|hr)[s]\\s+", "H");
//Do the same for minutes and seconds
//So adjusted looks like 1H30M20S
Duration d = Duration.parse("PT" + adjusted);

double result = d.toMillis() / 3_600_000d;
assylias
  • 321,522
  • 82
  • 660
  • 783
  • Didn't you mean `(hour|hr)` instead of `[hour|hr]`? The regex with `[]` doesn't match the whole string "hour" or "hr" –  Jun 05 '17 at 21:58
  • 1
    Yes indeed, that's what happens when you write code on a mobile without a compiler! – assylias Jun 06 '17 at 07:21
2

Usually I wouldn't recommend Joda-Time (as it's being replaced by the new Java API's), but it's the only API I know with a good formatter/parser for periods.

You can use the PeriodFormatterBuilder class and use appendSuffix method to define the suffixes for singular and plural values for each field:

import org.joda.time.Period;
import org.joda.time.format.PeriodFormatter;
import org.joda.time.format.PeriodFormatterBuilder;

// method to parse the period
public void getPeriod(String input) {
    PeriodFormatter formatter = new PeriodFormatterBuilder()
            // hours (singular and plural suffixes)
            .appendHours().appendSuffix("hour", "hours")
            // minutes
            .appendMinutes().appendSuffix("min", "mins")
            // seconds
            .appendSeconds().appendSuffix("sec", "secs")
            // create formatter
            .toFormatter();

    // remove spaces and change "hr" to "hour"
    Period p = formatter.parsePeriod(input.replaceAll(" ", "").replaceAll("hr", "hour")); 

    double hours = p.getHours();
    hours += p.getMinutes() / 60d;
    hours += p.getSeconds() / 3600d;
    System.out.println(hours);
}

// tests
getPeriod("1 hour 30 mins 20 secs");
getPeriod("2 hrs 10 mins");
getPeriod("45 mins");

Output:

1.5055555555555555
2.1666666666666665
0.75


Another way of creating the PeriodFormatter is using appendSuffix with regular expressions. It's useful when you have lots of different options for the suffix (like hour and hr for the hours field):

PeriodFormatter formatter = new PeriodFormatterBuilder()
    // hours (all possible suffixes for singular and plural)
    .appendHours()
    .appendSuffix(
        // regular expressions for singular and plural
        new String[] { "^1$", ".*", "^1$", ".*" },
        // possible suffixes for singular and plural
        new String[] { " hour", " hours", " hr", " hrs" })
    // optional space (if there are more fields)
    .appendSeparatorIfFieldsBefore(" ")
    // minutes
    .appendMinutes().appendSuffix(" min", " mins")
    // optional space (if there are more fields)
    .appendSeparatorIfFieldsBefore(" ")
    // seconds
    .appendSeconds().appendSuffix(" sec", " secs")
    // create formatter
    .toFormatter();

Note that I've also added appendSeparatorIfFieldsBefore(" ") to indicate that it has a space before the next field.

The good thing about this version is that you don't need to pre-process the input:

// no need to call replaceAll (take input just as it is)
Period p = formatter.parsePeriod(input);

The output is the same as above.


Java 8 date-time API

As stated in @assylian's answer, you can use the java.time.Duration class:

public void getDuration(String input) {
    // replace hour/min/secs strings for H, M and S
    String adjusted = input.replaceAll("\\s*(hour|hr)s?\\s*", "H");
    adjusted = adjusted.replaceAll("\\s*mins?\\s*", "M");
    adjusted = adjusted.replaceAll("\\s*secs?\\s*", "S");
    Duration d = Duration.parse("PT" + adjusted);

    double hours = d.toMillis() / 3600000d;
    System.out.println(hours);
}

//tests
getDuration("1 hour 30 mins 20 secs");
getDuration("2 hrs 10 mins");
getDuration("45 mins");

The output is the same.

PS: if your Java version is <= 7, you can use the ThreeTen Backport. The class name and methods are the same, the only difference is the package name: org.threeten.bp instead of java.time.

0

Currently I'm doing like this :

private double convertToHours(String inputTime) {
    inputTime = " 1 hour 30 mins 20 secs";
    double hours = 0;
    List<String> timeList = Arrays.asList(inputTime.split(" "));
    ListIterator<String> iterator = timeList.listIterator();
    String time = null;
    String previous = null;
    while (iterator.hasNext()) {
        int prevIndex = iterator.previousIndex();
        time = (String) iterator.next();

        if (!time.isEmpty() && time != null) {

            if (time.contains("hours") || time.contains("hrs") || time.contains("hr") || time.contains("hour")) {
                // time = time.substring(0, time.indexOf('h'));
                    previous = timeList.get(prevIndex);

                hours = hours + Double.parseDouble(previous.trim());
            }

            if (time.contains("mins") || time.contains("min") || time.contains("mns")) {
                //time = time.substring(0, time.indexOf('m'));
                previous = timeList.get(prevIndex);
                hours = hours + Double.parseDouble(previous) / 60;
            }

            if (time.contains("secs") || time.contains("sec") || time.contains("scs")) {
                //time = time.substring(0, time.indexOf('s'));
                previous = timeList.get(prevIndex);
                hours = hours + Double.parseDouble(previous) / 3600;
            }
        }
    }
    return hours;
}
Tiya
  • 553
  • 8
  • 26