Expiredmind
Expiredmind

Reputation: 808

SimpleDateFormat sometimes wrong hours formatting

I want to convert my timestamp into XXhours:YYminutes string. My approach sometimes works correctly unless it’s obtaining greater values. For example, the

193500000 -> 05hours:45minutes (Wrong, correct is 53hours:45minutes)
60300000 -> 16hours:45minutes (correct)
63900000 -> 17hours:45minutes (correct)
108000000-> 06hours:00minutes (Wrong, correct is 30hours:00minutes)
117000000 -> 08hours:30minutes (Wrong, correct is 32hours:30minutes)

    SimpleDateFormat dateFormat = new SimpleDateFormat("HH'hours' mm'minutes'");
    dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT"));
    String string  = dateFormat.format(totalTime);

Upvotes: 2

Views: 1975

Answers (6)

Arvind Kumar Avinash
Arvind Kumar Avinash

Reputation: 79015

SimpleDateFormat is the wrong class for this requirement

Your requirement is to calculate the duration instead of a date-time. SimpleDateFormat or java.time.format.DateTimeFormatter (part of the modern date-time API) should be used to represent a date/time/date-time i.e. something which represents a point in time instead of a period/duration of time. A thumb rule to remember this is:

  1. Use SimpleDateFormat or java.time.format.DateTimeFormatter for something which you refer with since in English grammar Tense.
  2. Use Period and Duration for something which you refer with for in English grammar Tense.

Check The Difference between Since and For – English grammar

In order to get the duration from milliseconds, you can use java.time.Duration which is modelled on ISO-8601 standards and was introduced with Java-8 as part of JSR-310 implementation. With Java-9 some more convenience methods were introduced.

Demo:

import java.time.Duration;

public class Main {
    public static void main(String[] args) {
        Duration duration = Duration.ofMillis(193500000);
        // Default format
        System.out.println(duration);

        // Custom format
        // ####################################Java-8####################################
        String formattedElapsedTime = String.format("%02d hours %02d minutes", duration.toHours(),
                duration.toMinutes() % 60);
        System.out.println(formattedElapsedTime);
        // ##############################################################################

        // ####################################Java-9####################################
        formattedElapsedTime = String.format("%02d hours %02d minutes", duration.toHours(), duration.toMinutesPart());
        System.out.println(formattedElapsedTime);
        // ##############################################################################
    }
}

Output:

PT53H45M
53 hours 45 minutes
53 hours 45 minutes

Learn about the modern date-time API from Trail: Date Time.

Upvotes: 1

Anonymous
Anonymous

Reputation: 86223

The answer by talex is correct that you should use a Duration for this. In Java 9 formatting the Duration has become a bit easier and more straightforward to write and read:

public static String formatDuration(long totalTimeMillis) {
    Duration totalDuration = Duration.ofMillis(totalTimeMillis);
    return String.format(Locale.ENGLISH,
            "%02d hours %02d minutes",
            totalDuration.toHours(),
            totalDuration.toMinutesPart());
}

We no longer need the modulo operation (or similar) for finding the minutes. To demonstrate I called the above method using your time values from the question:

    System.out.println(formatDuration(60300000));
    System.out.println(formatDuration(63900000));
    System.out.println(formatDuration(108000000));
    System.out.println(formatDuration(117000000));
    System.out.println(formatDuration(193500000));

It prints the results you asked for:

16 hours 45 minutes
17 hours 45 minutes
30 hours 00 minutes
32 hours 30 minutes
53 hours 45 minutes

I have put in a space between the number and the unit, I find it more readable that way, you can just remove it if you don’t want it.

The %02d specifier in the format string makes sure you get two digits with a leading zero as necessary as in the question: formatDuration(14700000), for example yields 04 hours 05 minutes.

SimpleDateFormat was meant for formatting a date and/or a time of day, not a duration or elapsed time. I say “was” because that class is now long outdated, and since it was also notoriously troublesome I recommend you never use it again. For formatting a date or hour of day use a DateTimeFormatter from java.time.

Upvotes: 2

talex
talex

Reputation: 20455

Unfortunately it is impossible with SimpleDateFormat. "HH" means hour in day. If you date is more than 24 hour it will result modulo 24.

I suggest you to write custom code with new time API.

Here is example:

    Duration duration = Duration.ofMillis(193500000);

    long minutes = duration.toMinutes() % 60;
    long hours = duration.toHours();

    System.out.println("" + hours + "hours:"+minutes+"minutes");

Upvotes: 2

Axel Podehl
Axel Podehl

Reputation: 4303

Carefully read this again: https://docs.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html

The HH is hours in a day, never more than 24 :-)

What you want is probably something like this:

    int msecPerHour = 3600*1000;
    int hours = totalTime/msecPerHour;
    int msecPerMinute = 60*1000;
    int minutes = totalTime/msecPerMinute - hours*60;
    String str = "" + hours + " hours " + minutes + " minutes";

Upvotes: 2

groodt
groodt

Reputation: 1993

It looks like you are trying to convert durations, not date-times. SimpleDateFormat is for formatting date-times into different formats for different Locales.

You might be looking for something like this for converting between durations:

String.format("%d hours, %d minutes", 
    TimeUnit.MILLISECONDS.toHours(millis),
    TimeUnit.MILLISECONDS.toMinutes(millis) - 
    TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis))
);

Upvotes: 0

MrSmith42
MrSmith42

Reputation: 10151

193500000 -> 05hours:45minutes (Wrong, correct is 53hours:45minutes)
That's because 53 hours = 2 days and 5 hours

60300000 -> 16hours:45minutes (correct)
63900000 -> 17hours:45minutes (correct)

108000000-> 06hours:00minutes (Wrong, correct is 30hours:00minutes)
That's because 30 hours = 1 day and 6 hours

117000000 -> 08hours:30minutes (Wrong, correct is 32hours:30minutes)
That's because 32 hours = 1 day and 8 hours

If you only want to calculate the number of hours from ms:

193500000/(1000*60*60)   = number of hours  
(193500000/(1000*60))%60 = number of remaining minutes 

Upvotes: 1

Related Questions