0

using Java and Java Swing for a GUI. The scenario is that I want a user to enter in a desired time (in a JTextbox) in the format of HH:MM:SS and from that given time, countdown by seconds until it hits zero. Currently I am using a timer and the timer.between function. I create an Instant() from the user input time and also use instant.now(). The instants are being created, however, the countdown clock doesn't count down from the user input time, but rather some random numbers that I can't figure out where they are coming from. Can anyone else see the problem?

 javax.swing.Timer countDown = new javax.swing.Timer(1000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                Duration countingDown = Duration.between(Instant.now(), userInputCountDown);
                autoShutOffTF.setText(String.format("%02d:%02d:%02d",
                        countingDown.toHours(),
                        countingDown.toMinutes() % 60,
                        countingDown.getSeconds() % 60));
            }
        });
        startButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                //Getting user input, parsing String in the form of HH:MM:SS 
                String countdownInput = autoShutOffTF.getText();

                String getHours = countdownInput.substring(0,2);
                int hours = Integer.parseInt(getHours);

                String getMins = countdownInput.substring(3,5);
                int mins = Integer.parseInt(getMins);

                String getSecs = countdownInput.substring(6,8);
                int seconds = Integer.parseInt(getSecs);

                //Creating a date instance, to get the current year, month and date
                Date date = new Date();
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(date);
                int year = calendar.get(Calendar.YEAR);
                int month = calendar.get(Calendar.MONTH);
                int day = calendar.get(Calendar.DAY_OF_MONTH);

                //creating a new calendar with all of the data
                Calendar cal = Calendar.getInstance();
                cal.set(year, month, day, hours, mins, seconds);

                //creating a new instant with the new calendar with all of the data
                userInputCountDown = cal.toInstant();

                //starting timer
                countDown.start();
            }
        });
  • 1
    Don't use `Calendar` for this, at the very least you could use `LocalTime`, but you might need to use `LocalDateTime` or even just use `Duration` – MadProgrammer Jan 29 '20 at 05:18
  • 2
    Please post [mre] – c0der Jan 29 '20 at 05:20
  • You'll also find that you will need a time in the future, so the `Duration` becomes smaller (just thinking of the top of my head) – MadProgrammer Jan 29 '20 at 05:21
  • @Madprogrammer is ```Calendar``` the reason why I am getting a random time that is counting down by second? –  Jan 29 '20 at 05:23

1 Answers1

2

Don't use Date or Calendar, the java.time API is more the capable of achieving what you want.

Looking at this...

Date date = new Date();
Calendar calendar = Calendar.getInstance();
calendar.setTime(date);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH);
int day = calendar.get(Calendar.DAY_OF_MONTH);

//creating a new calendar with all of the data
Calendar cal = Calendar.getInstance();
cal.set(year, month, day, hours, mins, seconds);

You're creating a new time, based on the hours/mins/seconds, but, what worries me is, is what happens if the time is less than now? This "might" be the issue you're having.

So, some thing you might want to do is verify if the time is before or after the current time and roll the day accordingly - assuming you want to use an absolute time (ie create a timer which counts down from now to 6pm)

This...

Duration countingDown = Duration.between(Instant.now(), userInputCountDown);

also seems off to me, as userInputCountDown should be in the future

The following example takes a slightly different approach, as it creates a "timer" that will create a target in the future (based on the input) from the current time (adding the hours, mins and seconds) and use it as the anchor point for the count down.

So, you might say, "create a 1 hour" timer, for example.

import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.Timer;

class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private JTextField targetHours;
        private JTextField targetMins;
        private JTextField targetSeconds;

        private Instant futureTime;
        private Timer timer;

        private JLabel countDown;

        public TestPane() {

            setLayout(new GridBagLayout());

            targetHours = new JTextField("00", 2);
            targetMins = new JTextField("00", 2);
            targetSeconds = new JTextField("00", 2);

            JPanel targetPane = new JPanel(new GridBagLayout());
            targetPane.add(targetHours);
            targetPane.add(new JLabel(":"));
            targetPane.add(targetMins);
            targetPane.add(new JLabel(":"));
            targetPane.add(targetSeconds);

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.gridwidth = GridBagConstraints.REMAINDER;
            gbc.insets = new Insets(8, 8, 8, 8);

            add(targetPane, gbc);

            JButton btn = new JButton("Start");
            btn.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    futureTime = LocalDateTime.now()
                            .plusHours(Long.parseLong(targetHours.getText()))
                            .plusMinutes(Long.parseLong(targetMins.getText()))
                            .plusSeconds(Long.parseLong(targetSeconds.getText()))
                            .atZone(ZoneId.systemDefault()).toInstant();

                    if (timer != null) {
                        timer.stop();
                    }

                    countDown.setText("---");
                    timer = new Timer(500, new ActionListener() {
                        @Override
                        public void actionPerformed(ActionEvent e) {
                            Duration duration = Duration.between(Instant.now(), futureTime);
                            if (duration.isNegative()) {
                                timer.stop();
                                timer = null;
                                countDown.setText("00:00:00");
                            } else {
                                String formatted = String.format("%02d:%02d:%02d", duration.toHours(), duration.toMinutesPart(), duration.toSecondsPart());
                                countDown.setText(formatted);
                            }
                        }
                    });
                    timer.start();
                }
            });

            add(btn, gbc);

            countDown = new JLabel("---");
            add(countDown, gbc);
        }

    }

}

WARNING - I do NO validation on the input, so you will have to be careful.

If, instead, you wanted to count down to a particular point in time (ie count down from now to 6pm), then you would need to use LocalDateTime#withHour(Long)#withMinute(Long)#withSecond(Long) chain instead. But, beware, you'll have to verify if the time is in the future or past and take appropriate action, because if you want to countdown to 6pm, but it's 7pm ... what does that actually mean :/ ?

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • it works just like i need it to! except it has the total amount of seconds and minutes.. for example if i put in 00:02:00 for a timer of 2 mins, the seconds countdown starts at 00:01:120. how do i make it so it does seconds and minutes from 60? –  Jan 29 '20 at 19:56
  • The example I've posted seems to work as expected. I enter 2 minutes and it will display `00:01:59` (because the amount of time it takes to spin and execute the `Timer` allows for at least 1 second to pass) – MadProgrammer Jan 29 '20 at 20:44
  • oh yes i see. In my program, I have 1 Jtextfield for the time and i think because I am getting the hours, mins, seconds by substrings of the JTextfield string instead before i make the future time instant that is why i am getting that problem. do you know how to fix that? –  Jan 29 '20 at 20:57
  • I can't see that been an issue. You extract `00:02:00` still means `2` minutes. So long as you're advancing the time correctly, it shouldn't make any difference, as far as I can see. I'd be more concerned with how you're formatting it – MadProgrammer Jan 29 '20 at 21:01
  • oh it is because i am using .getSeconds() and .toMinutes() instead since .toSecondsPart isn't working. –  Jan 29 '20 at 21:13
  • `toSecondsPart` is available in Java 9 I think, see [this example](https://stackoverflow.com/questions/50419165/java-format-hour-and-min/50419562#50419562) for an alternative – MadProgrammer Jan 29 '20 at 21:38