another strange behaviour with GregorianCalendar - java

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. :)

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.

LocalDateTime class in Java and Period

Have such a simple Java class:
public static void main(String[] args) {
LocalDateTime dateTime = LocalDateTime.of(2017, 11, 26, 15, 38);
Period period = Period.ofYears(1).ofMonths(2).ofDays(3);
dateTime = dateTime.minus(period);
System.out.println(dateTime);
}
}
Which results in 2017-11-23T15:38
Can someone explain me, why a date is subtracted while year and months - doesn't?
Because each three method
(ofYears(1) & ofMonths(2) & ofDays(3))
returns an instance of Period class
Each time you call another method value in Period period gets overriden.
If say ofYears(1) return instance of Period class named a then ofMonths(2) also return a new instance and override he initial one. So, the last one remains and stored to the period variable.
That's why only the ofDays(3) is showing effect.
If you want to do the same you should try calling below method
`public static Period of(int years,
int months,
int days)
It is simple.
Each of the methods, ofYears, ofMonths and ofDays return a new instance of period.
So effectively your period is equal to Period.ofDays(3)
This is not a builder pattern, where you keep modifying the same instance.
In order to achieve what you need, this code will do the work:
LocalDateTime dateTime = LocalDateTime.of(2017, 11, 26, 15, 38);
Period period = Period.of(1, 2, 3);
dateTime = dateTime.minus(period);
System.out.println(dateTime);
prints out
2016-09-23T15:38
Because it works like Period.ofDays(3) overridden every time since they are static method.
Use Period of(int years,int months,int days)
dateTime = dateTime.minus(Period.of(1, 2, 3));
Or you can use withDays, withMonths, withYears like for chaining
Give a Man a Fish, and You Feed Him for a Day. Teach a Man To Fish,
and You Feed Him for a Lifetime.
(It certainly goes for a woman too.)
I am trying to teach you to fish. I am explaining how to catch such an error without having to ask on Stack Overflow first.
Set up your IDE to warn you when you use an object (an instance) for calling a static method. That is whenever you do something like
yourObject.someStaticMethod();
In my Eclipse your code gives this warning:
The static method ofDays(int) from the type Period should be accessed
in a static way
This tells us that ofDays() is a static method, and therefore the result of the call is the same as calling Period.ofDays(3). Eclipse even offers to fix the problem for me:
After I click “Change access to static using Period (declaring type)”, that code line becomes:
Period.ofYears(1).ofMonths(2);
Period period = Period.ofDays(3);
Now I think you can see why you got the result you got.
So:
Make sure your IDE is configured to issue such warnings. It doesn’t have to be Eclipse, other IDEs can do the same.
Read those warnings when you get them and make sure you understand them. If you've skipped a warning once, if you get an unexpected result, go back and read the warning again. If you still need to ask on Stack Overflow, you're welcome of course, and consider including the text of that warning you didn't understand.
Others have nicely explained how to fix your code to give you your desired result. If Period.of(1, 2, 3) is a bit confusing because you can't tell what's years, months weeks and days, use Period.ofYears(1).plusMonths(2).plusDays(3), and everything is clear. It also resembles what you tried in the question.
Link: How to debug small programs with many tips similar to the one I am giving here (and not because yours was a poor question, it’s a good one, I upvoted it).
Remember that all classes in the java.time package are immutable (see Java Docs). Instances of Period cannot be changed after creation, you will have to create a new instance and reassign it.
This is the reason why only the last Period.ofDays(3) has an effect on the result (as previous answers suggest). And it is also the reason why withDays returns a copy of the respective Period.

Simple factory - checking data correctness

I have Month class which contains some data eg. number, number of days etc. I would like to create this class instance simply. I decided to create simple factory which contains months data and returns ready object. My question is where to check data correctness eg. January has 31 days and a couple others... Should Month class checks that data is correct or Factory should be responsible for it? (in this case we can create Month object with invalid data)...
Please don't reinvent calendar classes! Someone has already done it for you. Take a look at Joda-Time or the Java 8 package java.time. This are good starting points to learn how classes like Month should be designed.
The concepts of month and day of month shouldn't be mixed within a single class. Because how many days a month has depends on the year and the calendar system. The validation for invalid number of days should be done when you create a date from day of month, month and year. So it is part of the construction process of a date object.
This looks strange:
Month january = Month.newMonth(31);
The factory method which creates a Month object from an int should check if the parameter is in the range from 1 (January) to 12 (December) and return an enum representing the concrete month.
This is what I would expect:
Month january = Month.newMonth(1);
Your Month class should check this itself. It's always best practice to have the logic that is dependent only on the class in the class itself. Otherwise if someone else uses your application and instantiates a Month object without using the factory it will work, but it won't be what you as the designer intended.
You can handle an error however you like it, but I would do it in the constructor and then throw an error if someone tries instantiating it with invalid parameters.
You should validate the parameters before passing them in a constructor.
However a static factory method might not be the best place to validate input, if the input is dynamic and NOT done by you.
If you just want to make sure the compiler warns you if you use invalid parameters, you could throw a checked exception like this:
public static final Month newMonth(int numberOfDays) {
if(numberOfDays > 31 || numberOfDays < 28) {
throw new IllegalArgumentException("invalid numberOfDays");
}
return new Month(numberOfDays);
}
Either way, in my opinion, you should not go the try...catch way or make the constructor validate anything as it makes your code a lot less readable and also could break a lot of other code if you change your validation method.

Java equivalent of C's _timezone, _daylight and time()

With this C code:
int a = time(NULL);
_daylight = 0;
_timezone = 0;
int b = time(NULL);
assert(a != b);
"a" and "b" will have different values (and not just because they are called a few milliseconds apart). The difference will be whatever the offset of your PC's timezone is from UTC time. Also, changing the _daylight and _timezone values effect pretty much every other function I might use in my C app -- I assume because they all respect that value.
Is there anything like that in Java, or specifically for Java on Android OS? I tried TimeZone.setDefault(), but that didn't change the value that System.currentTimeMillis() returned, so I assume it isn't going to have a "global" effect like the C variables.
I understand that System.currentTimeMillis() is different than time(), in that it "always" returns the number of millis since now and epoch, and the time() function allows you to get "false" (fudged) values that are adjusted according to these global variables you can set.
Just trying to emulate a legacy C app on Android OS. It clears those _timezone and _daylight values which pretty much means it ignores any timezones. So if a user running the app on the west coast enters a time of 3pm, and then they change their timezone settings, or a user on the est coast views that item, it will still show as 3pm.
I know I can use the Calendar object and other methods to make sure I do the proper conversions, but I'd rather just have an easy "I don't care about timezones" settings like I did in the C app and then truely not have to worry about them.
Edit: I would still like to hear what other options I have, but for now I came up with this Java code that I'll do my best to always use for any code that needs to mimic the C app:
// IMPORTANT: Use this function everywhere a Calendar object is needed, instead of calling
// Calendar.getInstance() directly. This returns the correct kludged time that matches
// what our PC application uses (_daylight=0, _timezone=0, time(NULL) in C)
public static Calendar GetCalendarInstance()
{
// Get the current UTC time
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
// Offset it by the system time zone offset.
// This mimics what the C time(NULL) function does when you set _timezone=0 and _daylight=0
cal.add(Calendar.MILLISECOND, TimeZone.getDefault().getOffset(cal.getTimeInMillis()));
return(cal);
}
Also, I did already find one place in my Android app that I need the real, not adjusted, system time (when using AlarmManager to schedule a PendingIntent). So I guess "global" could be dangerous either way. I still think 95% of my code will be using the version that mimics the C app though, so if possible I'd like to default to that and then only have to do special handling for the other few places.

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!

Categories

Resources