I've just came upon a very strange behaviour of Java's Date class when I try to create two dates consequently:
Date startDate = new Date(1282863600000L);
System.out.println(startDate);
Date endDate = new Date(1321919999000L);
System.out.println(endDate);
The output is respectively:
Fri Aug 27 00:00:00 BST 2010
Mon Nov 21 23:59:59 GMT 2011
Has anyone seen something like that? Both date are initialized in an identical manner but when printed the first is shown in BST and the latter in GMT?
I tried to find explanation about that but I didn't. Can someone help me?
Thanks in advance!
This is documented behaviour.
From Date.toString():
Converts this Date object to a String of the form:
dow mon dd hh:mm:ss zzz yyyy
zzz is the time zone (and may reflect daylight saving time). Standard time zone abbreviations include those recognized by the method parse. If time zone information is not available, then zzz is empty - that is, it consists of no characters at all.
You are using a locale that uses British Summer Time and creating a date where a day-light-saving rule applies. This would be the expected form of the date at that time to a local user.
For me the output of this code is
Fri Aug 27 01:00:00 CEST 2010
Tue Nov 22 00:59:59 CET 2011
The exact result depends on the default locale Java is using on your system.
The difference is that CEST is the central european summer time, while CET is the central european time (i.e. not summer time).
You seem to be running in a british locale (en_GB or similar), so your output shows the British Summer Time and the Greenwich Mean Time respectively.
The first date you specify falls into the respective summer times and the second doesn't. So Java chooses the appropriate time zone for each locale/time combination.
After a lovely session of trying different long values I got this:
Date startDate1 = new Date(1284245999999L);
Date startDate2 = new Date(1284246000000L);
System.out.println(startDate1);
System.out.println(startDate2);
Date endDate = new Date(1321919999000L);
System.out.println(endDate);
The output was:
Sun Sep 12 01:59:59 IDT 2010
Sun Sep 12 01:00:00 IST 2010 <-- Long value is greater, but due to DST changes, actual time is one hour earlier
Tue Nov 22 01:59:59 IST 2011
Note that incrementing the long by 1 from 1284245999999L to 1284246000000L takes us "back in time" because of the transition from standard time to daylight savings time.
That is how Java time calculation behaves - the number of milliseconds since 1/1/1970 does not change, but the time it represents is based on the timezone.
Related
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.
I have the following date object Wed Nov 01 00:00:00 GMT 2017. This obviously is in GMT, however, I'd like to consider it to be in a different timezone.
As an example, I'd like to consider the above date in the following timezone US/Mountain and I'd like to then convert it to UTC, resulting in Wed Nov 01 07:00:00 UTC.
I've tried to find a way to change the timezone of a date, while preserving the time, but failed.
Thanks
I understand from you comment that you have got a java.util.Date instance. It prints as (for example) Wed Nov 01 00:00:00 GMT 2017. This is what its toString method produces. The Date doesn’t have a time zone in it. Usually Date.toString() grabs the JVM’s time zone setting and renders the date in this time zone. So it appears you are running GMT time zone? You can read more in this popular blog entry: All about java.util.Date.
If you can, avoid having a Date. The modern Java date and time API known as java.time or JSR-310 is so much nicer to work with, both in general and not least for time zone magic like yours. Then use assylias’ answer.
For this answer I am assuming that you got a Date from some legacy API that you cannot change (or cannot afford to change just now). I still recommend the modern API for the change you desire. The output from the following snippet I give as comments in the code.
System.out.println(oldFashionedDateObject); // Wed Nov 01 00:00:00 GMT 2017
// first thing, convert the Date to an instance of a modern class, Instant
Instant pointInTime = oldFashionedDateObject.toInstant();
// convert to same hour and minute in US/Mountain and then back into UTC
ZonedDateTime convertedDateTime = pointInTime.atOffset(ZoneOffset.UTC)
.atZoneSimilarLocal(ZoneId.of("US/Mountain"))
.withZoneSameInstant(ZoneOffset.UTC);
System.out.println(convertedDateTime); // 2017-11-01T06:00Z
// only assuming you absolutely and indispensably need an old-fashioned Date object back
oldFashionedDateObject = Date.from(convertedDateTime.toInstant());
System.out.println(oldFashionedDateObject); // Wed Nov 01 06:00:00 GMT 2017
As assylias, I got Wed Nov 01 06:00:00. According to Current Local Time in Denver, Colorado, USA summer time (DST) ended on November 5 this year.
With the java time API, you can:
Parse the string as a ZonedDateTime
Use the zonedDateTime.withZoneSameLocal and zonedDateTime.withZoneSameInstant to convert the result
Something like this:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE MMM dd HH:mm:ss z uuuu");
ZonedDateTime gmt = ZonedDateTime.parse("Wed Nov 01 00:00:00 GMT 2017", fmt);
ZonedDateTime mountain = gmt.withZoneSameLocal(ZoneId.of("US/Mountain"));
ZonedDateTime utc = mountain.withZoneSameInstant(ZoneOffset.UTC);
System.out.println(utc.format(fmt));
which, by the way, outputs: Wed Nov 01 06:00:00 Z 2017 (the DST will be in effect on November 3rd only).
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
c.set(2007, 0, 1);
System.out.println(c.getTime());
Output:
Tue Sep 12 12:36:24 IST 2017
Mon Jan 01 12:36:24 IST 2007
But, When I use the same code in a different environment, Output changes to below:
Output:
Tue Sep 12 12:36:24 IST 2017
Mon Jan 01 12:36:24 GMT 2007
FYI, I tried to print the timezone of the calendar instance, before and after setting the values and both are in "IST".
I want to know the root cause of this.
The second output in your question is the correct and expected behaviour on a JVM running Irish time (Europe/Dublin). On September 12, 2017 Ireland is on summer time (DST). While it is not clearly documented, Date.toString() (which you invoke implicitly when printing the Date you get from c.getTime()) prints the date and time in the JVM’s time zone, which in September is rendered as IST for Irish Summer Time.
When you set the date on the Calendar object also using Irish time, the hour of day is preserved; in your case you get Jan 01 2007 12:36:24 Irish standard time. Now imagine the confusion if both Irish Summer Time and Irish Standard Time were rendered as IST. You would not be able to distinguish. Instead, since Irish standard time coincides with GMT, this is what Date.toString() prints when the date is not in the summer time part of the year (which January isn’t).
My guess is that your first output is from a JVM running India time. It too is rendered as IST, and since India doesn’t use summer time, the same abbreviation is given summer and winter.
java.time
Before understanding the explanation for the behaviour you observed, I posted a comment about the outdated and the modern Java date and time classes. I still don’t think the comment is way off, though. This is the modern equivalent of your code:
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Europe/Dublin"));
System.out.println(zdt);
zdt = zdt.with(LocalDate.of(2007, Month.JANUARY, 1));
System.out.println(zdt);
It prints
2017-09-12T11:45:33.921+01:00[Europe/Dublin]
2007-01-01T11:45:33.921Z[Europe/Dublin]
If you want to use the JVM’s time zone setting, use ZoneId.systemDefault() instead of ZoneId.of("Europe/Dublin"). As the name states, contrary to Date, ZonedDateTime does include a time zone. It corresponds more to the old Calendar class. As you can see, its toString method prints the offset from UTC (Z meaning zero offset) and the time zone name in the unambiguous region/city format. I believe that this leaves a lot less room for confusion. If you want to print the date in a specific format, use a DateTimeFormatter.
Appendix: sample output from your code
For the sake of completeness, here are the outputs from your code when running different time zones that may be rendered as IST:
Europe/Dublin (agrees with your second output)
Tue Sep 12 11:19:28 IST 2017
Mon Jan 01 11:19:28 GMT 2007
Asia/Tel_Aviv
Tue Sep 12 13:19:28 IDT 2017
Mon Jan 01 13:19:28 IST 2007
Asia/Kolkata (agrees with your first output)
Tue Sep 12 15:49:28 IST 2017
Mon Jan 01 15:49:28 IST 2007
You need to set time zone and you will get desired result.
TimeZone.setDefault(TimeZone.getTimeZone("IST"));
Here is a working code.
import java.util.Calendar;
import java.util.TimeZone;
public class Cal {
public static void main(String[] args) {
// TODO Auto-generated method stub
TimeZone.setDefault(TimeZone.getTimeZone("IST")); // Add this before print
Calendar c = Calendar.getInstance();
System.out.println(c.getTime());
c.set(2007, 0, 1);
System.out.println(c.getTime());
}
}
As per Doc "Typically, you get a TimeZone using getDefault which creates a TimeZone based on the time zone where the program is running. For example, for a program running in Japan, getDefault creates a TimeZone object based on Japanese Standard Time."
SO when you running in different timezone it is using as default timezone. Hope you clear now. I attach doc. please read.
To talk about this interesting behaviour:
The source code from the Calendar class:
public final void set(int year, int month, int date)
{
set(YEAR, year);
set(MONTH, month);
set(DATE, date);
}
Which leads to the set method:
public void set(int field, int value)
{
// If the fields are partially normalized, calculate all the
// fields before changing any fields.
if (areFieldsSet && !areAllFieldsSet) {
computeFields();
}
internalSet(field, value);
isTimeSet = false;
areFieldsSet = false;
isSet[field] = true;
stamp[field] = nextStamp++;
if (nextStamp == Integer.MAX_VALUE) {
adjustStamp();
}
}
The interesting part here is the computeFields() method which has two implementation (one for Gregorian and one for Japenese calendar). These methods are quite complex but as far as I can see this is the only place where your Calendar instance may change time zone in your usecase.
If i create a new Date() object. What will be the default timezone it will print.
I have my machine running in GMT. And i am creating a new Date() object. If i print why does it shows Thu Jul 05 08:21:05 PKT 2012. How does it takes the timezone as PKT ?
The date itself doesn't have any time zone. Its toString() method uses the current default time zone to return a String representing this date:
Date date = new Date();
System.out.println(TimeZone.getDefault());
System.out.println(date);
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
System.out.println(TimeZone.getDefault());
System.out.println(date);
Executing the above code on my machine leads to the following output:
sun.util.calendar.ZoneInfo[id="Europe/Paris",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=184,lastRule=java.util.SimpleTimeZone[id=Europe/Paris,offset=3600000,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]]
Fri Jul 06 09:24:45 CEST 2012
sun.util.calendar.ZoneInfo[id="UTC",offset=0,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]
Fri Jul 06 07:24:45 UTC 2012
Well actually basic date times usually are time zone agnostic, they don't store time zones. To make use of time zone you use formatters, calendars and the like. Of course the basic date has to be in a default time zone, whatever it might be (usually GMT), otherwise you wouldn't be able to create a local date from the basic date instance.
In you particular case it would help to look into a) the javadocs and b) into the class itself, since JDK is usually distributed with source code of java.util.Date. According to javadoc the java.util.Date class represents number of milliseconds since the standard base time known as "the epoch", namely 1 January 1970, 00:00:00 GMT.
If it shows a different date when printed on your machine, it is because your system will print it using time zone default for your system. You can however print it yourself using any other time zone.
If you are running under linux, there is some magic timezone file somewhere in the system. For my gentoo the magic was just creating a text file /etc/timezone with content
Europe/Berlin
The funny thing is that date gave the correct time and timezone (CEST) all the time, but java sent me to Greenwhich.
> date
Thu Sep 18 08:49:14 CEST 2014
I've written a Java app in which I start the date at Jan 1 00:00 of a particular year. Then I increment the day 355 times and print out the results along the way. I was trying to understand how the timezones work around daylight savings time, and if adding 1 day would shift the time from midnight to 1am when crossing into daylight savings. My code looks like:
TimeZone tz = TimeZone.getDefault();
SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss z");
sdf.setTimeZone(tz);
Calendar cal = Calendar.getInstance(tz);
cal.set(2008, 0, 1, 0, 0, 0);
cal.set(Calendar.MILLISECOND, 0);
for ( int i = 0; i < 355; i++ ) {
System.out.println(sdf.format(cal.getTime()));
cal.add(Calendar.DAY_OF_MONTH, 1);
}
When running in my default time zone (US Eastern), the output looks like as I would expect:
01/01/2008 00:00:00 EST
01/02/2008 00:00:00 EST
01/03/2008 00:00:00 EST
01/04/2008 00:00:00 EST
...
and when I cross into daylight savings time, I get
03/10/2008 00:00:00 EDT
03/11/2008 00:00:00 EDT
03/12/2008 00:00:00 EDT
When I use a timezone that observes Eastern European summer time, (in this case, I chose the Africa/Cairo timezone) I get the output:
01/01/2008 00:00:00 EET
01/02/2008 00:00:00 EET
01/03/2008 00:00:00 EET
01/04/2008 00:00:00 EET
01/05/2008 00:00:00 EET
but when I cross into Eastern European summer time, I get:
04/25/2008 01:00:00 EEST
04/26/2008 01:00:00 EEST
04/27/2008 01:00:00 EEST
And strangely when EEST ends, I still get
08/29/2008 01:00:00 EET
08/30/2008 01:00:00 EET
I'm confused as to why these timezones behave differently and if it is a bug with Africa/Cairo timezone, or if I am misunderstanding how the timezones work...
Any insight into this would be appreciated.
The Cairo time zone changes into daylight saving time at midnight - so the hour between midnight on April 25th 2008 was skipped... the wall clocks went:
04/24/2008 23:59:58
04/24/2008 23:59:59
04/25/2008 01:00:00
04/25/2008 01:00:01
Try adding one day at a time from 2am in the US Eastern time zone and you'll see the same thing - it'll go to 03:00 at some point.
It's not clear what you're trying to achieve exactly - I tend to think it's better to either add "a number of experienced milliseconds" to a date/time in a particular time zone, or add "a number of logical milliseconds" to a local date/time, which doesn't take time zones into account. When you convert from local time to time in a specific zone, you need to consider the possibility of ambiguity (one local time occurring twice) or skipping (one local time not occurring at all).
For added fun, last year Samoa skipped December 30th entirely, due to changing its time zone from -14 to +10...
EDIT: Oh, and my standard recommendation applies: ditch java.util.Calendar/Date, and go with Joda Time