-3

EDIT: I have created a countdown timer, however I cannot figure out how to update without creating a new instance. The code is below:

import java.util.concurrent.*;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.DAYS;

public class Countdown {
static int countdownStarterD = 7;
static int countdownStarterH = 24;
static int countdownStarterM = 60;
static int countdownStarterS = 60;
String cSD = Integer.toString(countdownStarterD);
String cSH = Integer.toString(countdownStarterH);
String cSM = Integer.toString(countdownStarterM);
String cSS = Integer.toString(countdownStarterS);
JFrame frame = new JFrame();
JPanel panel = new JPanel();
JLabel days = new JLabel(cSD);
JLabel hours = new JLabel(cSH);
JLabel minutes = new JLabel(cSM);
JLabel seconds = new JLabel(cSS);

public Countdown() {
    panel.add(days);
    panel.add(hours);
    panel.add(minutes);
    panel.add(seconds);
    frame.add(panel);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setTitle("Countdown Timer");
    frame.pack();
    frame.setVisible(true);
}

public static void main(String[] args) {
    final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

    final Runnable days = new Runnable() {

        public void run() {

            System.out.println(countdownStarterD);
            countdownStarterD--;

            if (countdownStarterD < 0) {
                countdownStarterD = 6;
            }
        }
    };
    final Runnable hours = new Runnable() {

        public void run() {

            System.out.println(countdownStarterH);
            countdownStarterH--;

            if (countdownStarterH < 0) {
                countdownStarterH = 23;
            }
        }
    };
    final Runnable minutes = new Runnable() {

        public void run() {

            System.out.println(countdownStarterM);
            countdownStarterM--;

            if (countdownStarterM < 0) {
                countdownStarterM = 59;
            }
        }
    };
    final Runnable seconds = new Runnable() {

        public void run() {

            System.out.println(countdownStarterS);
            countdownStarterS--;

            if (countdownStarterS < 0) {
                countdownStarterS = 59;
            }
        }
    };
    final Runnable gui = new Runnable() {

        public void run() {
            new Countdown();
            
        }
    };
    scheduler.scheduleAtFixedRate(days, 0, 1, DAYS);
    scheduler.scheduleAtFixedRate(hours, 0, 1, HOURS);
    scheduler.scheduleAtFixedRate(minutes, 0, 1, MINUTES);
    scheduler.scheduleAtFixedRate(seconds, 0, 1, SECONDS);
    scheduler.scheduleAtFixedRate(gui, 0, 1, SECONDS);
    
}

}

OLD: I am trying to create a simple GUI timer that counts down from 1 week and restarts once it is finished, it doesn't need to run anything just show a countdown. I would also like it so have a set time. I am stuck trying to convert the number of seconds in the countdown to Days:Hours:Minutes:Seconds and I don't know how to connect that conversion to a GUI. I am thinking I could possibly use a JLabel but I am not sure.

I have this code so far that I found on another forum.

import java.util.concurrent.*;

import static java.util.concurrent.TimeUnit.SECONDS;

public class Countdown {
    public static void main(String[] args) {

        final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        final Runnable runnable = new Runnable() {
            int countdownStarter = 20;

            public void run() {

                System.out.println(countdownStarter);
                countdownStarter--;

                if (countdownStarter < 0) {
                    System.out.println("Timer Over!");
                    scheduler.shutdown();
                }
            }
        };
        scheduler.scheduleAtFixedRate(runnable, 0, 1, SECONDS);
    }
}

It counts down from 20 at the moment which you can change by changing

int countdownStarter = 20;

I have noticed that there is a

import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

That can be used in the code I have so far. Any help on this would be greatly appreciated. I am not looking for a substitute application to use instead.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Mute
  • 17
  • 1
  • 1
    "_convert the number of seconds in the countdown to Days:Hours:Minutes:Seconds_" One approach - take a look at [`Duration`](https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#ofSeconds-long-), and the various methods it has to extract different duration units such as `toHours()`, `toDays()`. – andrewJames May 09 '23 at 12:42
  • 1
    Do not use ScheduledExecutorService. GUIs require the use of a particular thread, and using an arbitrary thread of your own is not compatible with them. If you are planning to use Swing for your GUI, use [javax.swing.Timer](https://docs.oracle.com/en/java/javase/20/docs/api/java.desktop/javax/swing/Timer.html) (*not* java.util.Timer!). If you are planning to use JavaFX for your GUI, use [ScheduledService](https://openjfx.io/javadoc/20/javafx.graphics/javafx/concurrent/ScheduledService.html). – VGR May 09 '23 at 13:36
  • Does this answer your question? [How to update a JLabel text?](https://stackoverflow.com/questions/17456401/how-to-update-a-jlabel-text) – Ole V.V. May 10 '23 at 04:19

2 Answers2

1

java.time

You seem to be working much too hard here. I found your code hard to follow, and likely overwrought. Java has an industry-leading date-time framework in the java.time classes, defined in JSR 310. Make use of that.

Use javax.swing.Timer, not your own threads

As others commented, for a Swing app, you should be using the javax.swing.Timer class (or SwingWorker) rather than counting down in your own threads and executor service. See Tutorial by Oracle.

Tracking time to elapse

As to your specific question about determining the days, hours, minutes, seconds, and nanos remaining before a particular moment:

  • Decide whether you want to track your span of time (a) by calendar days or, (b) by generic 24-hour chunked “days”.
  • Store your target moment.
  • Capture the current moment.
  • Calculate elapsed time.

I will assume you want 24-hour chunked days rather than calendar days.

The Instant class represents a moment, a specific point on the timeline, as seen in UTC (an offset from UTC of zero hours-minutes-seconds).

The Duration class represents a span of time unattached to the timeline, on a scale of 24-hour generic days, hours, minutes, seconds, and nanos.

Duration week = Duration.ofDays( 7 ) ;  // 7 * 24 hours = 1 week, ignoring calendar, ignoring time zones.
Instant target = Instant.now().plus( week ) ;

When your timer task runs, capture the current moment, and calculate time remaining.

Duration timeRemaining = Duration.between( Instant.now() , target ) ;

Check to see if the remaining duration is positive or is negative.

  • If positive, the target is still in the future. So report its current value.
  • If negative, the target has passed. So recalculate the next target, the next week out. And report the new value.

Example code

Here is a class to track the repeating weekly countdown.

package work.basil.example.time;

import java.time.*;

public class Weekly
{
    private final Duration week = Duration.ofDays( 7 );
    private Instant target = Instant.now().plus( week );

    public Duration timeRemaining ( )
    {
        Duration timeRemaining = Duration.between( Instant.now() , target );
        if ( timeRemaining.isNegative() )  // If the current moment is *after* the target, calculate a new target.
        {
            target = target.plus( week );
            timeRemaining = Duration.between( Instant.now() , target );
        }
        return timeRemaining;
    }
}

If you want to report on the next target moment, add a getter method that returns an Instant. The java.time objects such as Instant are immutable, so no need to make a copy when returning.

Here is some code to exercise that Weekly class. The Weekly#timeRemaining method is what your SwingTimer code will call.

Weekly weekly = new Weekly();

Duration duration = weekly.timeRemaining() ;
System.out.println( duration );

try { Thread.sleep( Duration.ofSeconds( 3 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
System.out.println( weekly.timeRemaining() );
try { Thread.sleep( Duration.ofSeconds( 30 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
System.out.println( weekly.timeRemaining() );
try { Thread.sleep( Duration.ofMinutes( 5 ) ); } catch ( InterruptedException e ) { throw new RuntimeException( e ); }
System.out.println( weekly.timeRemaining() );

When run:

PT167H59M59.999799S
PT167H59M56.994437S
PT167H59M26.989259S
PT167H54M26.987014S

Generate text

Lastly, you want to generate text to represent this countdown value.

You can interrogate the Duration objects for its parts, a number of generic 24-hour days (not related to the calendar), hours, minutes, seconds, and nanos. Use the various to…Part methods on the Duration class.

long days = duration.toDaysPart() ;
int hours = duration.toHoursPart() ;
int minutes = duration.toMinutesPart() ;
int seconds = duration.toSecondsPart() ;

Combine those numbers together as a string.

String output = 
    String.format(
        "Time remaining: %d days, %d hours,  %d minutes,  %d seconds", 
        days, 
        hours, 
        minutes, 
        seconds 
    ) 
;

Time remaining: 6 days, 23 hours, 59 minutes, 59 seconds

Display that text in your JLabel widget. See tutorial by Oracle.

myJLabel.setText( output ) ;

Example app

Here is a complete example Swing app.

package work.basil.example.time;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;

public class WeeklySwingApp
{
    // Code modified from example app provided by Oracle Corp at:
    // https://docs.oracle.com/javase/tutorial/displayCode.html?code=https://docs.oracle.com/javase/tutorial/uiswing/examples/start/HelloWorldSwingProject/src/start/HelloWorldSwing.java
    private static void createAndShowGUI ( )
    {
        Weekly weekly = new Weekly();

        //Create and set up the window.
        JFrame frame = new JFrame( "Weekly Countdown" );
        frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

        //Add the ubiquitous "Hello World" label.
        JLabel label = new JLabel( "Time remaining: …" );
        frame.getContentPane().add( label );

        // Countdown.
        int delay = 1_000; // milliseconds
        ActionListener taskPerformer = ( ActionEvent actionEvent ) -> label.setText( formatTimeRemaining( weekly.timeRemaining() ) );
        new Timer( delay , taskPerformer ).start();

        //Display the window.
        frame.setPreferredSize( new Dimension( 420 , 100 ) );
        frame.pack();
        frame.setVisible( true );
    }

    private static String formatTimeRemaining ( final Duration duration )
    {
        return String.format(
                "Time remaining: %d days, %d hours,  %d minutes,  %d seconds" ,
                duration.toDaysPart() ,
                duration.toHoursPart() ,
                duration.toMinutesPart() ,
                duration.toSecondsPart()
        );
    }
    
    public static void main ( String[] args )
    {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater( new Runnable() {public void run ( ) { createAndShowGUI(); }} );
    }
}

screenshot of example app displaying a JLabel with countdown text

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
  • This actually helps out a bunch and went really in depth for what I was trying to do. Thank you! – Mute May 10 '23 at 10:15
0

As you have already noticed, you can use the TimeUnit class to perform the conversions. Here is an example:

int seconds = 3600;
int day = (int)TimeUnit.SECONDS.toDays(seconds);
long hours = TimeUnit.SECONDS.toHours(seconds) - (day * 24);
long minute = TimeUnit.SECONDS.toMinutes(seconds) - (TimeUnit.SECONDS.toHours(seconds)* 60);
long second = TimeUnit.SECONDS.toSeconds(seconds) - (TimeUnit.SECONDS.toMinutes(seconds) *60);
System.out.println(day + ":" + hours + ":" + minute + ":" + second);

on regards to the ouput possibilities, that depends on how you want to display it. JFrame, Terminal, something else.

roediGERhard
  • 163
  • 1
  • 12