3

If the question is a duplicate, I apologise, I didn't find anything via Google.

To my problem:

I have the following test:

public void testSecondsToMinutes() {
    long zero = 0;
    long thirty_seconds = 30;
    long one_minute = 60;
    long thirty_minutes_fiftynine_seonds = 1859;
    long two_hours_ten_miutes_fourty_seconds = 7840;

    String format1 = "00:00:00";
    String format2 = "00:00:30";
    String format3 = "00:01:00";
    String format4 = "00:30:59";
    String format5 = "02:10:40";

    assertEquals(format1,Entrypoint.secondsToMinutes(zero));
    assertEquals(format2,Entrypoint.secondsToMinutes(thirty_seconds));
    assertEquals(format3,Entrypoint.secondsToMinutes(one_minute));
    assertEquals(format4,Entrypoint.secondsToMinutes(thirty_minutes_fiftynine_seonds));
    assertEquals(format5,Entrypoint.secondsToMinutes(two_hours_ten_miutes_fourty_seconds)); 
}

and the following function:

public static String secondsToMinutes(long seconds)
{
    return String.format("%TH:%TM:%TS", seconds, seconds, seconds);
}

the Java documentation for the Formatter states the following:

The following conversion characters are used for formatting common date/time compositions.
This conversion may be applied to long, Long, Calendar, and Date.
[...]
'R' '\u0052'    Time formatted for the 24-hour clock as "%tH:%tM"
'T' '\u0054'    Time formatted for the 24-hour clock as "%tH:%tM:%tS".

However, I am getting the following ComparisionFailure:

expected 00:00:00 but was 01:00:00

I must be missing something obvious here...

maio290
  • 6,440
  • 1
  • 21
  • 38

3 Answers3

3

When you pass a long to String.format, it’s interpreted as milliseconds from the epoch, which is an entirely different entity than your intended seconds from midnight.

Your problem is solved easiest by directly encoding your intention:

public static String secondsToTwentyFourHourString(long seconds) {
    return String.format("%1$TH:%1$TM:%1$TS", LocalTime.ofSecondOfDay(seconds));
}

This way, you specify an operation which is not dependent on time zones.

The same logic also works with DateTimeFormatter:

public static String secondsToTwentyFourHourString(long seconds) {
    return LocalTime.ofSecondOfDay(seconds)
        .format(DateTimeFormatter.ofPattern("HH:mm:ss"));
}
Holger
  • 285,553
  • 42
  • 434
  • 765
  • The actual intention is to format the runtime of a program, which is already using instants to set the start and end. Which is then converted into the epochtime in order to subtract the end from the start. However, your solution fixes this problem in an elegant fashion. You can use %tT btw. instead of the long String I used ;) – maio290 Apr 29 '20 at 14:12
  • 1
    Well, the difference between two instants is not an instant. In this case, it would be more correct to use a `Duration`, however, that would not allow straightforward formatting. – Holger Apr 29 '20 at 14:43
  • To be honest, in the end it doesn't matter much how it's implemented in this case. It's only called a few times and even a longer approach wouldn't matter much regarding performance, I just wanted to keep it short and somewhat elegant. – maio290 Apr 29 '20 at 14:51
2

So, to answer this:

I actually tried the native strftime in C:

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char*argv[])
{
        time_t t = 0;
        struct tm * tmp = localtime(&t);
        char outstr[200];
        strftime(outstr,sizeof(outstr),"%T",tmp);
        printf(outstr);
        exit(EXIT_SUCCESS);
}

Which obviously returned 01:00:00 due to the use of localtime() and not gmtime(). Since timestamps don't hold any timezone, it was now obvious for me that the time did get adjusted to my timezone, which was the CET on the 1st January of 1970.

The dirty workaround for this to work would be:

public static String secondsToTwentyFourHourString(long seconds)
{
    TimeZone current = TimeZone.getDefault();
    TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
    seconds *= 1000;
    try
    {return String.format("%1$TH:%1$TM:%1$TS", seconds);}
    finally
    {TimeZone.setDefault(current);}
}

the better solution is to use Java 8's Time classes:

public static String secondsToTwentyFourHourString(long seconds)
{
    ZonedDateTime zdt = ZonedDateTime.ofInstant(Instant.ofEpochSecond(seconds), ZoneId.of("UTC"));
    return zdt.format(DateTimeFormatter.ofPattern("HH:mm:ss")); 
}
maio290
  • 6,440
  • 1
  • 21
  • 38
1

The String format method require a Calendar to work. You cannot just give seconds. Actually, if you launch each of your tests individually, none of them are green.

I used the response given here to write a more classic solution:

public class Entrypoint {
    public static String secondsToMinutes(long seconds)
    {
        long absSeconds = Math.abs(seconds);
        String positive = String.format(
                "%02d:%02d:%02d",
                absSeconds / 3600,
                (absSeconds % 3600) / 60,
                absSeconds % 60);
        return seconds < 0 ? "-" + positive : positive;
    }
}
Nicolas Dupouy
  • 450
  • 1
  • 7
  • 11
  • It actually works with longs, see my answer. It was the timezone which adjusted the long into CET instead of keeping it UTC. Thanks for the answer though :) – maio290 Apr 29 '20 at 09:17