Asked  7 Months ago    Answers:  5   Viewed   35 times

I want to record the time using System.currentTimeMillis() when a user begins something in my program. When he finishes, I will subtract the current System.currentTimeMillis() from the start variable, and I want to show them the time elapsed using a human readable format such as "XX hours, XX mins, XX seconds" or even "XX mins, XX seconds" because its not likely to take someone an hour.

What's the best way to do this?

 Answers

85

Use the java.util.concurrent.TimeUnit class:

String.format("%d min, %d sec", 
    TimeUnit.MILLISECONDS.toMinutes(millis),
    TimeUnit.MILLISECONDS.toSeconds(millis) - 
    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
);

Note: TimeUnit is part of the Java 1.5 specification, but toMinutes was added as of Java 1.6.

To add a leading zero for values 0-9, just do:

String.format("%02d min, %02d sec", 
    TimeUnit.MILLISECONDS.toMinutes(millis),
    TimeUnit.MILLISECONDS.toSeconds(millis) - 
    TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
);

If TimeUnit or toMinutes are unsupported (such as on Android before API version 9), use the following equations:

int seconds = (int) (milliseconds / 1000) % 60 ;
int minutes = (int) ((milliseconds / (1000*60)) % 60);
int hours   = (int) ((milliseconds / (1000*60*60)) % 24);
//etc...
Tuesday, June 1, 2021
 
SpiderLinked
answered 7 Months ago
91

I changed my computer’s time zone to Europe/Bucharest for an experiment. This is UTC + 2 hours like your time zone.

Now when I copy your code I get a result similar to yours:

    Instant now = Instant.now();
    System.out.println(now); // prints 2017-03-14T06:16:32.621Z
    Timestamp current = Timestamp.from(now);
    System.out.println(current); // 2017-03-14 08:16:32.621

Output is given in comments. However, I go on:

    DateFormat df = DateFormat.getDateTimeInstance();
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    // the following prints: Timestamp in UTC: 14-03-2017 06:16:32
    System.out.println("Timestamp in UTC: " + df.format(current));

Now you can see that the Timestamp really agrees with the Instant we started out from (only the milliseconds are not printed, but I trust they are in there too). So you have done everything correctly and only got confused because when we printed the Timestamp we were implicitly calling its toString method, and this method in turn grabs the computer’s time zone setting and displays the time in this zone. Only because of this, the displays are different.

The other thing you attempted, using LocalDateTime, appears to work, but it really does not give you what you want:

    LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
    System.out.println(ldt); // 2017-03-14T06:16:32.819
    current = Timestamp.valueOf(ldt);
    System.out.println(current); // 2017-03-14 06:16:32.819
    System.out.println("Timestamp in UTC: " + df.format(current)); // 14-03-2017 04:16:32

Now when we print the Timestamp using our UTC DateFormat, we can see that it is 2 hours too early, 04:16:32 UTC when the Instant is 06:16:32 UTC. So this method is deceiving, it looks like it’s working, but it doesn’t.

This shows the trouble that lead to the design of the Java 8 date and time classes to replace the old ones. So the real and good solution to your problem would probably be to get yourself a JDBC 4.2 driver that can accept an Instant object readily so you can avoid converting to Timestamp altogether. I don’t know if that’s available for you just yet, but I’m convinced it will be.

Wednesday, June 16, 2021
 
DMTintner
answered 6 Months ago
71

Surely you just need:

double seconds = milliseconds / 1000.0;

There's no need to manually do the two parts separately - you just need floating point arithmetic, which the use of 1000.0 (as a double literal) forces. (I'm assuming your milliseconds value is an integer of some form.)

Note that as usual with double, you may not be able to represent the result exactly. Consider using BigDecimal if you want to represent 100ms as 0.1 seconds exactly. (Given that it's a physical quantity, and the 100ms wouldn't be exact in the first place, a double is probably appropriate, but...)

Tuesday, August 10, 2021
 
jyriand
answered 4 Months ago
25

You can use a setTimeout function on the keydown event which will fire after X seconds to compare the time that event was triggered to the last time a keyup was triggered - if at all.

var lastKeyUpAt = 0;

$(elem).on('keydown', function() {
    // Set key down time to the current time
    var keyDownAt = new Date();

    // Use a timeout with 1000ms (this would be your X variable)
    setTimeout(function() {
        // Compare key down time with key up time
        if (+keyDownAt > +lastKeyUpAt)
            // Key has been held down for x seconds
        else
            // Key has not been held down for x seconds
    }, 1000);
});

$(elem).on('keyup', function() {
    // Set lastKeyUpAt to hold the time the last key up event was fired
    lastKeyUpAt = new Date();
});

elem here is the element you're wanting to handle the event on.

JSFiddle demo.

Saturday, August 14, 2021
 
binoculars
answered 4 Months ago
61

So the 1st question I have is. Since this "tab" is running on a separate controller but is included into the main program, does it run on a separate application thread?

No, there can only be one JavaFX Application instance per JVM, and also one JavaFX Application Thread per JVM.

As for how you could update the timer, it is fine to use Timeline - one for each timer. Timeline does not run on separate thread - it is triggered by the underlying "scene graph rendering pulse" which is responsible for updating the JavaFX GUI periodically. Having more Timeline instances basically just means that there are more listeners that subscribes to the "pulse" event.

public class TimerController {
    private final Timeline timer;

    private final ObjectProperty<java.time.Duration> timeLeft;

    @FXML private Label timeLabel;

    public TimerController() {
        timer = new Timeline();
        timer.getKeyFrames().add(new KeyFrame(Duration.seconds(1), ae -> updateTimer()));
        timer.setCycleCount(Timeline.INDEFINITE);

        timeLeft = new SimpleObjectProperty<>();
    }
    public void initialize() {
        timeLabel.textProperty().bind(Bindings.createStringBinding(() -> getTimeStringFromDuration(timeLeft.get()), timeLeft));
    }

    @FXML
    private void startTimer(ActionEvent ae) {
        timeLeft.set(Duration.ofMinutes(5)); // For example timer of 5 minutes
        timer.playFromStart();
    }

    private void updateTimer() {
        timeLeft.set(timeLeft.get().minusSeconds(1));
    }

    private static String getTimeStringFromDuration(Duration duration) {
        // Do the conversion here...
    }
}

Of course, you can also use Executor and other threading methods, provided you update the Label via Platform.runLater(). Alternatively, you could use a Task.

This is a general example when using background thread:

final Duration countdownDuration = Duration.ofSeconds(5);
Thread timer = new Thread(() -> {
    LocalTime start = LocalTime.now();
    LocalTime current = LocalTime.now();
    LocalTime end = start.plus(countDownDuration);

    while (end.isAfter(current)) {
        current = LocalTime.now();
        final Duration elapsed = Duration.between(current, end);

        Platform.runLater(() -> timeLeft.set(current)); // As the label is bound to timeLeft, this line must be inside Platform.runLater()
        Thread.sleep(1000);
    }
});
Tuesday, August 31, 2021
 
Navaneeth K N
answered 4 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share