How can a step be specified for JSpinner? (for example of 10 minutes instead of 1)
There is a model that allow select step size for numbers
SpinnerNumberModel(value, min, max, step);
but how to set it for Dates?
This code
JSpinner timeSpinner = new JSpinner( new SpinnerDateModel() );
DateEditor timeEditor = new DateEditor(timeSpinner, "HH:mm:ss");
timeSpinner.setEditor(timeEditor);
timeSpinner.setValue(new Date()); // will only show the current time
(From this answer Is there any good and free Date AND Time Picker available for Java Swing?)
Allows edit time only, but the steps are always 1 hour, 1 minute, or 1 second depending on if "HH", "HH:mm", or "HH:mm:ss" is specified.
Is there any simple way to have min, max, and step for minutes?
Thanks
Use DateEditor.getModel() to get the SpinnerDateModel which provides setStart,setEnd and setCalendarField.
As you can see in the code of SpinnerDateModel:
public Object getNextValue() {
Calendar cal = Calendar.getInstance(); //Current date.
cal.setTime(value.getTime()); //Set the date to the current value of the model.
cal.add(calendarField, 1); //Increment the date by 1 unit of the selected calendar field (e.g. 1 month).
Date next = cal.getTime(); //Convert back to Date Object.
return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
}
public Object getPreviousValue() {
Calendar cal = Calendar.getInstance(); //Current date.
cal.setTime(value.getTime()); //Set the date to the current value of the model.
cal.add(calendarField, -1); //Decrement the date by 1 unit of the selected calendar field (e.g. 1 month).
Date prev = cal.getTime(); //Convert back to Date Object.
return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
}
It works with adding or substracting 1 unit of the specified calendar field.
You want to customize this number 1 to something else.
So I see two options here:
Reimplement AbstractSpinnerModel. Just copy-paste SpinnerDateModel (it's NOT big), then introduce your integer field "step" for example, and instead of the number 1, just put "step" in getNext and getPrevious.
Implement a SpinnerDateModel, which also works internally with a SpinnerDateModel. It's going to be a lot smaller, but is a bit hackish I guess.
Follows the code of such an SpinnerDateModel (case 2):
import java.awt.GridLayout;
import java.util.Calendar;
import java.util.Date;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.SpinnerDateModel;
public class MultiStepDateSpinner extends JPanel {
private static class StepperSpinnerDateModel extends SpinnerDateModel {
private final SpinnerDateModel internal; //We let the internal SpinnerDateModel do the work for us.
private final int step; //The number of steps to increment and decrement per click.
private Object currentValue; //Needed to get restored each time getPreviousValue and getNextValue is called.
private StepperSpinnerDateModel(final Date value, final Comparable start, final Comparable end, final int calendarField, final int step) {
internal = new SpinnerDateModel(value, start, end, calendarField);
if (step <= 0)
throw new IllegalArgumentException("Non positive step.");
this.step = step;
currentValue = internal.getValue();
}
private StepperSpinnerDateModel(final int step) {
this(new Date(), null, null, Calendar.DAY_OF_MONTH, step);
}
#Override
public Object getValue() {
return currentValue;
}
#Override
public void setValue(final Object value) {
internal.setValue(value);
currentValue = value;
fireStateChanged(); //Important step for the spinner to get updated each time the model's value changes.
}
#Override
public Object getNextValue() {
Object next = null;
for (int i=0; i<step; ++i) { //Calculate step next values:
next = internal.getNextValue();
internal.setValue(next); //We have to set the next value internally, in order to recalculate the next-next value in the next loop.
}
internal.setValue(currentValue); //Restore current value.
return next;
}
#Override
public Object getPreviousValue() {
Object prev = null;
for (int i=0; i<step; ++i) { //Calculate step previous values:
prev = internal.getPreviousValue();
internal.setValue(prev); //We have to set the previous value internally, in order to recalculate the previous-previous value in the next loop.
}
internal.setValue(currentValue); //Restore current value.
return prev;
}
}
private MultiStepDateSpinner() {
super(new GridLayout(0, 1));
//Increment and decrement by 4 minutes each step.
//The null values indicate there shall be no minimum nor maximum date.
//The current value is set to the current date.
final JSpinner spinner = new JSpinner(new StepperSpinnerDateModel(new Date(), null, null, Calendar.MINUTE, 4));
final JButton getValueButton = new JButton("Get value");
getValueButton.addActionListener(e -> {
JOptionPane.showMessageDialog(null, spinner.getValue(), "Got value", JOptionPane.PLAIN_MESSAGE);
});
add(spinner);
add(getValueButton);
}
public static void main(final String[] args) {
final JFrame frame = new JFrame("Frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MultiStepDateSpinner());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
The example code above, is runnable, so you can see it in practice.
Why not just let it be an AbstractSpinnerModel instead of SpinnerDateModel? Because we need it to be identified as an instance of SpinnerDateModel so the JSpinner internally allocates by default a DateEditor for the editor.
Even if you extend AbstractSpinnerModel and supply the spinner with a DateEditor, you will get an exception complaining about the model not being a SpinnerDateModel.
The cleanest way though as I see it, is to reimplement AbstractSpinnerModel and copy paste the code of SpinnerDateModel if needed (which is not big) and introduce any fields you feel.
Incrementa JSpinnerDateEditor_15_+_15_minutos
Calendar Horacalendario = Calendar.getInstance();
Horacalendario.setTime(cita_hora.getDate());
Date HoraParaCita = Horacalendario.getTime();//para asignar luego del incremento de 15 formato date()'
int minutos = Horacalendario.get(Calendar.MINUTE);
Horacalendario.set(Horacalendario.get(Calendar.YEAR), Horacalendario.get(Calendar.MONTH),
Horacalendario.get(Calendar.DATE), Horacalendario.get(Calendar.HOUR), 0);//hago cero los minutos
if (minutos >= 1 & minutos <= 14) {
Horacalendario.add(Calendar.MINUTE, 15); //minutos A Sumar
HoraParaCita = Horacalendario.getTime();
cita_hora.setDate(HoraParaCita);
}
if (minutos >= 16 & minutos <= 29) {
Horacalendario.add(Calendar.MINUTE, 30);
HoraParaCita = Horacalendario.getTime();
cita_hora.setDate(HoraParaCita);
}
if (minutos >= 31 & minutos <= 44) {
Horacalendario.add(Calendar.MINUTE, 45);
HoraParaCita = Horacalendario.getTime();
cita_hora.setDate(HoraParaCita);
}
if (minutos >= 46 & minutos <= 59) {
Horacalendario.set(Horacalendario.get(Calendar.YEAR), Horacalendario.get(Calendar.MONTH),
Horacalendario.get(Calendar.DATE), Horacalendario.get(Calendar.HOUR), 0);//hago cero los minutos
HoraParaCita = Horacalendario.getTime();
cita_hora.setDate(HoraParaCita);
}
Related
I have a problem and I can't get past it...
I am writing a program in Java using swing. That program will be used to to chose a day from a displayed calendar and put an hours of Your work (e.g 8:00 - 16:00) then the program will calculate how many hours You have worked in month and will calculate Your salary.
I've written some code so when starting the program you see a representation of current month. I wanted to add an ActionListenerto a button which will rearrange look of calendar to previous month. I wanted to use the same method that generates the current month but sending a different argument (previous month date).
To test it I used that method on the ActionListener (so when I start it I see blank form and after pressing that button it will show me the current method) and the problem is that nothing at all is happening... That method works fine when I put it in the constructor of my class but doesn't work when it is used as action performed and I don't know why.
I hope You will help me to figure it out and maybe tell me where I made a mistake and what I can do about it. This is a hobby for me I don't have any professional experience in programming.
My code:
package zadanie;
import javax.swing.*;
import java.awt.*;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
class Panel extends JPanel {
private JButton[] buttonArray = new JButton[42];
private JButton nextButton, previousButton;
private JLabel monthYear;
private Color buttonColor = new Color(116, 185, 255);
private Color buttonColorInactive = new Color(255,255,255);
private Color sundey = new Color(0, 184, 148);
private Color saturday = new Color(85, 239, 196);
private Color labelColor = new Color(255, 211, 42);
private LocalDate dateNow = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
Panel(){
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(getMonthLabel());
add(getWeekDaysPanel());
add(Box.createRigidArea(new Dimension(0,5)));
add(getMonthPanel());
calendarGenerator();
getWeekDaysPanel().setAlignmentX(Component.CENTER_ALIGNMENT);
getMonthPanel().setAlignmentX(Component.CENTER_ALIGNMENT);
}
private JComponent getMonthPanel(){
JPanel monthPanel = new JPanel();
monthPanel.setLayout(new GridLayout(6,7));
monthPanel.setMaximumSize(new Dimension(710,460));
monthPanel.setPreferredSize(new Dimension(710,460));
monthPanel.setMaximumSize(new Dimension(710,460));
//Loop that in every iteration creates a "b" button set it properties and to a "p" panel and a buttonArray.
for (int i=0; i<42; i++){
JButton b = new JButton();
b.setMaximumSize(new Dimension(95,70));
b.setPreferredSize(new Dimension(95,70));
b.setMaximumSize(new Dimension(95,70));
b.setBorderPainted(false);
b.setRolloverEnabled(false);
b.setVisible(true);
JPanel p = new JPanel();
p.add(b);
buttonArray[i] = b;
monthPanel.add(p);
}
return monthPanel;
}
// Similar to getMonthPanel method - it adds a 7 labels with the names of the days
private JComponent getWeekDaysPanel(){
JPanel daysPanel = new JPanel();
daysPanel.setBackground(labelColor);
daysPanel.setMinimumSize(new Dimension(700,35));
daysPanel.setPreferredSize(new Dimension(700,35));
daysPanel.setMaximumSize(new Dimension(700,35));
String[] daysList = {"pn.", "wt.", "śr.", "czw.", "pt.", "sob.", "niedz."};
for (int i = 0; i < 7; i++){
JLabel e = new JLabel("", JLabel.CENTER);
e.setMinimumSize(new Dimension(95,25));
e.setPreferredSize(new Dimension(95,25));
e.setMaximumSize(new Dimension(95,25));
e.setLayout(new GridLayout(1,7));
e.setText(daysList[i]);
daysPanel.add(e);
}
return daysPanel;
}
// a method that adds a two buttons (to switch to previous and next month) and a label that displays the displayed month and year
private JComponent getMonthLabel(){
JPanel monthLabel = new JPanel();
monthLabel.setMinimumSize(new Dimension(700,45));
monthLabel.setPreferredSize(new Dimension(700,45));
monthLabel.setMaximumSize(new Dimension(700,45));
monthLabel.setBackground(buttonColorInactive);
monthLabel.revalidate();
nextButton = new JButton();
ImageIcon nIcon = new ImageIcon("n.png");
nextButton.setMinimumSize(new Dimension(25,25));
nextButton.setPreferredSize(new Dimension(25,25));
nextButton.setMaximumSize(new Dimension(25,25));
nextButton.setIcon(nIcon);
nextButton.setBorderPainted(false);
nextButton.setBackground(new Color(255,255,255));
// nextButton.addActionListener();
previousButton = new JButton();
ImageIcon pIcon = new ImageIcon("p.png");
previousButton.setMinimumSize(new Dimension(25,25));
previousButton.setPreferredSize(new Dimension(25,25));
previousButton.setMaximumSize(new Dimension(25,25));
previousButton.setIcon(pIcon);
previousButton.setBorderPainted(false);
previousButton.setBackground(new Color(255,255,255));
monthYear = new JLabel("MIESIĄC_ROK", JLabel.CENTER);
monthYear.setMinimumSize(new Dimension(620,25));
monthYear.setPreferredSize(new Dimension(620,25));
monthYear.setMaximumSize(new Dimension(620,25));
monthLabel.add(previousButton);
monthLabel.add(monthYear);
monthLabel.add(nextButton);
return monthLabel;
}
// A method that change the appearance of the buttons in the "buttonArray" so the whole thing looks like calendar of the month
private void calendarGenerator(){
int noOfDays = dateNow.lengthOfMonth(); /// getting number of days in a month
int firstDayIndex = (dateNow.getDayOfWeek().getValue() - 1); // gettin the value (number) of the first day of month (it is decreased because getValue starts with 1 and buttonArray with 0)
int dayNo = 1; // variable that is used to set number of day in the setText() method of button
int month = (dateNow.getMonth().getValue() - 1); // variable that has a number of the previous month, that is why I decreased it by 1
int year = dateNow.getYear(); // getting current year
if (month == 0){ // safety - when the month variable hits 0 it is set for December (no 12) and year is decreased by 1
month = 12;
year --;
}
LocalDate previousMonthDate = LocalDate.of(year, month, 1); // a new variable for the previous month
int dayNo2 = previousMonthDate.lengthOfMonth() - (firstDayIndex - 1); // getting number of days of the previous mont (similar to dayNo but it responsible for the previous month during displaying
for (int i = 0; i < firstDayIndex; i++){ // loop that fill days in buttons that represent previous month
buttonArray[i].setText(""+dayNo2);
buttonArray[i].setVisible(true);
buttonArray[i].setEnabled(false);
buttonArray[i].setBackground(buttonColorInactive);
dayNo2++;
}
for (int i = firstDayIndex; i < noOfDays + firstDayIndex; i++){ // loop that fill days in buttons that represent current month
buttonArray[i].setText(""+dayNo);
buttonArray[i].setVisible(true);
if (i == 6 || i == 13 || i == 20 || i == 27 || i == 34 || i == 41){
buttonArray[i].setBackground(sundey);
}
else if (i == 5 || i == 12 || i == 19 || i == 26 || i == 33 || i == 40){
buttonArray[i].setBackground(saturday);
}
else{
buttonArray[i].setBackground(buttonColor);
}
monthYear.setText(""+translate(dateNow.getMonth().getValue())+" "+year); // "translate()" method is used for translating month names from English to my native language
dayNo++;
}
dayNo = 1; // setting dayNo 1 because next month always starts with 1
for (int i = (noOfDays + firstDayIndex); i < 42; i++){ // loop that fills the rest, empty buttons that represent next month
buttonArray[i].setText(""+ dayNo);
buttonArray[i].setVisible(true);
buttonArray[i].setEnabled(false);
buttonArray[i].setBackground(buttonColorInactive);
dayNo++;
}
}
// Method for translating English names to my native Language
private String translate(int a){
String monthInPolish = "";
switch (dateNow.getMonth()){
case JANUARY: monthInPolish = "Styczeń"; break;
case FEBRUARY: monthInPolish = "Luty"; break;
case MARCH: monthInPolish = "Marzec"; break;
case APRIL: monthInPolish = "Kwiecień"; break;
case MAY: monthInPolish = "Maj"; break;
case JUNE: monthInPolish = "Czerwiec"; break;
case JULY: monthInPolish = "Lipiec"; break;
case AUGUST: monthInPolish = "Sierpień"; break;
case SEPTEMBER: monthInPolish = "Wrzesień"; break;
case OCTOBER: monthInPolish = "Październik"; break;
case NOVEMBER: monthInPolish = "Listopad"; break;
case DECEMBER: monthInPolish = "Grudzień"; break;
}
return monthInPolish;
}
}
The method that I'm talking about is called calendarGenerator()
Thanks for the effort!
This is how it looks when I use that method in the constructor
This is how it looks when I not use that method in the constructor
Edit: I've added pictures of how it looks when I use calendarGenerator() method in constructor and when that method is not used. Using that method in that form (as showed above) when the button is pressed, I wanted to see if my approach is correct (I know that I can send arguments and thus use it to switch months). So I removed the calendarGenerator() method from constructor (the second picture shows how the program looks like without it) and put it to ActionPerformed method for the button (that black arrow). I thought that when I press the button the window will change the look so it will look like on the first picture but only text on the label above is changing nothing else and I still don't know why.
Change calendarGenerator so it accepts an argument which is an arbitrary date in the month you want to generate:
private void calendarGenerator(LocalDate dateInMonth){
int noOfDays = dateInMonth.lengthOfMonth(); /// getting number of days in a month
.........
}
To generate the current month call it by calendarGenerator(dateNow);
To generate next month: calendarGenerator(dateNow.plusMonths(1));
The following is an mre(1) demonstrating how to use the modified method:
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class Panel extends JPanel {
private JPanel monthPanel;
private JLabel monthYear;
private final static Color buttonColor = new Color(116, 185, 255), buttonColorInactive = new Color(255,255,255),
sundey = new Color(0, 184, 148), saturday = new Color(85, 239, 196), labelColor = new Color(255, 211, 42);
private final static int DAYS=7, WEEKS =6;
private final LocalDate dateNow = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
private LocalDate calendarDate;
private final JButton[] buttonArray = new JButton[DAYS*WEEKS];
Panel(){
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
add(getMonthLabel());
add(getWeekDaysPanel());
add(Box.createRigidArea(new Dimension(0,5)));
makeMonthPanel();
add(monthPanel);
calendarGenerator(dateNow);
}
private void makeMonthPanel(){
monthPanel = new JPanel();
monthPanel.setLayout(new GridLayout(WEEKS,DAYS));
monthPanel.setPreferredSize(new Dimension(710,460));
monthPanel.setAlignmentX(Component.CENTER_ALIGNMENT);
//Loop that in every iteration creates a "b" button set it properties and to a "p" panel and a buttonArray.
for (int i=0; i< DAYS * WEEKS; i++){
JButton b = new JButton();
b.setPreferredSize(new Dimension(95,70));
b.setBorderPainted(false);
b.setRolloverEnabled(false);
JPanel p = new JPanel();
p.add(b);
buttonArray[i] = b;
monthPanel.add(p);
}
}
// Similar to getMonthPanel method - it adds a 7 labels with the names of the days
private JComponent getWeekDaysPanel(){
JPanel daysPanel = new JPanel();
daysPanel.setBackground(labelColor);
daysPanel.setPreferredSize(new Dimension(700,35));
String[] daysList = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
for (int i = 0; i < daysList.length ; i++){
JLabel e = new JLabel("", JLabel.CENTER);
e.setPreferredSize(new Dimension(95,25));
e.setText(daysList[i]);
daysPanel.add(e);
}
return daysPanel;
}
// a method that adds a two buttons (to switch to previous and next month) and a label that displays the displayed month and year
private JComponent getMonthLabel(){
JPanel monthLabel = new JPanel();
monthLabel.setBackground(buttonColorInactive);
JButton nextButton = new JButton(">");;
nextButton.setBorderPainted(false);
nextButton.setBackground(new Color(255,255,255));
nextButton.addActionListener(e -> calendarGenerator(calendarDate.plusMonths(1)));
JButton previousButton = new JButton("<");
previousButton.setBorderPainted(false);
previousButton.setBackground(new Color(255,255,255));
previousButton.addActionListener(e -> calendarGenerator(calendarDate.minusMonths(1)));
monthYear = new JLabel("MIESIĄC_ROK", JLabel.CENTER);
monthYear.setPreferredSize(new Dimension(620,25));
monthLabel.add(previousButton);
monthLabel.add(monthYear);
monthLabel.add(nextButton);
return monthLabel;
}
// A method that change the appearance of the buttons in the "buttonArray" so the whole thing looks like calendar of the month
private void calendarGenerator(LocalDate dateInMonth){
calendarDate = dateInMonth;
int noOfDays = dateInMonth.lengthOfMonth(); /// getting number of days in a month
int firstDayIndex = dateInMonth.getDayOfWeek().getValue() - 1; // gettin the value (number) of the first day of month (it is decreased because getValue starts with 1 and buttonArray with 0)
int dayNo = 1; // variable that is used to set number of day in the setText() method of button
int month = dateInMonth.getMonth().getValue() - 1; // variable that has a number of the previous month, that is why I decreased it by 1
int year = dateInMonth.getYear(); // getting current year
if (month == 0){ // safety - when the month variable hits 0 it is set for December (no 12) and year is decreased by 1
month = 12;
year --;
}
LocalDate previousMonthDate = LocalDate.of(year, month, 1); // a new variable for the previous month
int dayNo2 = previousMonthDate.lengthOfMonth() - (firstDayIndex - 1); // getting number of days of the previous mont (similar to dayNo but it responsible for the previous month during displaying
for (int i = 0; i < firstDayIndex; i++){ // loop that fill days in buttons that represent previous month
buttonArray[i].setText(""+dayNo2);
buttonArray[i].setEnabled(false);
buttonArray[i].setBackground(buttonColorInactive);
dayNo2++;
}
for (int i = firstDayIndex; i < noOfDays + firstDayIndex; i++){ // loop that fill days in buttons that represent current month
buttonArray[i].setText(""+dayNo);
buttonArray[i].setVisible(true);
if (i == 6 || i == 13 || i == 20 || i == 27 || i == 34 || i == 41){
buttonArray[i].setBackground(sundey);
}
else if (i == 5 || i == 12 || i == 19 || i == 26 || i == 33 || i == 40){
buttonArray[i].setBackground(saturday);
}
else{
buttonArray[i].setBackground(buttonColor);
}
monthYear.setText(""+dateInMonth.getMonth()+" "+year);
dayNo++;
}
dayNo = 1; // setting dayNo 1 because next month always starts with 1
for (int i = noOfDays + firstDayIndex; i < 42; i++){ // loop that fills the rest, empty buttons that represent next month
buttonArray[i].setText(""+ dayNo);
buttonArray[i].setVisible(true);
buttonArray[i].setEnabled(false);
buttonArray[i].setBackground(buttonColorInactive);
dayNo++;
}
monthPanel.revalidate();
}
public static void main(String[] args) {
JFrame frame=new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel calendarPane = new Panel();
frame.getContentPane().add(calendarPane);
frame.pack();
frame.setVisible(true);
}
}
(1) Please consider mre when posting questions and answers. To achieve it remove every thing that is not essential (like translation in this case) to show the problem. mre should demonstrate the problem and not necessarily your application.
I have a method which works in this way:
Take as an argument 3 params - a list with dates (sorted in ascending order) , interval unit and interval value
Check whether the next element doesn't exceed the previous date (interval). In other words, given the interval of 30 min, prev - 10:00, next 10:29 - iterate further. if next is 10:31 - break it and return the counter of dates in a row.
The code for it is below:
public static void main(String[] args)
{
Date d1 = new Date();
Date d2 = addOrSubtractTimeUnitFromDate(d1, Calendar.MINUTE, 10, true);
Date d3 = addOrSubtractTimeUnitFromDate(d2, Calendar.MINUTE, 10, true);
Date d4 = addOrSubtractTimeUnitFromDate(d3, Calendar.MINUTE, 10, true);
Date d5 = addOrSubtractTimeUnitFromDate(d4, Calendar.MINUTE, 10, true);
Date d6 = addOrSubtractTimeUnitFromDate(d5, Calendar.MINUTE, 10, true);
List<Date> threeDates = new ArrayList<>();
threeDates.add(d1);
threeDates.add(d2);
threeDates.add(d3);
threeDates.add(d4);
threeDates.add(d5);
threeDates.add(d6);
System.out.println(returnDatesInARowCounter(threeDates, Calendar.MINUTE, 30));
}
private static int returnDatesInARowCounter(List<Date> allDates, int intervalBetween2DatesTimeUnit, int intervalValue)
{
int datesInARowCounter = allDates.size() > 0 ? 1 : 0; // esp. this line (in case allDates is empty)
Date lastDate = null;
Date nextDate;
Iterator<Date> iter = allDates.iterator();
while (iter.hasNext())
{
nextDate = iter.next();
if (lastDate != null) // both lastDate и nextDate are initialized now
{
if(isNextIncidentInIntervalWithLastOneOrNot(lastDate, nextDate, intervalBetween2DatesTimeUnit, intervalValue, true))
{
datesInARowCounter += 1;
}
else break;
}
lastDate = nextDate;
}
return datesInARowCounter;
}
public static Date addOrSubtractTimeUnitFromDate(Date dateToAddToOrSubtractFrom, int calendarTimeUnit, int value, boolean isAdd)
{
if(!isAdd)
{
value = -value;
}
Calendar cal = Calendar.getInstance();
cal.setTime(dateToAddToOrSubtractFrom);
cal.add(calendarTimeUnit, value);
return cal.getTime();
}
private static boolean isNextIncidentInIntervalWithLastOneOrNot(Date lastIncidentRegDate, Date nextIncidentRegDate, int intervalTimeUnit, int intervalValue, boolean isBetween)
{
Date currentIncidentPlusInterval = addOrSubtractTimeUnitFromDate(lastIncidentRegDate, intervalTimeUnit, intervalValue, true);
boolean betweenBool = isDateBetween(nextIncidentRegDate, lastIncidentRegDate, currentIncidentPlusInterval);
return isBetween == betweenBool;
}
private static boolean isDateBetween(Date targetDate, Date startDate, Date endDate)
{
return targetDate.compareTo(startDate) >= 0 && targetDate.compareTo(endDate) <= 0;
}
However, the code looks peculiar to me. Is the any way to make it look more readable?
If you are using Java 8 or newer, you can use the java.time-API instead. It's built-in support for "periods of time" makes the actual implementation much simpler.
static int daysInARow(List<Instant> allInstants, Duration maxDifference) {
int counter = allInstants.size() > 0 ? 1 : 0;
Instant previous = allInstants.get(0);
for (int i = 1; i < allInstants.size(); i++) {
Instant current = allInstants.get(i);
if (Duration.between(previous, current).compareTo(maxDifference) > 0)
break;
counter++;
previous = current;
}
return counter;
}
If you're using java.util.Date in other parts of your project, you can easily convert between Instants by using
Date#from(Instant)
and
Date#toInstant()
I have a custom java sync that fetch data by date range thoght SOAP service running on tomcat.
Ex:
getDataByDateRange(startDate,endDate)
getDataByDateRange('2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000')
I want to write a control program to check if any range has been missed by any kind of runtime or server error.
How can I find the missing date ranges?
Thanks.
Visually Example:
TimeLine : [------------------------------------------------------------------]
Processed Dates: [----1---][---2----]---[-3-][--4---]---[----5---][---6--]-----------
Missing Dates : -------------------[-1-]-----------[-2-]----------------[-----3----]
TimeLine:
1: '2016-01-01 10:00:00.00000','2016-02-01 09:00:00.00000'
Processed Dates:
1: '2016-01-01 10:00:00.00000','2016-01-01 11:00:00.00000'
2: '2016-01-01 11:00:00.00000','2016-01-01 12:00:00.00000'
3: '2016-01-01 13:00:00.00000','2016-01-01 13:30:00.00000'
4: '2016-01-01 13:30:00.00000','2016-01-01 14:30:00.00000'
5: '2016-01-01 15:30:00.00000','2016-01-01 16:30:00.00000'
6: '2016-01-01 16:30:00.00000','2016-01-01 17:00:00.00000'
Missing Dates:
1: '2016-01-01 12:00:00.00000','2016-01-01 13:00:00.00000'
2: '2016-01-01 14:30:00.00000','2016-01-01 15:30:00.00000'
3: '2016-01-01 17:00:00.00000','2016-01-02 09:00:00.00000'
According to your comment I post my previous comment as answer. This solution uses my library Time4J (including the range-module):
// prepare parser
ChronoFormatter<PlainTimestamp> f =
ChronoFormatter.ofTimestampPattern( // five decimal digits
"uuuu-MM-dd HH:mm:ss.SSSSS", PatternType.CLDR, Locale.ROOT);
// parse input to intervals - here the overall time window
TimestampInterval timeline =
TimestampInterval.between(
f.parse("2016-01-01 10:00:00.00000"),
f.parse("2016-02-01 09:00:00.00000"));
// for more flexibility - consider a for-each-loop
TimestampInterval i1 =
TimestampInterval.between(f.parse("2016-01-01 10:00:00.00000"), f.parse("2016-01-01 11:00:00.00000"));
TimestampInterval i2 =
TimestampInterval.between(f.parse("2016-01-01 11:00:00.00000"), f.parse("2016-01-01 12:00:00.00000"));
TimestampInterval i3 =
TimestampInterval.between(f.parse("2016-01-01 13:00:00.00000"), f.parse("2016-01-01 13:30:00.00000"));
TimestampInterval i4 =
TimestampInterval.between(f.parse("2016-01-01 13:30:00.00000"), f.parse("2016-01-01 14:30:00.00000"));
TimestampInterval i5 =
TimestampInterval.between(f.parse("2016-01-01 15:30:00.00000"), f.parse("2016-01-01 16:30:00.00000"));
TimestampInterval i6 =
TimestampInterval.between(f.parse("2016-01-01 16:30:00.00000"), f.parse("2016-01-01 17:00:00.00000"));
// apply interval arithmetic
IntervalCollection<PlainTimestamp> icoll =
IntervalCollection.onTimestampAxis().plus(Arrays.asList(i1, i2, i3, i4, i5, i6));
List<ChronoInterval<PlainTimestamp>> missed = icoll.withComplement(timeline).getIntervals();
// result
System.out.println(missed);
// [[2016-01-01T12/2016-01-01T13), [2016-01-01T14:30/2016-01-01T15:30), [2016-01-01T17/2016-02-01T09)]
The core of the whole interval arithmetic is just done by the code fragment icoll.withComplement(timeline). The rest is only about creation of intervals. By applying a for-each-loop you can surely minimize again the count of lines in presented code.
The output is based on the canonical description of the intervals implicitly using toString(), for example: [2016-01-01T12/2016-01-01T13) The square bracket denotes a closed boundary while the round bracket to the right end denotes an open boundary. So we have here the standard case of half-open timestamp intervals (without timezone). While other interval types are possible I have chosen that type because it corresponds to the type of your input strings.
If you plan to combine this solution with Joda-Time in other parts of your app then keep in mind that a) there is not yet any special bridge between both libraries available and b) the conversion looses microsecond precision (Joda-Time only supports milliseconds) and c) Time4J has much more power than Joda-Time (for almost everything). Anyway, you can do this as conversion (important if you don't want to do the effort of bigger rewriting of your app):
ChronoInterval<PlainTimestamp> missed0 = missed.get(0);
PlainTimestamp tsp = missed0.getStart().getTemporal();
LocalDateTime ldt = // joda-equivalent
new LocalDateTime(
tsp.getYear(), tsp.getMonth(), tsp.getDayOfMonth(),
tsp.getHour(), tsp.getMinute(), tsp.getSecond(), tsp.get(PlainTime.MILLI_OF_SECOND));
System.out.println(ldt); // 2016-01-01T10:00:00.000
About a Joda-only solution:
Joda-Time does only support instant intervals, not timestamp intervals without timezone. However, you could simulate that missing interval type by hardwiring the timezone to UTC (using fixed offset).
Another problem is missing support for five decimal digits. You can circumvent it by this hack:
DateTime start =
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss.SSS")
.withZoneUTC()
.parseDateTime("2016-01-01 16:30:00.00000".substring(0, 23));
System.out.println(start); // 2016-01-01T16:30:00.000Z
DateTime end = ...;
Interval interval = new Interval(start, end);
The other more critical element of a solution is almost missing - interval arithmetic. You have to sort the intervals first by start instant (and then by end instant). After sorting, you can iterate over all intervals such that you find the gaps. The best thing Joda-Time can do for you here is giving you methods like isBefore(anotherInstant) etc. which you can use in your own solution. But it gets pretty much bloated.
Given that the frequency of date ranges is one hour, you can start with range start date, iterate till range end date and write a method that checks for an entry with dates. You can use DateUtils to add hour to date, as shown in the below pseudo code:
Date startDate = startDate;
Date endDate = endDate;
while (startDate.before(endDate){
if(!exists(startDate, DateUtils.addHours(startDate, 1), entries)){
//Add into missing entries
}
startDate = DateUtils.addHours(startDate, 1);
}
I posted my IntervalTree a while ago - it seems to work well with this kind of problem.
See the minimise method for what you are looking for.
/**
* Title: IntervlTree
*
* Description: Implements a static Interval Tree. i.e. adding and removal are not possible.
*
* This implementation uses longs to bound the intervals but could just as easily use doubles or any other linear value.
*
* #author OldCurmudgeon
* #version 1.0
* #param <T> - The Intervals to work with.
*/
public class IntervalTree<T extends IntervalTree.Interval> {
// My intervals.
private final List<T> intervals;
// My center value. All my intervals contain this center.
private final long center;
// My interval range.
private final long lBound;
private final long uBound;
// My left tree. All intervals that end below my center.
private final IntervalTree<T> left;
// My right tree. All intervals that start above my center.
private final IntervalTree<T> right;
public IntervalTree(List<T> intervals) {
if (intervals == null) {
throw new NullPointerException();
}
// Initially, my root contains all intervals.
this.intervals = intervals;
// Find my center.
center = findCenter();
/*
* Builds lefts out of all intervals that end below my center.
* Builds rights out of all intervals that start above my center.
* What remains contains all the intervals that contain my center.
*/
// Lefts contains all intervals that end below my center point.
final List<T> lefts = new ArrayList<>();
// Rights contains all intervals that start above my center point.
final List<T> rights = new ArrayList<>();
long uB = Long.MIN_VALUE;
long lB = Long.MAX_VALUE;
for (T i : intervals) {
long start = i.getStart();
long end = i.getEnd();
if (end < center) {
lefts.add(i);
} else if (start > center) {
rights.add(i);
} else {
// One of mine.
lB = Math.min(lB, start);
uB = Math.max(uB, end);
}
}
// Remove all those not mine.
intervals.removeAll(lefts);
intervals.removeAll(rights);
uBound = uB;
lBound = lB;
// Build the subtrees.
left = lefts.size() > 0 ? new IntervalTree<>(lefts) : null;
right = rights.size() > 0 ? new IntervalTree<>(rights) : null;
// Build my ascending and descending arrays.
/**
* #todo Build my ascending and descending arrays.
*/
}
/*
* Returns a list of all intervals containing the point.
*/
List<T> query(long point) {
// Check my range.
if (point >= lBound) {
if (point <= uBound) {
// Gather all intersecting ones.
List<T> found = intervals
.stream()
.filter((i) -> (i.getStart() <= point && point <= i.getEnd()))
.collect(Collectors.toList());
// Gather others.
if (point < center && left != null) {
found.addAll(left.query(point));
}
if (point > center && right != null) {
found.addAll(right.query(point));
}
return found;
} else {
// To right.
return right != null ? right.query(point) : Collections.<T>emptyList();
}
} else {
// To left.
return left != null ? left.query(point) : Collections.<T>emptyList();
}
}
/**
* Blends the two lists together.
*
* If the ends touch then make them one.
*
* #param a
* #param b
* #return
*/
static List<Interval> blend(List<Interval> a, List<Interval> b) {
// Either empty - return the other.
if (a.isEmpty()) {
return b;
}
if (b.isEmpty()) {
return a;
}
// Where does a end and b start.
Interval aEnd = a.get(a.size() - 1);
Interval bStart = b.get(0);
ArrayList<Interval> blended = new ArrayList<>();
// Do they meet/cross?
if (aEnd.getEnd() >= bStart.getStart() - 1) {
// Yes! merge them.
// Remove the last.
blended.addAll(a.subList(0, a.size() - 1));
// Add a combined one.
blended.add(new SimpleInterval(aEnd.getStart(), bStart.getEnd()));
// Add all but the first.
blended.addAll(b.subList(1, b.size()));
} else {
// Just join them.
blended.addAll(a);
blended.addAll(b);
}
return blended;
}
static List<Interval> blend(List<Interval> a, List<Interval> b, List<Interval>... more) {
List<Interval> blended = blend(a, b);
for (List<Interval> l : more) {
blended = blend(blended, l);
}
return blended;
}
List<Interval> minimise() {
// Calculate min of left and right.
List<Interval> minLeft = left != null ? left.minimise() : Collections.EMPTY_LIST;
List<Interval> minRight = right != null ? right.minimise() : Collections.EMPTY_LIST;
// My contribution.
long meLeft = minLeft.isEmpty() ? lBound : Math.max(lBound, minLeft.get(minLeft.size() - 1).getEnd());
long meRight = minRight.isEmpty() ? uBound : Math.min(uBound, minRight.get(0).getEnd());
return blend(minLeft,
Collections.singletonList(new SimpleInterval(meLeft, meRight)),
minRight);
}
private long findCenter() {
//return average();
return median();
}
protected long median() {
if (intervals.isEmpty()) {
return 0;
}
// Choose the median of all centers. Could choose just ends etc or anything.
long[] points = new long[intervals.size()];
int x = 0;
for (T i : intervals) {
// Take the mid point.
points[x++] = (i.getStart() + i.getEnd()) / 2;
}
Arrays.sort(points);
return points[points.length / 2];
}
/*
* What an interval looks like.
*/
public interface Interval {
public long getStart();
public long getEnd();
}
/*
* A simple implemementation of an interval.
*/
public static class SimpleInterval implements Interval {
private final long start;
private final long end;
public SimpleInterval(long start, long end) {
this.start = start;
this.end = end;
}
#Override
public long getStart() {
return start;
}
#Override
public long getEnd() {
return end;
}
#Override
public String toString() {
return "{" + start + "," + end + "}";
}
}
/**
* Not called by App, so you will have to call this directly.
*
* #param args
*/
public static void main(String[] args) {
/**
* #todo Needs MUCH more rigorous testing.
*/
// Test data.
long[][] data = {
{1, 4}, {2, 5}, {5, 7}, {10, 11}, {13, 20}, {19, 21},};
List<Interval> intervals = new ArrayList<>();
for (long[] pair : data) {
intervals.add(new SimpleInterval(pair[0], pair[1]));
}
// Build it.
IntervalTree<Interval> test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Normal test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
// Check minimise.
List<Interval> min = test.minimise();
System.out.println("Minimise test: ---");
System.out.println(min);
// Check for empty list.
intervals.clear();
test = new IntervalTree<>(intervals);
// Test it.
System.out.println("Empty test: ---");
for (long i = 0; i < 10; i++) {
List<Interval> intersects = test.query(i);
System.out.println("Point " + i + " intersects:");
intersects.stream().forEach((t) -> {
System.out.println(t.toString());
});
}
}
}
This gets close to what you are looking for. Here's some code to minimise your ranges into just 3.
static String[][] dates = {{"2016-01-01 10:00:00.00000", "2016-01-01 11:00:00.00000"}, {"2016-01-01 11:00:00.00000", "2016-01-01 12:00:00.00000"}, {"2016-01-01 13:00:00.00000", "2016-01-01 13:30:00.00000"}, {"2016-01-01 13:30:00.00000", "2016-01-01 14:30:00.00000"}, {"2016-01-01 15:30:00.00000", "2016-01-01 16:30:00.00000"}, {"2016-01-01 16:30:00.00000", "2016-01-01 17:00:00.00000"}};
static List<IntervalTree.SimpleInterval> ranges = new ArrayList<>();
static final DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");
static {
for (String[] pair : dates) {
try {
ranges.add(new IntervalTree.SimpleInterval(df.parse(pair[0]).getTime(), df.parse(pair[1]).getTime()));
} catch (ParseException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public void test() {
IntervalTree tree = new IntervalTree<>(ranges);
List<IntervalTree.Interval> min = tree.minimise();
//System.out.println("min->" + min);
for (IntervalTree.Interval i : min) {
System.out.println(df.format(new Date(i.getStart())) + " - " + df.format(new Date(i.getEnd())));
}
}
which prints
2016-01-01 10:00:00.0 - 2016-01-01 12:00:00.0
2016-01-01 13:00:00.0 - 2016-01-01 14:30:00.0
2016-01-01 15:30:00.0 - 2016-01-01 17:00:00.0
which is all of your Processed Dates joined into three date ranges.
I'm new here and I already have a question.
I'm making a assignment for school (in Processing with Java), we are loading a schedule from a txt file.
And I need to make a visualisation from it, it's going good but I'm stuck at one point.
I want to make a if statement, to see if the current time is between the Start time and end time. I did parse the start time and End time from the txt file and I can call the current time. But i can't find or know the if statement. can someone help?
Parts of my code ( i can't show everything because i have 6 tabs) :
**//From the main tab**
int s = second(); // Values from 0 - 59
int mi = minute(); // Values from 0 - 59
int h = hour(); // Values from 0 - 23
int d = day(); // Values from 1 - 31
int mo = month(); // values from 1 - 12
int y = year();
// searching all data from Table, comparing location with roomsTTH, if its the same check iff itint(random(20)), 20's free
for (TableRow singleRow : tableRooster.rows()) {
ParseInfo parse = new ParseInfo(singleRow);
for (Room roomToCheck : roomsTTH) {
if (parse.location == roomToCheck.id) {
if (roomToCheck.available) {
if ( y == parseInt(parse.year)) {
if (mo == parseInt(parse.month)) {
if ( d == parseInt(parse.day)) {
// if ( isBetween = currentTime.after(parse.startTime) && currantTime.before(parse.endTime)) {
// occupied = true;
// Floor.maxRooms ++;
}
}
}
}
}
}
}
// }
**//From tab Parse**
//StartTime
String startTime;
//EndTime
String endTime;
TableRow myTableRow;
ParseInfo(TableRow tableRow) {
parseRow(tableRow);
}
// Parse data into variables
void parseRow(TableRow row) {
// Divide the parse location to level and room
this.location = row.getString("Location");
this.level = this.location.substring(5, 6);
this.room = this.location.substring(8, 9);
// Divide the parse start date to year, month, day
this.date = row.getString("Start date");
this.day = this.date.substring(9, 10);
this.month = this.date.substring(6, 7);
this.year = this.date.substring(0, 3);
// Parse Start time & End time
this.startTime = row.getString("Start time");
this.endTime = row.getString("End time");
}
}
It looks like your dates are stored as String values. String values don't have a concept of whether they are before or after each other, so you have to do the comparison yourself. You have two options:
Option 1: Compare the String values alphabetically. Something like this:
String date1 = "2015/10/24";
String date2 = "2015/10/25";
if(date1.compareTo(date2) < 0){
//date 1 is before date2
}
else if(date1.compareTo(date2) > 0){
//date 1 is after date2
}
else{
//they are the same date
}
Option 2: If that won't work for some reason (if the dates are in different formats, for example), then you can use Java's SimpleDateFormat class to parse the date Strings into Date objects. Something like this:
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date1 = sdf.parse("2015/10/24");
Date date2 = sdf.parse("2015/10/25");
if(date1.before(date2)){
//date 1 is before date2
}
else if(date1.after(date2)){
//date 1 is after date2
}
else{
//they are the same date
}
I am trying to create a spinner that has hours and minutes. The minutes part needs to increment by 10 mins only and the time must range from the current time to an end time. I also need the minimum value (previously current time) to update to current time.
I tried playing around with it, but I just couldn't get it to work.
JSpinner spinner1 = new javax.swing.JSpinner();
SpinnerDateModel spinnermodel = new SpinnerDateModel();
spinnermodel.setCalendarField(Calendar.MINUTE);
spinner1.setModel(spinnermodel);
spinner1.setEditor(new JSpinner.DateEditor(spinner1, "hh:mm"));
SpinnerModel model = new SpinnerDateModel(currentDate, currentDate, latestDate, Calendar.MINUTE * 10 ?);
The SpinnerDateModel just uses 1 to increment the field you want to change.
I extended the SpinnerDateModel to add an addition property to the model to control the increment value instead of hard coding to 1:
import java.util.*;
import javax.swing.*;
public class MySpinnerDateModel extends SpinnerDateModel
{
private int increment = 1;
public MySpinnerDateModel(Date value, Comparable start, Comparable end, int calendarField)
{
super(value, start, end, calendarField);
}
public MySpinnerDateModel()
{
this(new Date(), null, null, Calendar.DAY_OF_MONTH);
}
public void setIncrement(int increment)
{
this.increment = increment;
}
public int getIncrement()
{
return increment;
}
#Override
public Object getNextValue()
{
Calendar cal = Calendar.getInstance();
Date value = (Date)getValue();
cal.setTime(value);
cal.add(getCalendarField(), increment);
Date next = cal.getTime();
Comparable end = getEnd();
return ((end == null) || (end.compareTo(next) >= 0)) ? next : null;
}
#Override
public Object getPreviousValue()
{
Calendar cal = Calendar.getInstance();
Date value = (Date)getValue();
cal.setTime(value);
cal.add(getCalendarField(), -increment);
Date prev = cal.getTime();
Comparable start = getStart();
return ((start == null) || (start.compareTo(prev) <= 0)) ? prev : null;
}
}
You should be able to use the model the way you did before but with one additional statement:
MySpinnerDateModel model = new MySpinnerDateModel(currentDate, currentDate, latestDate, Calendar.MINUTE);
model.setIncrement( 10 );
You can extend the SpinnerDateModel to specify the behavior. Below is an example in which the getNextValue and getPreviousValue are overridden to return values +/- 10 minutes:
Date now = new Date();
Date start = now;
final long tenMinutesInMillis = 1000 * 60 * 10;
Date end = new Date(now.getTime() + tenMinutesInMillis * 60);
SpinnerModel model = new SpinnerDateModel(now, start, end, Calendar.MINUTE){
#Override
public Object getNextValue(){
Date newDate = new Date(getDate().getTime() + tenMinutesInMillis);
Date endDate = (Date)getEnd();
return newDate.getTime() > endDate.getTime() ? endDate : newDate;
}
#Override
public Object getPreviousValue(){
Date newDate = new Date(getDate().getTime() - tenMinutesInMillis);
Date startDate = (Date)getStart();
return newDate.getTime() < startDate.getTime() ? startDate : newDate;
}
};