Day Light Saving Issue with Java Calendar - java

This is regarding Java Calendar and the effects we encountered after today day light saving change in Toronto.
Below is the code
Date date = new Date(); //Sun Mar 11 00:00:00 EST 2018
Integer time = 349;
Calendar scheduleDateCal = Calendar.getInstance();
scheduleDateCal.setTime(date);
scheduleDateCal.set(Calendar.MINUTE, 0);
scheduleDateCal.set(Calendar.HOUR_OF_DAY, 0);
String strSchAdminTime = String.valueOf(time);
Integer schAdminMinute = time;
if (strSchAdminTime.length() >= 2) {
schAdminMinute = Integer.valueOf(strSchAdminTime.substring(strSchAdminTime.length()-2));
}
if(time>60){
Integer schAdminHour = Integer.valueOf(strSchAdminTime.substring(0,strSchAdminTime.length()-2));
scheduleDateCal.add(Calendar.HOUR_OF_DAY, schAdminHour);
}else{
scheduleDateCal.add(Calendar.HOUR_OF_DAY, 0);
}
scheduleDateCal.add(Calendar.MINUTE, schAdminMinute);
System.out.println(scheduleDateCal.getTime());
I know this code hasn't done with the best practises however I need to maintain it for the current release. In here it uses integer to represent the time portion and later there is a logic to extract the hours and minutes from it.
When I followed the logic, the hour portion is 3. Then there is a logic to add this time to Calendar object with value 'Sun Mar 11 00:00:00 EST 2018' with below statement
scheduleDateCal.add(Calendar.HOUR_OF_DAY, schAdminHour);
Theoretically after this calculation, the calendar object should have value "Sun Mar 11 03:00:00 EDT 2018". However it returns "Sun Mar 11 04:00:00 EDT 2018" I know starting from today the time will go one hour ahead with the daylight saving. Can any one please help me to understand this
Appreciate the help.

At 00:00:00 today (Sunday March 11, 2018) summer time (DST) was not yet in effect, so that time was correctly rendered as Sun Mar 11 00:00:00 EST 2018 (EST for Eastern Standard Time). Date.toString chooses between EST and EDT based on the time contained in the Date object (not based on the time the toString method is called). When you add 3 hours to that time, you cross the time at 2 when the clock was turned forward to 3. So 3 hours after your start time the time is 04:00:00 EDT (EDT for Eastern Daylight Time).
PS Modern code
PS In case you or someone else is interested, here is the modern — both simpler and shorter — version of your code. To set the time to 03:49:
int time = 349;
ZoneId zone = ZoneId.of("America/Toronto");
ZonedDateTime scheduledDateTime = LocalDate.now(zone)
.atTime(time / 100, time % 100)
.atZone(zone);
System.out.println(scheduledDateTime);
Today this printed
2018-03-11T03:49-04:00[America/Toronto]
Still better, of course, if you can get completely rid of representing 03:49 as the integer value 349. To use your JVM’s time zone setting you may set zone to ZoneId.systemDefault(). This is fragile because the setting may be changed at any time by other parts of your program or other programs running in the same JVM.
To set the time to 3 hours 49 minutes after midnight (which with DST transition isn’t the same thing, as you have seen):
ZonedDateTime scheduledDateTime = LocalDate.now(zone)
.atStartOfDay(zone)
.plusHours(time / 100)
.plusMinutes(time % 100);
This time I got
2018-03-11T04:49-04:00[America/Toronto]

EST is -5, EDT is -4, so you get 1 hour when you are calling add().
You can use scheduleDateCal.set(Calendar.HOUR_OF_DAY, schAdminHour) and scheduleDateCal.set(Calendar.MINUTE, schAdminMinute), if you need result in different timezone.

Related

How to set correct time with AM PM with Calendar object?

Simple question why the result is like the following for this code:
Calendar cal2 = Calendar.getInstance();
cal2.set(Calendar.HOUR, 12);
cal2.set(Calendar.AM_PM, Calendar.PM);
System.out.println(cal2.getTime().toString()); // Wed Jan 13 00:11:08 EET 2021
cal2.set(Calendar.AM_PM, Calendar.PM);
System.out.println(cal2.getTime().toString()); // Wed Jan 13 12:11:08 EET 2021
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR, 12);
cal.set(Calendar.AM_PM, Calendar.AM);
System.out.println(cal.getTime().toString()); // Tue Jan 12 12:11:08 EET 2021
cal.set(Calendar.AM_PM, Calendar.AM);
System.out.println(cal.getTime().toString()); // Tue Jan 12 00:11:08 EET 2021
The first looks like it is 12 at midnight not afternoon.
The third one looks like it is 12 afternoon, not midnight
Why setting calendar AM or PM multiple times change the result?
How to set the time correctly?
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
Documentation of Calendar.HOUR
Oracle tutorial: Date Time explaining how to use java.time.
Calendar.HOUR takes inputs in range 0-11. It will wrap around the 12 to a 0.
Use Calendar.HOUR_OF_DAY instead to use values from 0-23.

Future dates in java calendar are giving a strange behaviour

I have an application which I create dates that a user can select to an appointment. If a user start to work at 9, and an appointment takes 2 hours, I create dates at 9, 11, 13... until a limit, of course. And then I change the day and start again.
This is the code for doing this:
public List<Agenda> createListOfDates(Calendar initial, Calendar end,
int appointmentDuration, int lunchTimeDuration, int lunchTimeStart) {
List<Agenda> agendaList = new ArrayList<Agenda>();
Agenda agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
while (true) {
initial.add(Calendar.HOUR_OF_DAY, appointmentDuration);
// Logger.error("" + initial.getTime());
if (initial.getTime().after(end.getTime())) {
break;
} else if (initial.get(Calendar.HOUR_OF_DAY) == lunchTimeStart
&& initial.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY
) {
initial.add(Calendar.HOUR_OF_DAY, lunchTimeDuration);
agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
} else {
agenda = new Agenda();
agenda.setWorkingHour(initial.getTime());
agendaList.add(agenda);
}
}
for(Agenda agendaX : agendaList){
Logger.info("" + agendaX.getWorkingHour());
}
return agendaList;
}
I am working with the "America/Sao_Paulo" timezone to create these dates. I set the variables "initial" and "end" as "America/Sao_Paulo". My system timezone is "GMT", and that is ok, because I want to save these dates in GMT in the database. When I print the dates in last "for", magically it is already converted from "America/Sao_Paulo" to "GMT" and it is printing right. The strange thing is that from a certain date, it changes the time zone. Example of prints:
Sat Mar 30 12:00:00 GMT 2019
Sat Mar 30 14:00:00 GMT 2019
Sat Mar 30 16:00:00 GMT 2019
Sat Mar 30 18:00:00 GMT 2019
Mon Apr 01 13:00:00 BST 2019
Mon Apr 01 15:00:00 BST 2019
Mon Apr 01 18:00:00 BST 2019
Mon Apr 01 20:00:00 BST 2019
Mon Apr 01 22:00:00 BST 2019
While is in GMT, it is right, but I can't understand this BST. Can it be because it's too much in the future? It always starts on April.
Your system time isn’t GMT, it’s Europe/London (or something similar). In March London time coincides with GMT. Not in April. That’s why.
getWorkingHour() returns an instance of Date (another poorly designed and long outdated class, but let that be a different story for now). When you append it to the empty string, Date.toString is implicitly called and builds the string using your system time zone. During standard time it prints GMT as time zone abbreviation. Summer time (DST) begins in London on the last Sunday of March, in this case March 31. So in April Date.toString on your JVM uses British Summer Time and its abbreviation, BST for printing the time.
The good solution involves two changes:
Don’t rely on the JVM’s default time zone. It can be changed at any time from another part of your program or another program running in the same JVM, so is too fragile. Instead give explicit time zone to your date-time operations.
Skip the old date-time classes Calendar and Date and instead use java.time, the modern Java date and time API. It is so much nicer to work with and gives much clearer code, not least when it comes to conversions between time zones.
Instead of Calendar use ZonedDateTime. Depending on the capabilities of your JDBC driver, convert it to either Instant or OffsetDateTime in UTC for saving to the database.
To create a ZonedDateTime, one option is to use one of its of methods (there are several):
ZonedDateTime initial = ZonedDateTime.of(2019, 3, 10, 9, 0, 0, 0, ZoneId.of("America/Sao_Paulo"));
This creates a date-time of March 10, 2019 at 09:00 in São Paolo. To add 2 hours to it:
int appointmentDuration = 2;
ZonedDateTime current = initial.plusHours(appointmentDuration);
System.out.println(current);
Output:
2019-03-10T11:00-03:00[America/Sao_Paulo]
To convert to an Instant for your database:
Instant inst = current.toInstant();
System.out.println(inst);
Output:
2019-03-10T14:00:00Z
Instants are time zone neutral, just a point in time, but print in UTC. Some JDBC drivers accept them for UTC times. If yours doesn’t happen to, you will need to give it an OffsetDateTime instead. Convert like this:
OffsetDateTime odt = current.toOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC);
System.out.println(odt);
Output:
2019-03-10T14:00Z
Note that I give UTC explicitly rather than relying on the JVM default. So this is explicitly in UTC. You notice that the date and time agree with what was printed from the Instant.

Calendar.add changes time zone

In the following code, range equals durationInDays only when range is less than 30. If it is equal to 30 or greater, durationInDays is always range - 1
Calendar c = Calendar.getInstance();
Date now = new Date();
c.setTime(now);
int range = 35;
c.add(Calendar.DATE, range);
Date then = c.getTime();
Duration duration = Duration.between(now.toInstant(), then.toInstant());
int durationInDays = (int)duration.toDays();
When debugging this example, the variables are set as follows:
this = {CalTest#871}
c = {GregorianCalendar#876} "java.util.GregorianCalendar[time=1522449516301,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Dublin",offset=0,dstSavings=3600000,useDaylight=true,transitions=228,lastRule=java.util.SimpleTimeZone[id=Europe/Dublin,offset=0,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2018,MONTH=2,WEEK_OF_YEAR=13,WEEK_OF_MONTH=5,DAY_OF_MONTH=30,DAY_OF_YEAR=89,DAY_OF_WEEK=6,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=11,HOUR_OF_DAY=23,MINUTE=38,SECOND=36,MILLISECOND=301,ZONE_OFFSET=0,DST_OFFSET=3600000]"
now = {Date#877} "Fri Feb 23 23:38:36 GMT 2018"
range = 35
then = {Date#878} "Fri Mar 30 23:38:36 IST 2018"
duration = {Duration#879} "PT839H"
durationInDays = 34
Why does then have an IST timezone? This difference is the causing duration to be a little less than 35 days, rounding to 34.
First, then hasn’t got IST timezone. A Date hasn’t got any time zone. Date.toString chooses a time zone, usually the JVM’s time zone setting, for generating the string only.
Looking at how your two Date objects are rendered in the debugger, one might wonder that it would appear they are rendered in two different time zones. They are not. Both are in Europe/Dublin time zone. As you are probably aware, Ireland with most of the EU switches to summer time (DST) on the last Sunday in March. Therefore, your date in February is in standard time, which in Ireland coincides with GMT, and therefore your string is rendered with GMT as “time zone”. On March 30, summer time is in effect, so the time zone is rendered as IST for Irish Summer Time this time. Edit: the transistion to summer time also accounts for the missing hour compared to your expected duration of 35 days (840 hours).
Edit: Since you can use java.time, the modern Java date and time API, I suggest you go all in and forget about the old-fashioned Date and Calendar. The modern API is so much nicer to work with, and you won’t need all the conversions any longer:
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Europe/Dublin"));
int range = 35;
ZonedDateTime then = now.plusDays(range);
Duration duration = Duration.between(now, then);
int durationInDays = (int) duration.toDays();
The result is still PT839H and hence 34 days.
To obtain a duration in days that agrees with the number of days we added:
int durationInDays = (int) ChronoUnit.DAYS.between(now, then);
System.out.println(durationInDays);
This prints
35
Duration is mostly for durations in hours, minutes and seconds. It does support days, but only days at 24 hours each, so gives the surprising result you saw when used across the summer time transition. ChronoUnit.DAYS on the other hand is exactly made for days. It sees that the two ZonedDateTime instances have the same time-of-day and therefore acknowledges a full 35 days between the two.

How Can I Set Miniumum Hour for Date in Java?

I'm trying to get the date and time of the first day of the prior month; specifically as it is January I'm trying to get: Sun Dec 01 00:00:00 EST 2013. I am using the below code snippet which I have modified from another found here on Stackoverflow while researching this subject; this code snippet will return: Sun Dec 01 12:00:00 EST 2013. I do not understand why setting the minimum hour for the 1st in fact returns noon.
Calendar aCalendar = Calendar.getInstance();
// add -1 month to current month
aCalendar.add(Calendar.MONTH, -1);
// set DATE to 1, so first date of previous month
//aCalendar.set(Calendar.DATE, 1);
aCalendar.set(Calendar.DATE, aCalendar.getActualMinimum(Calendar.DAY_OF_MONTH));
aCalendar.set(Calendar.HOUR, aCalendar.getActualMinimum(Calendar.HOUR));
aCalendar.set(Calendar.MINUTE, aCalendar.getActualMinimum(Calendar.MINUTE));
aCalendar.set(Calendar.SECOND, aCalendar.getActualMinimum(Calendar.SECOND));
Date firstDateOfPreviousMonth = aCalendar.getTime();
If I modify the following line as shown and set 0 I get the same result:
aCalendar.set(Calendar.HOUR, 0);
If I modify it as follows I get 1pm:
aCalendar.set(Calendar.HOUR, 0);
Result: Sun Dec 01 13:00:00 EST 2013
From the Java Docs
HOUR Field number for get and set indicating the hour of the morning
or afternoon.
You'll want to use HOUR_OF_DAY instead
HOUR_OF_DAY Field number for get and set indicating the hour of the
day.
Or set the AM_PM accordingly...
AM_PM Field number for get and set indicating whether the HOUR is
before or after noon.
In any case, take the time to consult the Java Docs
Some example code using Joda-Time 2.3.
Using default time zone, we create a DateTime instance of now (current moment), go back to previous month (in a smart manner, compensating for short month like February), get the first of month by asking for "minimum value", and then get first moment of that first-of-month day.
org.joda.time.DateTime startOfPreviousMonth = new org.joda.time.DateTime().minusMonths(1).dayOfMonth().withMinimumValue().withTimeAtStartOfDay();
Note the method withTimeAtStartOfDay to get the first moment of the day. That moment may or may not be 00:00:00 in local time (because of Daylight Saving Time or other anomalies).
Using explicit time zone (almost always a better practice)…
DateTimeZone timeZone = DateTimeZone.forId( "Europe/Paris" ); // "Asia/Kolkata" etc.
org.joda.time.DateTime startOfPreviousMonth = new org.joda.time.DateTime( timeZone ).minusMonths(1).dayOfMonth().withMinimumValue().withTimeAtStartOfDay();
Get a string…
String string = startOfPreviousMonth; // or startOfPreviousMonth.toString();
To convert back to a java.util.Date instance for communicating with other libraries…
java.util.Date date = startOfPreviousMonth.toDate();

Convert a full Java date (e.g. 8.00am 2013-06-19) to just a time (i.e. 8.00am 1970-01-01)

As part of some logic, it is necessary in my program to turn a long Java timestamp (including year, month, etc.) to a 'short' Java time. This should correspond to exactly the same hours, minutes and seconds of the original time, but within 1 day of Jan 1 1970 (i.e. a value between 0 (00:00:00) and 86400000 (23:59:59)). An example is the conversion in the question.
In order the perform this, I thought the below code would work:
public int convertToTime(long fullTimeStamp) {
Calendar c = Calendar.getInstance();
c.setTimeInMillis(date);
c.set(Calendar.DATE, 1);
c.set(Calendar.MONTH, 0);
c.set(Calendar.YEAR, 1970);
return (int) c.getTimeInMillis();
}
The issue I am having is to do with timezones. In the UK we are currently in BST. After setting all the values with the function, the time remains the same numbers (e.g. 8.00am) but changes the timezone to GMT! 8.00am GMT is of course not the same as 8.00am BST, and is instead equal to 9.00am BST.
Adding some console output to the function demonstrates this issue:
public int convertToTime(long fullTimeStamp) {
System.out.println(new Date(fullTimeStamp)); // correct
Calendar c = Calendar.getInstance();
c.setTimeInMillis(fullTimeStamp);
System.out.println(c.getTime()); // correct
c.set(Calendar.DATE, 1);
c.set(Calendar.MONTH, 0);
c.set(Calendar.YEAR, 1970);
System.out.println(c.getTime()); // incorrect!
return (int) c.getTimeInMillis();
}
Program output:
Wed Jun 19 12:15:00 BST 2013 // ok
Wed Jun 19 12:15:00 BST 2013 // this makes sense
Thu Jan 01 12:15:00 GMT 1970 // Calendar, stahp!
The desired behaviour is for the last part to read:
Thu Jan 01 11:15:00 GMT 1970
or
Thu Jan 01 12:15:00 BST 1970
Is this expected behaviour of the calendar? My understanding was that it keeps all the 'digits' the same that aren't modified, so if the value of HOUR_OF_DAY is 8, it should stay at 8, even if the timezone is modified.
I have tried setting the timezone on the calendar (before any values are set) to BST and GMT and exactly the same behaviour occurs. I also cannot manually add or remove milliseconds to delete all years after 1970 as I will have to handle leap years.
Aside from 'use Joda time (or some other time package)' does anyone have any other suggestions to perform this operation? I kind of need to get a quick fix in before experimenting with other packages if possible.
Thanks!
I think you're running foul of a little-known fact about the UK time zone: at the Unix epoch, we were actually in UTC+1. Java is getting the time of day right (within the UK time zone), but the name wrong - it shouldn't be specifying GMT, but BST. This isn't BST as in British Summer Time; it's BST as in British Standard Time. Yes, it's that mad.
From the relevant wikipedia article:
An inquiry during the winter of 1959–60, in which 180 national organisations were consulted, revealed a slight preference for a change to all-year GMT+1, but the length of summer time was extended as a trial rather than the domestic use of Greenwich Mean Time abolished.[8] A further inquiry during 1966–67 led the government of Harold Wilson to introduce the British Standard Time experiment, with Britain remaining on GMT+1 throughout the year. This took place between 27 October 1968 and 31 October 1971, when there was a reversion to the previous arrangement.
It's worth bearing in mind that your original problem statement is somewhat ambiguous: you're taking in a long, which is just the millis since the Unix epoch - but then you're trying to interpret it in terms of the hour of day, which immediately begs the question of which time zone you need to interpret it in. Have you made that decision? If so, you should document it very carefully, and make sure your code complies with it.
Ultimately, my recommendations are:
If you can possibly use Joda Time, do so. It will save you hours and hours of heartache.
If you're trying to calendar calculations like this, consider changing the time zone of the calendar to UTC before doing anything else; it will save you some heartache
Avoid using Date.toString() where possible - you could use a DateFormatter with the time zone set to UTC, and then you would see the expected results
As user2340612's answer states, to get just the "millisecond of UTC day" you can use simple arithmetic - but not quite with the values given. I would use:
long timeOfDay = millisecondsSinceUnixEpoch % TimeUnit.DAYS.toMillis(1);
... but this only works if you're interested in the UTC time of day for the given instant. (It will also give a negative result for negative input, but you may not care about that.)
If you need a timestamp between 0 and 86399999 (which is 23:59:59.999) you can get the current timestamp and calculate the remainder of the division between it and 86400000:
desired_time = cur_time % 86400000
But you'll miss the summer time, if present.

Categories