I am using calendar object in java to convert an input date(year/month/date etc are getting from network ) to epoch time. I am reusing same calendar object . Sometimes the year i am getting from
network is 0 and there was no validation for this once. Once this happens the whenever i convert the date to epoch, the epoch time im getting is always negative. Is this a valid behaviour
Please find sample code and result I got while doing a unit test for this issue.
Calendar cal= Calendar.getInstance();
cal.set(Calendar.YEAR, 0);
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2020);
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
cal.set(Calendar.YEAR, 2010);
cal.set(Calendar.MONTH, 10);
System.out.println("time 3 : "+cal.getTimeInMillis());
System.out.println("Date 3 : "+new Date(cal.getTimeInMillis()));
Output for this code is
Time 1 : -62151385126938
Date 1 : Sun Jul 04 11:51:13 IST 1
Time 2 : -125866201126938
Date 2 : Wed Jul 04 11:51:13 IST 2020
Time 3 : -125540041126938
Date 3 : Fri Nov 04 11:51:13 IST 2010
Is this an expected behaviour for java Calendar?
My JDK version is
OpenJDK Runtime Environment Corretto-8.252.09.1 (build 1.8.0_252-b09)
Edit:
There are other option/class to fix this issue, But my purpose is to find the root cause of this behaviour
I am using calendar object
Stop doing that. Never use the terrible Calendar and Date classes. Use only the modern java.time classes defined in JSR 310.
Sometimes the year i am getting from network is 0 and there was no validation for this once. Once this happens the whenever i convert the date to epoch, the epoch time im getting is always negative. Is this a valid behaviour
Yes, a negative count of milliseconds since the epoch reference of first moment of 1970 in UTC would be correct for a date with year zero. Year 0000 occurred over two thousands years ago, so that would be a very large number of milliseconds counting backwards from 1970-01-01T00:00Z.
I understand that you see Calendar returning bizarre negative numbers after resetting for contemporary years. If you want more info, see excellent Answer by Ole V.V. In my opinion: there is no purpose to investigating Calendar now that we have java.time. Let’s bury the dead, and move on.
If you must inter-operate with old code not yet updated for java.time, you can convert back and forth between the legacy classes and the modern. Look to new methods added to the old classes. Minimize your use of the old legacy classes.
if( myCalendar instanceof GregorianCalendar )
{
ZonedDateTime zdt = ( ( GregorianCalendar ) myCalendar ).toZonedDateTime() ;
}
…and…
Calendar myCalendar = GregorianCalendar.from( zdt ) ;
You said:
I am reusing same calendar object .
Stop doing that. Reusing objects of that class leads to various problems.
One of the important design considerations in java.time is the use of only immutable objects, to avoid these reuse problems.
am using calendar object in java to convert an input date(year/month/date
You do not show us this code, so I cannot help you any further.
Search Stack Overflow before posting. All this has been covered many times already.
Is this an expected behaviour for java Calendar?
My perhaps polemic answer is that Calendar pretty often behaves contrary to expectations. It has been designed and documented to do so. I understand your confusion.
There is no year 0. So a first expectation might have been that Calendar should throw an exception when you try to set the year of era to 0. With standard settings it doesn’t. Instead it extrapolates: year 0 is taken to mean the year before year 1 , so that is year 1 BC. In your output you can see that the year is 1 (not 0); but what you cannot see is the era, it’s BC, not AD (CE). So let’s add a couple of lines that print the era to your code:
Calendar cal= Calendar.getInstance();
System.out.format("Initial: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
cal.set(Calendar.YEAR, 0);
System.out.format("Year 0: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 1 : "+cal.getTimeInMillis());
System.out.println("Date 1 : "+new Date(cal.getTimeInMillis()));
When I ran the code just now, the output was:
Initial: Era AD year 2020
Year 0: Era BC year 1
time 1 : -62151371618330
Date 1 : Sun Jul 04 11:06:21 CET 1
Next, Calendar.YEAR means year of era, at least to a GregorianCalendar (which is the concrete subclass of Calendar of which you got an instance from Calendar.getInstance()). So when next you set YEAR, that is, year of era, to 2020, the Calendar stays before Christ (before the common era), so you get 2020 BC, more than 4000 years ago. And your milliseconds “grow” to approximately double magnitude of the already negative value.
cal.set(Calendar.YEAR, 2020);
System.out.format("Year 2020: Era %s year %d%n",
cal.getDisplayName(Calendar.ERA, Calendar.SHORT_STANDALONE, Locale.ENGLISH),
cal.get(Calendar.YEAR));
System.out.println("time 2 : "+cal.getTimeInMillis());
System.out.println("Date 2 : "+new Date(cal.getTimeInMillis()));
Year 2020: Era BC year 2020
time 2 : -125866187618330
Date 2 : Wed Jul 04 11:06:21 CET 2020
IMHO it’s a fine case for avoiding the Calendar class.
java.time
And yes, if only for other readers, there are alternatives.
First, you should not try to handle a meaningless year. I don’t know your situation and requirements, so cannot tell you what to do when you get a year that is 0 or negative, but you need to decide, and you need to check and act appropriately. What you have been doing until now is wrong, as you are aware.
Second, very obviously you should use java.time, the modern Java date and time API.
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.systemDefault());
zdt = zdt.withYear(0);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2020);
System.out.println(zdt);
System.out.println(zdt.toEpochSecond());
zdt = zdt.withYear(2010);
System.out.println(zdt);
0000-07-04T11:23:08.573357+00:50:20[Europe/Copenhagen]
-62151197232
2020-07-04T11:23:08.573357+02:00[Europe/Copenhagen]
1593854588
2010-07-04T11:23:08.573357+02:00[Europe/Copenhagen]
You notice in this output that the seconds since the epoch turn positive again after they have been negative. In java.time a year means the proleptic year, that is, a signed year. Proleptic year 0 is 1 BC, -1 means 2 BC, etc. So year 0 is just any other year and doesn’t incur any weird behaviour.
Also java.time does object to setting year of era to 0, which might have helped you discover earlier that you had an issue:
zdt = zdt.with(ChronoField.YEAR_OF_ERA, 0);
Exception in thread "main" java.time.DateTimeException: Invalid value for YearOfEra (valid values 1 - 999999999/1000000000): 0
at java.base/java.time.temporal.ValueRange.checkValidValue(ValueRange.java:311)
at java.base/java.time.temporal.ChronoField.checkValidValue(ChronoField.java:717)
at java.base/java.time.LocalDate.with(LocalDate.java:1048)
at java.base/java.time.LocalDateTime.with(LocalDateTime.java:970)
at java.base/java.time.ZonedDateTime.with(ZonedDateTime.java:1312)
at ovv.so.date.special.SetCalendarYear0.main(SetCalendarYear0.java:25)
Related
For some date in the past, GregorianCalendar.toZonedDateTime() returns a date that is 1 day off.
For 2nd April 1893, toZonedDateTime() returns the same date, for 1st April 1893, ZonedDateTime shows me the 31st March 1893 and there is also a difference in the "day of the year" values. There is always an offset for dates before this "magic" date.
Here is some sample code:
final GregorianCalendar gc = new GregorianCalendar(1893, 0, 1); // Set to 1st January 1893
for(int i = 1; i < 365; i++) {
gc.set(Calendar.DAY_OF_YEAR, i); // Update day of year
final ZonedDateTime zdt = gc.toZonedDateTime();
System.out.println(String.format(
"GC: %02d.%02d.%d (%d) -> ZDT: %02d.%02d.%d (%d)",
gc.get(Calendar.DAY_OF_MONTH),
gc.get(Calendar.MONTH) + 1, // "+1" is needed, because GregorianCalendar encodes January as 0.
gc.get(Calendar.YEAR),
gc.get(Calendar.DAY_OF_YEAR),
zdt.getDayOfMonth(),
zdt.getMonthValue(),
zdt.getYear(),
zdt.getDayOfYear()
));
}
When running the code, you will get the output
[...]
GC: 31.03.1893 (90) -> ZDT: 30.03.1893 (89)
GC: 01.04.1893 (91) -> ZDT: 31.03.1893 (90)
GC: 02.04.1893 (92) -> ZDT: 02.04.1893 (92)
GC: 03.04.1893 (93) -> ZDT: 03.04.1893 (93)
[...]
What am I doing wrong here?
Thanks in advance for your answers!
Best regards,
Markus
What is your system timezone?
I suspect you may be in a locale which observes, or observed, a DST type change on the 1st April 1893. Try printing out the offset value of the ZonedDateTime at each iteration of your loop.
Or a little more info can be gleaned by removing the timezone factor LocalDateTime.ofInstant(zdt.toInstant(), ZoneOffset.UTC)
Seems this is related to Berlin choosing to adopt CET on that date
The tzdata file europe contains only one zone Europe/Berlin for all of Germany.
It is not the best possible choice for several reasons:
- Berlin started CET only in 1893, later than several southern states.
https://mm.icann.org/pipermail/tz/2011-August/008736.html
From the JavaDocs of GregorianCalendar:
public ZonedDateTime toZonedDateTime()
Converts this object to a ZonedDateTime that represents the same point on the time-line as this GregorianCalendar.
Since this object supports a Julian-Gregorian cutover date and ZonedDateTime does not, it is possible that the resulting year, month and day will have different values. The result will represent the correct date in the ISO calendar system, which will also be the same value for Modified Julian Days.
I think it is some kind of non-desired but expected behaviour.
I suspect it’s a bug in GregorianCalendar. I have before seen bugs in Date for dates before 1900 in certain time zones. I consider ZonedDateTime and the other classes from java.time solider and more trustworthy.
Berlin was at offset +0:53:28 until April 1, 1893 00:00. At day of year 89 (for the sake of an example) your code gives a ZonedDateTime of 1893-03-29T23:53:28+00:53:28[Europe/Berlin] giving the correct offset. But both your GregorianCalendar and your ZonedDateTime represent a moment of 1893-03-29T23:00:00Z (UTC), so the GregorianCalendar seems to have assumed an offset of +01:00 instead, which is wrong. The conversion converts the moment correctly (and also the time zone correctly) and therefore the individual date and time fields incorrectly.
Source: For the offset for Berlin up until 1893 go to Time Zone in Berlin, Germany on timeanddate.com. Under “Time Changes in Berlin Over the Years”, in the dropdown on the top right select “1850 — 1899”. For the years 1850–92 you will see “No changes, UTC +0:53:28 hours all of the period”, and a change on April 1, 1893 at 00:00 to CET/UTC +1.
Here’s what I tried within your loop:
System.out.println(gc.getTimeZone().getID());
System.out.println(gc.getTime());
System.out.println(Instant.ofEpochMilli(gc.getTimeInMillis()));
System.out.println();
final ZonedDateTime zdt = gc.toZonedDateTime();
System.out.println(zdt);
System.out.println(zdt.toInstant());
Output for day of year (i) 89 was:
Europe/Berlin
Thu Mar 30 00:00:00 CET 1893
1893-03-29T23:00:00Z
1893-03-29T23:53:28+00:53:28[Europe/Berlin]
1893-03-29T23:00:00Z
Consider this code:
Date date = new SimpleDateFormat("MMddyyyy").parse("01011500");
LocalDate localDateRight = LocalDate.parse(formatter.format(date), dateFormatter);
LocalDate localDateWrong = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();
System.out.println(date); // Wed Jan 01 00:00:00 EST 1500
System.out.println(localDateRight); // 1500-01-01
System.out.println(localDateWrong); // 1500-01-10
I know that 1582 is the cutoff between the Julian and Gregorian calendars. What I don't know is why this happens, or how to adjust for it.
Here's what I've figured out so far:
The date Object has a BaseCalender set to JulianCalendar
date.toInstant() just returns Instant.ofEpochMilli(getTime())
date.getTime() returns -14830974000000
-14830974000000 is Wed, 10 Jan 1500 05:00:00 GMT Gregorian
So it seems like either the millis returned by getTime() is wrong (unlikely) or just different than I expect and I need to account for the difference.
LocalDate handles the proleptic gregorian calendar only. From its javadoc:
The ISO-8601 calendar system is the modern civil calendar system used
today in most of the world. It is equivalent to the proleptic
Gregorian calendar system, in which today's rules for leap years are
applied for all time. For most applications written today, the
ISO-8601 rules are entirely suitable. However, any application that
makes use of historical dates, and requires them to be accurate will
find the ISO-8601 approach unsuitable.
In contrast, the old java.util.GregorianCalendar class (which is indirectly also used in toString()-output of java.util.Date) uses a configurable gregorian cut-off defaulting to 1582-10-15 as separation date between julian and gregorian calendar rules.
So LocalDate is not useable for any kind of historical dates.
But bear in mind that even java.util.GregorianCalendar often fails even when configured with correct region-dependent cut-off date. For example UK started the year on March 25th before 1752. And there are many more historical deviations in many countries. Outside of Europe even the julian calendar is not useable before introduction of gregorian calendar (or best useable only from a colonialist perspective).
UPDATE due to questions in comment:
To explain the value -14830974000000 let's consider following code and its output:
SimpleDateFormat format = new SimpleDateFormat("MMddyyyy", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("America/New_York"));
Date d = format.parse("01011500");
long t1500 = d.getTime();
long tCutOver = format.parse("10151582").getTime();
System.out.println(t1500); // -14830974000000
System.out.println(tCutOver); // default gregorian cut off day in "epoch millis"
System.out.println((tCutOver - t1500) / 1000); // output: 2611699200 = 30228 * 86400
It should be noted that the value -12219292800000L mentioned in your earlier comment is different by 5 hours from tCutOver due to timezone offset difference between America/New_York and UTC. So in timezone EST (America/New_York) we have exactly 30228 days difference. For the timespan in question we apply the rules of julian calendar that is every fourth year is a leap year.
Between 1500 and 1582 we have 82 * 365 days + 21 leap days. Then we have also to add 273 days between 1582-01-01 and 1582-10-01, finally 4 days until cut-over (remember 4th of Oct is followed by 15th of Oct). At total: 82 * 365 + 21 + 273 + 4 = 30228 (what was to be proved).
Please explain to me why you have expected a value different from -14830974000000 ms. It looks correct for me since it handles the timezone offset of your system, the julian calendar rules before 1582 and the jump from 4th of Oct 1582 to cut-over date 1582-10-15. So for me your question "how do I tell the date object to return the ms to the correct Gregorian date?" is already answered - no correction needed. Keep in mind that this complex stuff is a pretty long time in production use and can be expected to work correctly after so many years.
If you really want to use JSR-310 for that stuff I repeat that there is no support for gregorian cut-over date. The best thing is that you might do your own work-around.
For example you might consider the external library Threeten-Extra which contains a proleptic julian calendar since release 0.9. But it will still be your effort to handle the cut-over between old julian calendar and new gregorian calendar. (And don't expect such libraries to be capable of handling REAL historic dates due to many other reasons like new year start etc.)
Update in year 2017: Another more powerful option would be using HistoricCalendar of my library Time4J which handles much more than just julian/gregorian-cutover.
This question already has answers here:
How to subtract n days from current date in java? [duplicate]
(5 answers)
Closed 7 years ago.
I'm trying to subtract 5 days from a date which comes in as a string initially.
I have had a look at some of the other posts on this subject but the result i get from the code is always incorrect. The main problem is that the year value does not seem to change when the days are subtracted for example - 2012-01-01 subtract 5 days gives me 'Jan 27 2012' using this code -
cal.add(Calendar.DATE, -5);
Please help.
Did you know that, in Java, month 1 is actually February?
Date februaryTheFirst = new Date(2012,1,1); // equals 2012-02-01
This might explain what you are seeing. If you want to instantiate 2012-01-01 instead, you should do:
Date firstDayOf2012 = new Date(2012,0,1); // this is 2012-01-01
Exactly the same thing happens when dealing with Calendar:
Calendar.getInstance().set(2012,0,1); // 2012-01-01
Be sure to check the documentation for Date(int, int, int) and Calendar.set(int, int, int). Also, you could check the way you are parsing the string. If you use SimpleDateFormat.parse(...), things can be easier.
Strange, isn't it? Go figure... Just as a fun fact, IntelliJ's documentation annotates this second parameter, month, with #MagicConstant, to remember the programmer that there's something very strange going on.
Calendar.FEBRUARY is 1 and five days before 1 Feb 2012 was 27 Jab 2012.
Your implementation is correct and you are getting the correct value aslo.
Calendar's Months started with 0
0 = Jan
1 = Feb
so subtracting 5 days from 2012-01-01 will definitely returns you Jan 27 2012
something is here also which will helps you Why is January month 0 in Java Calendar?
Joda-Time
The Joda-Time 2.7 library makes this work much easier. Just call the minusDays method.
String input = "2012-01-01";
DateTimeZone zone = DateTimeZone.forID( "America/Montreal" );
DateTime dateTime = new DateTime( input, zone );
DateTime then = now.minusDays( 5 );
DateTimeFormatter formatter = DateTimeFormat.forStyle( "FF" ).withZone( zone ).withLocale( Locale.CANADA_FRENCH );
String output = formatter.print( then );
If you want the beginning of the day, add a call to withTimeAtStartOfDay. This is unnecessary in your case, when parsing a date-only string with no time-of-day.
DateTime dateTimeAtStartOfDay = new DateTime( input, zone ).withTimeAtStartOfDay();
If you want only date without time-of-day or time zone, use LocalDate instead of DateTime.
LocalDate then = new LocalDate( "2012-01-01" ).minusDays( 5 );
If you need to convert to the old java.util.Date, call toDate on the DateTime.
java.time
Java 8 has a new package, java.time. These new classes were inspired by Joda-Time but were re-architected. Both java.time and Joda-Time can solve this particular problem equally well.
Use:
cal.add(Calendar.DAY_OF_MONTH, -5)
EDIT: sorry. DAY_OF_MONTH is a synonym to DATE. Instead of 1 use Calendar.JANUARY.
This a segment of code that is working on my pc. first you have to get the calendar instance the perform your calculation.
Calendar cal = Calendar.getInstance();
System.out.println("Today : " + cal.getTime());
// Subtract 300 days from the calendar
cal.add(Calendar.DATE, -300);
System.out.println("300 days ago: " + cal.getTime());
This is the output that you will get:
Today : Wed Oct 17 10:41:23 EET 2012
300 days ago: Thu Dec 22 10:41:23 EET 2011
When I parse a date with the year 0000 it appears to be stored as the year 0001.
See below for code:
String dateStr = "00000102";
System.out.println(dateStr);
DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
Date date = dateFormat.parse("00000102");
String convertedStr = dateFormat.format(date);
System.out.println(convertedStr);
The output is as per below:
00000102
00010102
Is there a way to represent the year 0000 in Java using the standard Java API?
I don't believe it's possible, since java.util.Date is based on UTC, which is based on the Gregorian calendar, and the Gregorian calendar has no year zero.
...the traditional proleptic Gregorian calendar (like the Julian calendar) does not have a year 0 and instead uses the ordinal numbers 1, 2, … both for years AD and BC. Thus the traditional time line is 2 BC, 1 BC, AD 1, and AD 2.
(Source: The Wikipedia article on the Gregorian calendar)
I don't think the calendar is zero-based. Before 1 AD there was 1 BC. No 0.
Also: what kind of application are you building that needs to handle dates from that era? And if you need to cover that area, consider this: "Dates obtained using GregorianCalendar are historically accurate only from March 1, 4 AD onward, when modern Julian calendar rules were adopted. Before this date, leap year rules were applied irregularly, and before 45 BC the Julian calendar did not even exist."
Year 0 does not exist in the Gregorian calendar. From Year 0 at Wikipedia:
"Year zero" does not exist in the widely used Gregorian calendar or in its predecessor, the Julian calendar. Under those systems, the year 1 BC is followed by AD 1.
...
The absence of a year 0 leads to some confusion concerning the boundaries of longer decimal intervals, such as decades and centuries. For example, the third millennium of the Gregorian calendar began on Monday, 1 January, 2001, rather than the widely celebrated Saturday, 1 January, 2000. Likewise, the 20th century began on 1 January 1901.
...
java.time
I recommend that you use java.time, the modern Java date and time API, for your date work.
As others have said, the Julian/Gregorian calendar that the old Date and SimpleDateFormat classes used does not have a year zero. Before year one in the current era (CE also known as AD) came year 1 before the current era (BCE also known as BC).
A side effect of using java.time is that you do get a year zero! That’s right. java.time uses the proleptic Gregorian calendar, a modern inventions that not only extends the rules of the Gregorian back into times before the Gregorian calendar was invented, but also includes a year 0 before year 1, and a year -1 (minus one) before that. You may say that year 0 corresponds to 1 BCE and -1 to 2 BCE, etc.
So parsing your string is no problem. There’s even a built-in formatter for it.
String dateStr = "00000102";
LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.BASIC_ISO_DATE);
System.out.println("Parsed date is " + date);
String convertedStr = date.format(DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(convertedStr);
Output:
Parsed date is 0000-01-02
00000102
We see that in both output lines year 0000 is printed back as expected.
What went wrong in your code?
When we all agree that there was no year 0, we should have expected your parsing to fail with an exception because of the invalid year. Why didn’t it? It’s one of the many problems with the old SimpleDateFormat class: with default settings it just extrapolates and takes year 0000 to mean the year before year 0001, so year 1 BCE. And falsely pretends that all is well. This explains why year 0001 was printed back: it meant year 1 BCE, but since you didn’t print the era too, this was really hard to tell.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Proleptic Gregorian calendar on Wikipedia.
I have the following problem using Joda-Time for parsing and producing date and time around Daylight Saving Time (DST) hours. Here is an example (please, note that March 30th 2008 is Daylight Saving change in Italy):
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
DateTime x = dtf.parseDateTime("30/03/2008 03:00:00");
int h = x.getHourOfDay();
System.out.println(h);
System.out.println(x.toString("dd/MM/yyyy HH:mm:ss"));
DateTime y = x.toDateMidnight().toDateTime().plusHours(h);
System.out.println(y.getHourOfDay());
System.out.println(y.toString("dd/MM/yyyy HH:mm:ss"));
I get the following output:
3
30/03/2008 03:00:00
4
30/03/2008 04:00:00
When i parse hour I get hour is 3. In my data structure I save the day storing midnight time, and then I have some value for each hour of the day (0-23). Then, when I write out the date, I re-compute the full date time making midnight plus hour. When I sum 3 hours to my midnight I get 04:00:00! And if I parse it again, I get hour 4!
Where is my mistake? Is there some way to get hour 2 when I parse or get hour three when I print out?
I have also tried to build output by hand:
String.format("%s %02d:00:00", date.toString("dd/MM/yyyy"), h);
but in this case for hour 2, I produce 30/03/2008 02:00:00 which is not a valid date (since hour 2 does not exist) and cannot be parsed any more.
Thank you in advance for your help.
Filippo
When I sum 3 hours to my midnight I get 04:00:00! And if I parse it again, I get hour 4! Where is my mistake?
You mentioned already that this date is exactly when the time changes. So there is no mistake. March 30, 2010 00:00 CEST (the timezone in Italy) is precisely speaking March 29, 2010 23:00 UTC. When you add 3 hours, you will get March 30, 2010 02:00 UTC. But this is post the moment, that we switch times (which happens on 01:00 UTC), so when you convert time to local timezone you get March 30, 04:00. That's correct behavior.
Is there some way to get hour 2 when I parse or get hour three when I print out?
No, because March 30, 2010 02:00 CEST does not exist. Precisely at March 30, 2010 01:00 UTC we switch time from +1 hour to +2 hours versus UTC, so March 30, 2010 00:59 UTC is March 30, 2010: 01:59 CEST, but March 30, 2010 01:00 UTC become March 30, 2010 03:00 CEST. No 02:xx hour exist on that particular date.
BTW. In a week you can expect another "fun". Can you tell what date in UTC this refers to:
October 31, 2010 02:15 CEST ?
Well, the funny part is, we do not know. It could be either 0ctober 31, 2010 00:15 UTC (before actual time switch) or October 31, 2010 01:15 UTC (after the switch).
That's exactly why you should always store date and times in relation to UTC and convert them to local time zone before displaying, otherwise you risk an ambiguity.
HTH.
The data structure you are saving your data is not very optimal for the days with daylight saving time. Your day in this particular day should only have 23 hours.
If you do:
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss").withLocale(Locale.US);
DateTime x = dtf.parseDateTime("30/03/2008 00:00:00");
DateTimeFormatter parser = DateTimeFormat.fullDateTime();
System.out.println("Start:"+parser.print(x));
DateTime y = x.plusHours(4);
System.out.println("After add of 4:"+parser.print(y));
You get the expected result, that the time is 05:00.
I recommend that you change the way you store your day and use a date. If not, you must handle daylight saving time when storing the hour of day.
You might do something like this:
In the case where we move the time forward one hour, as this case, you must store 4 and not 5 as the time for 5. And when you calculate the time, you should use the plusHours() method to get the actual time. I think you might get away with something like:
public class DateTest {
private static final int HOUR_TO_TEST = 2;
public static void main(String[] args) {
DateTimeFormatter dtf = DateTimeFormat.forPattern("dd/MM/yyyy HH:mm:ss");
DateTime startOfDay = dtf.parseDateTime("30/03/2008 00:00:00");
/* Obtained from new DateTime() in code in practice */
DateTime actualTimeWhenStoring = startOfDay.plusHours(HOUR_TO_TEST);
int hourOfDay = actualTimeWhenStoring.getHourOfDay();
int hourOffset = startOfDay.plusHours(hourOfDay).getHourOfDay();
System.out.println("Hour of day:" + hourOfDay);
System.out.println("Offset hour:" + hourOffset);
int timeToSave = hourOfDay;
if (hourOffset != hourOfDay) {
timeToSave = (hourOfDay + (hourOfDay - hourOffset));
}
System.out.println("Time to save:" + timeToSave);
/* When obtaining from db: */
DateTime recalculatedTime = startOfDay.plusHours(timeToSave);
System.out.println("Hour of time 'read' from db:" + recalculatedTime.getHourOfDay());
}
}
...or basicly something like that. I'd write a test for it if you choose for going down this route. You can change the HOUR_TO_TEST to see that it moves passed the daylight saving time.
Building on the correct answers by Paweł Dyda & Knubo…
ISO 8601 For String Format
You should never store (serialize) a date-time as a string in the format you mentioned: "30/03/2008 03:00:00". Problems:
Omitted time zone.
Day, Month, Year order is ambiguous.
Should have been translated to UTC time.
If you must serialize a date-time value to text, use a reliable format. The obvious choice is the ISO 8601 standard format. Even better is converting the local time to UTC (Zulu) time zone and then out to ISO 8601 format. Like this: 2013-11-01T04:48:53.044Z
No Midnight
The midnight methods in Joda-Time are deprecated in favor of the Joda-Time method withTimeAtStartOfDay() (see doc). Some days do not have a midnight.
Example Code in Joda-Time 2.3
Some comments about this source code:
// © 2013 Basil Bourque. This source code may be used freely forevery by anyone taking full responsibility for doing so.
// Joda-Time - The popular alternative to Sun/Oracle's notoriously bad date, time, and calendar classes bundled with Java 7 and earlier.
// http://www.joda.org/joda-time/
// Joda-Time will become outmoded by the JSR 310 Date and Time API introduced in Java 8.
// JSR 310 was inspired by Joda-Time but is not directly based on it.
// http://jcp.org/en/jsr/detail?id=310
// By default, Joda-Time produces strings in the standard ISO 8601 format.
// https://en.wikipedia.org/wiki/ISO_8601
Example showing 23 hours in the day of DST (Daylight Saving Time) in Rome Italy, while the day after has 24 hours. Note that the time zone (for Rome) is specified.
// Time Zone list: http://joda-time.sourceforge.net/timezones.html
org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
org.joda.time.DateTime dayOfDstChange = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ) ; // Day when DST
org.joda.time.DateTime dayAfter = dayOfDstChange.plusDays(1);
// How many hours in this day? Should be 23 rather than 24 on day of Daylight Saving Time "springing ahead" to lose one hour.
org.joda.time.Hours hoursObjectForDay = org.joda.time.Hours.hoursBetween(dayOfDstChange.withTimeAtStartOfDay(), dayAfter.withTimeAtStartOfDay());
System.out.println( "Expect 23 hours, got: " + hoursObjectForDay.getHours() ); // Extract an int from object.
// What time is 3 hours after midnight on day of DST change?
org.joda.time.DateTime threeHoursAfterMidnightOnDayOfDst = dayOfDstChange.withTimeAtStartOfDay().plusHours(3);
System.out.println( "Expect 4 AM (04:00) for threeHoursAfterMidnightOnDayOfDst: " + threeHoursAfterMidnightOnDayOfDst );
// What time is 3 hours after midnight on day _after_ DST change?
org.joda.time.DateTime threeHoursAfterMidnightOnDayAfterDst = dayAfter.withTimeAtStartOfDay().plusHours(3);
System.out.println( "Expect 3 AM (03:00) for threeHoursAfterMidnightOnDayAfterDst: " + threeHoursAfterMidnightOnDayAfterDst );
Example of storing a date-time by first translating to UTC. Then upon restoring the date-time object, adjust to the desired time zone.
// Serialize DateTime object to text.
org.joda.time.DateTimeZone romeTimeZone = org.joda.time.DateTimeZone.forID("Europe/Rome");
org.joda.time.DateTime dayOfDstChangeAtThreeHoursAfterMidnight = new org.joda.time.DateTime( 2008, 3, 30, 0, 0, romeTimeZone ).withTimeAtStartOfDay().plusHours(3);
System.out.println("dayOfDstChangeAtThreeHoursAfterMidnight: " + dayOfDstChangeAtThreeHoursAfterMidnight);
// Usually best to first change to UTC (Zulu) time when serializing.
String dateTimeSerialized = dayOfDstChangeAtThreeHoursAfterMidnight.toDateTime( org.joda.time.DateTimeZone.UTC ).toString();
System.out.println( "dateTimeBeingSerialized: " + dateTimeSerialized );
// Restore
org.joda.time.DateTime restoredDateTime = org.joda.time.DateTime.parse( dateTimeSerialized );
System.out.println( "restoredDateTime: " + restoredDateTime );
// Adjust to Rome Italy time zone.
org.joda.time.DateTime restoredDateTimeAdjustedToRomeItaly = restoredDateTime.toDateTime(romeTimeZone);
System.out.println( "restoredDateTimeAdjustedToRomeItaly: " + restoredDateTimeAdjustedToRomeItaly );
When run:
dayOfDstChangeAtThreeHoursAfterMidnight: 2008-03-30T04:00:00.000+02:00
dateTimeBeingSerialized: 2008-03-30T02:00:00.000Z
restoredDateTime: 2008-03-30T02:00:00.000Z
restoredDateTimeAdjustedToRomeItaly: 2008-03-30T04:00:00.000+02:00