Calendar is very confusing
You are so far from the first being confused about how the Calendar
class works. Fortunately the class is also long outdated. You should not use it.
Why setting calendar AM or PM multiple times change the result? …
To answer your question as asked, Andi80 is correct in the other answer and the comments to it: HOUR
goes from 0 through 11. The documentation says about HOUR
:
Field number for get
and set
indicating the hour of the morning or
afternoon. HOUR
is used for the 12-hour clock (0 - 11). Noon and
midnight are represented by 0, not by 12. E.g., at 10:04:15.250 PM the
HOUR
is 10.
When you first set hour to 12 and AM/PM to PM, one should have expected an exception because the hour value is out of range. But no, a Calendar
object with default settings doesn’t give you that. Instead it sets the time to 0 AM the following day; Jan 13 when you ran the code on Jan 12. By Calendar
logic hour 12 is the hour that comes after hour 11.
When you set PM again, Calendar
takes off from the time you had already got, which is in AM, and changes it into PM, so you get 12:11:08, still on Jan 13, the following day.
Why does it calculate the time twice? Not once and not three times when you do three calls to set()
? It’s another confusing trait of Calendar
. It calculates the time when you call getTime()
(and some designated other methods). At that point it picks up all the changes from the calls to set()
up to that point and combines them to the best of its abilities, discarding some if there are conflicts, using rules that no person in their right mind will want to understand.
The case for AM is similar, so I leave the details to the reader.
java.time
… How to set the time correctly?
I recommend that you use java.time, the modern Java date and time API, for your time work. If you just want 12 noon or 12 midnight, they are built in as constants:
LocalTime t12Noon = LocalTime.NOON;
System.out.println(t12Noon);
LocalTime t12Midnight = LocalTime.MIDNIGHT;
System.out.println(t12Midnight);
Output is:
12:00
00:00
A LocalTime
is a time of day without a date.
If you have already got a time and only want to adjust the hour and AM/PM, use with()
:
LocalTime t12Noon = LocalTime.now(ZoneId.systemDefault())
.with(ChronoField.CLOCK_HOUR_OF_AMPM, 12)
.with(ChronoField.AMPM_OF_DAY, 1); // 1 = PM
System.out.println(t12Noon);
LocalTime t12Midnight = LocalTime.now(ZoneId.systemDefault())
.with(ChronoField.CLOCK_HOUR_OF_AMPM, 12)
.with(ChronoField.AMPM_OF_DAY, 0); // 0 = AM
System.out.println(t12Midnight);
12:47:00.665155
00:47:00.669248
If you need the date too, use ZonedDateTime
or another appropriate class. All of the date-time classes of java.time that include time of day have the same with
method, so the code will be the same.
If you indispensably need a Calendar
object for a legacy API that you cannot afford to upgrade to java.time just now, use a ZonedDateTIme
from java.time for your time math. Then use GregorianCalendar.from(ZoendDateTIme)
for the conversion to a Calendar
object.
Links