JSpinner SpinnerDateModel question - java

I'm trying to create a JSpinner to enable the user to pick a Date. I want there to be a lower date limit and an upper date limit. I also want the initial value to be the lower date limit. Unfortunately, My problem is that it won't let me use the lower limit as the initial value (the JSpinner simply becomes unresponsive). Here is my code:
SpinnerDateModel model = new SpinnerDateModel();
model.setStart(minTime); //lower limit
model.setEnd(maxTime); //upper limit
model.setValue(minTime); //doesn't like this!
model.setCalendarField(Calendar.MINUTE);
JSpinner timePicker = new JSpinner(model);
timePicker.setEditor(new JSpinner.DateEditor(timePicker, "HH:mm dd/MM/yy"));
If I set the initial value to be one minute before or after the lower limit, it works fine. But for my requirements, I do not want that.
Help?

Looks like a bug to me. I messed around with some code and it appears that whatever you pass to setValue, I'll call it value, must be at least one calendarField unit greater than minTime.
i.e. if you had used model.setCalendarField(Calendar.YEAR), value would have to be any date in 2011, assuming you used a date in 2010 for minTime.
According to Sun, the invariant enforced by the SpinnerDateModel constructors is minimum <= value <= maximum, so this problem shouldn't be happening.
The first workaround that comes to mind is creating a custom SpinnerDateModel which overrides the getPreviousValue() and setValue() methods to manually check against your desired minTime.

Related

How to indicate some fields are not supported

Trying to move from Calendar to the new Java 8 time on Android. Is there a way to indicate that a time or date field is not supported? I can use the 'Truncate' method that will set all time fields of a shorter duration to zero, so a time stamp like 2020-09-30T10:37:15.345-04:00 can be truncated say at the minutes level. But that will leave 2020-09-30T10:00:00.00-04:00.
However, what I want to indicate is that the clock does not have minutes or less precision so that when one tries to read the minutes or seconds, there will be some indication that there are no such fields or that they are unknown. Zero is a valid value.
Right now in the Calendar case I have to add numerous methods to a class to indicate that. For example, I made a class called TimeStruct which wraps a Calendar. If I take a time stamp like 2020-10-01T04:55 it does not have minutes. So to keep that information I have a variable 'isSecondsSet' and set it to false. I create the Calendar from the elements I DO have. But as soon as I call something like Calendar.getTimeInMillis() the seconds and milliseconds fields get set to 0 and are indicated as set. So my additional variables let me know that there was no seconds field.
I was hoping that the new classes would no longer require me to keep my own indicators and I would also be able to parse something like 2020-10-01T04:55. I could not, but I could parse a full time stamp. So if I do that and truncate, can I indicate that the truncated fields are not supported? That way I wont use a value of 0 in the seconds.

How can i set the min and max value in a JDateChooser?

I saw there's another question like this but the answer isn't useful for me since I added the JDateChooser with the design option in NetBeans, so I don't know how to set the min and max date for the pick.
Assuming JDateChooser uses an extends of the SpinnerDateModel then you just need to get the model and set the start and end date.
SpinnerDateModel model = (SpinnerDateModel) chooser.getModel();
model.setStart(startDate)
model.setEnd(endDate);

How do I use Android's Handler.PostDelayed to make an event happen at a specified time?

I want to have my application execute code at a point in the future.
I want to do:
Date now = new Date();
for (Date beep : scheduledBeeps) {
if (beep.after(now))
{
Logger.i("adding beep");
m_beepTimer.postAtTime(beepNow, beep.getTime());
}
}
In the log I can see 4 beeps added, however they never fire. I'm assuming it has something to do with uptimeMillis, but I'm not sure what to do.
You will have to get the difference between now and beep.gettime() and pass it to postattime function. Since uptime is used as base, it may not be accurate if the phone goes to deep sleep.
beep.gettime - now + SystemCLock.uptimeMillis()
should be passed to postattime function
You are currently passing a very large number equivalent to current milliseconds from jan 1 1970.
You could use the Calendar class to set a certain point in time.
Calendar beepTime = Calendar.getInstance();
beepTime.set(Calendar.DAY_OF_MONTH, 2);
beepTIme.set(Calendar.HOUR_OF_DAY, 01);
beepTime.set(Calendar.MINUTE, 55);
beepTime.set(Calendar.SECOND, 00);
getInstance will set it to the current time, and you can change any variable you like, such as the ones above. For example this would create a time at 1:55 on the 2nd of the current month. You would then set this to be the time to go off with
beepTime.getTimeInMillis()
just pop that into your postAtTime method
Edit: Also I don't know enough about your problem to say for sure, but it may be better to use AlarmManager. I know that that still works even if the program is not running, whereas I don't think PostDelayed does. Feel free to correct me if I'm wrong!

another strange behaviour with GregorianCalendar

Take a look at the piece of code bellow:
Calendar today1 = Calendar.getInstance();
today1.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today1.getTime());
Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14);
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today2.getTime());
I'm quite confused... Assuming I am running it today as July 14th, 2010, the output is:
Fri Jul 16 14:23:23 PDT 2010
Wed Jul 14 00:00:00 PDT 2010
The most annoying thing is that if I add today2.getTimeInMillis() (or any other get() method) it will produce consistent result. For the code bellow:
Calendar today2 = new GregorianCalendar(2010, Calendar.JULY, 14);
today2.getTimeInMillis();
today2.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
System.out.println(today2.getTime());
The result is:
Fri Jul 16 00:00:00 PDT 2010
The answer is actually documented in the JavaDoc for java.util.Calendar
Quoted here:
set(f, value) changes calendar field f to value. In addition, it sets an internal member variable to indicate that calendar field f has been changed. Although field f is changed
immediately, the calendar's
milliseconds is not recomputed until
the next call to get(), getTime(), or
getTimeInMillis() is made.
So that explains the behavior you are seeing, but I concur with another responder to your question that you should consider JodaTime if you're going to do a lot of Date coding.
You should in fact be using Calendar#getInstance() to get an instance and not new GregorianCalendar(). Replace that line by
Calendar today2 = Calendar.getInstance();
today2.set(2010, Calendar.JULY, 14);
and it will go well.
Sorry, no detailed explanation for the behaviour, expect that Calendar along with java.util.Date are one of the major epic failures in the current Java SE API. If you're doing intensive date/time operations, then I'd recommend to have a look at JodaTime. The upcoming new Java 7 will ship with an improved date/time API based on JodaTime (JSR-310).
(Sorry for the edit, I wanted this to be a little more readable, but couldn't get it right when I originally wrote the answer...now it's essay length, but there you go...)
Just to add to what's already been said, the issue arises from the returned Calendar instances being prepared differently. I personally feel like this is a design flaw, but there may be good reason for it.
When you call Calendar.getInstance(), it creates a new GregorianCalendar using the default constructor. This constructor calls setCurrentTimeMillis(time) with the current system time, and then calls the protected method complete().
However, when you create a new GregorianCalendar using the constructor that you did, complete() is never called; instead, among other things, only set(field, value) is called for the various bits of information that is provided. This is all well and good, but it has some confusing consequences.
When complete() is called in the first case, the member variables dustmachine alluded to are checked to determine what information should be recalculated. This results in a branch that forces calculation all of the fields (DAY, WEEK_OF_MONTH, etc.). Note that Calendar is indeed lazy; it just happens that using this method of instantiation forces an explicit recalculation (or in this case initial calculation) on the spot.
So, what impact does this have? Given that no upfront field computation was performed in the case of the second object creation, the two objects have vastly different states. The first has all of its field information populated, while the second only has the information which you provided. When you call the various get*() methods, it shouldn't matter, because any changes should provoke the lazy recalculation step when you retrieve the information. However, the order in which this recalculation occurs exposes the differences between the two varying initial states.
In your particular case, this is due to the following relevant code in computeTime(), which is necessarily invoked to compute the correct time when you request it with getTime():
boolean weekMonthSet = isSet[WEEK_OF_MONTH] || isSet[DAY_OF_WEEK_IN_MONTH];
...
boolean useDate = isSet[DATE];
if (useDate && (lastDateFieldSet == DAY_OF_WEEK
|| lastDateFieldSet == WEEK_OF_MONTH
|| lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) {
useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet);
}
In the first case, all fields are set due to that initial calculation. This allows weekMonthSet to be true, which, along with the DAY_OF_WEEK that you provided in your call to set(field, value) being set, causes useDate to be false.
However, in the second case, as no fields have been calculated, the only fields set are the ones you provided in the constructor and in the subsequent set(field, value) call. Thus, useDate will remain true, because isSet[DATE] is true per your constructor, but weekMonthSet is false as the other fields in the object have not been computed anywhere, nor set by you.
When useDate is true, as implied, it uses your date information to generate the value for the time. When useDate is false, it's able to use your DAY_OF_WEEK information to compute the time you expect, resulting in the difference you see.
Finally, this raises the question of why calling getTimeInMillis() before calling getTime() will fix the unexpected behaviour. As it turns out, the fields will be recalculated as a result of your set(field, value) call in both objects. This just happens to occur after the time is calculated, for whatever (probably genuine) reason. Therefore, forcing the time to be calculated once on the second Calendar will essentially align the states of the two objects. After that, I believe the calls to get*() should all work consistently for both objects.
Ideally, the constructor you used in the second case should perform this initial calculation step in the name of consistency (although maybe for reasons of performance this wouldn't be preferred), but it doesn't, and this is what you get.
So, in short, as the others mentioned, JodaTime is your friend, and clearly these classes are less so. :)

Zero-padding a spinner in Java

How do you add zero padding to a JSpinner?
Since the spinner creates the JFormattedTextField itself, I can't just pass the format into the JFormattedTextField constructor.
Isn't there a way to set the formatting on an existing JFormattedTextField?
What I want: value = 37, editor = "0037"
UPDATE:
I have tried this as suggested:
JSpinner mySpinner = new JSpinner();
mySpinner.setEditor(
new JSpinner.NumberEditor(mySpinner, "####"));
and the result is no change at all to the presentation of the spinner's data. It seems like a reasonable solution; has anyone tried this successfully so I can be sure it's just something flaky in my own application?
You can set the editor yourself, like this:
// minimum of four digits
mySpinner.setEditor(new JSpinner.NumberEditor(mySpinner, "0000"));
"0000" is a DecimalFormat string specifying four digits, zero-padded as necessary; "####" specifies four digits but does not zero-pad.
The DecimalFormat API documentation covers formatting strings in more detail.
Referring to the javadocs, JSpinner has a setEditor(JComponent) method. Use that to set your custom JFormattedTextField, with its custom Format.

Categories

Resources