I'm trying to get the sunday of the same week as a given date.
During this I ran into this problem:
Calendar calendar = Calendar.getInstance(Locale.GERMANY);
calendar.set(2017, 11, 11);
calendar.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(calendar.getTime().toString());
results in "Sun Jan 07 11:18:42 CET 2018"
but
Calendar calendar2 = Calendar.getInstance(Locale.GERMANY);
calendar2.set(2017, 11, 11);
calendar2.getTime();
calendar2.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY);
System.out.println(calendar2.getTime().toString());
gives me the correct Date "Sun Dec 17 11:18:42 CET 2017"
Can someone explain why the first exmple is behaving this way? Is this really intended?
Thanks
Basically, the Calendar API is horrible, and should be avoided. It's not documented terribly clearly, but I think I see where it's going, and it's behaving as intended in this situation. By that I mean it's following the intention of the API authors, not the intention of you or anyone reading your code...
From the documentation:
The calendar field values can be set by calling the set methods. Any field values set in a Calendar will not be interpreted until it needs to calculate its time value (milliseconds from the Epoch) or values of the calendar fields. Calling the get, getTimeInMillis, getTime, add and roll involves such calculation.
And then:
When computing a date and time from the calendar fields, there may be insufficient information for the computation (such as only year and month with no day of month), or there may be inconsistent information (such as Tuesday, July 15, 1996 (Gregorian) -- July 15, 1996 is actually a Monday). Calendar will resolve calendar field values to determine the date and time in the following way.
If there is any conflict in calendar field values, Calendar gives priorities to calendar fields that have been set more recently. The following are the default combinations of the calendar fields. The most recent combination, as determined by the most recently set single field, will be used.
For the date fields:
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
In the first example, the fact that the last field set was "day of week" means it will then use the YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK calculation (I think). The year and month have been set to December 2017, but the week-of-month is the current week-of-month, which is the week 5 of January 2018... so when you then say to set the day of week to Sunday, it's finding the Sunday in the "week 5" of December 2017. December only had 4 weeks, so it's effectively rolling it forward... I think. It's all messy and you shouldn't have to think about that, basically.
In the second example, calling getTime() "locks in" the year/month/day you've specified, and computes the other fields. When you set the day of week, that's then adjusting it within the existing computed fields.
Basically, avoid this API as far as you possibly can. Use java.time, which is a far cleaner date/time API.
As Jon Skeet said, avoid Calendar. For your case it is truly horrible, and it’s poorly designed in general. Instead do
WeekFields weekFieldsForLocale = WeekFields.of(Locale.GERMANY);
// To find out which number Sunday has in the locale,
// grab any Sunday and get its weekFieldsForLocale.dayOfWeek()
int dayNumberOfSundayInLocale = LocalDate.now()
.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY))
.get(weekFieldsForLocale.dayOfWeek());
LocalDate date = LocalDate.of(2017, Month.DECEMBER, 11);
LocalDate sunday
= date.with(weekFieldsForLocale.dayOfWeek(), dayNumberOfSundayInLocale);
System.out.println(sunday);
This prints the expected date
2017-12-17
As others have already mentioned, the solution is to use java.time, the modern Java date and time API. Also generally it is so much nicer to work with. One nice feature is the LocalDate class that I am using. It is a date without time of day, which seems to match your requirements more precisely that Calendar did.
If the above looks complicated, it’s because, as I think you are aware, “Sunday of the same week” means different things in different locales. In the international standard that Germany follows, weeks begin on Monday, so Sunday is the last day of the week. In the American standard, for example, Sunday os the first day of the week. WeekFields.dayOfWeek() numbers the days of the week from 1 to 7, so when we want to set the day to Sunday, we first need to find out which number Sunday has got in this numbering (7 in Germany, 1 in the US). So for any Sunday, get its weekFieldsForLocale.dayOfWeek() value and later use this for setting the day of week to Sunday. The reason why this is necessary is that the with() method is so general and therefore has been designed to accept only numeric values; we can’t just pass it a DayOfWeek object.
If I substitute Locale.US into the code, I get 2017-12-10, which is the correct Sunday for a calendar where Sunday is the first day of the week. If you are sure your only want your code to work for Germany, you may of course just hardcode a 7 (please make it a constant with a very explanatory name).
Link: Oracle Tutorial Date Time explaining how to use java.time. There are other resources on the net (just avoid the outdated placed that suggest java.util.Calendar :-)
Related
How would one calculate a number of days in 1582. Yes, that is the year of introduction of the Georgian Calendar (in some countries). I assume October 1582 should not have 31 days as some of the dates never existed.
Yet when I tried Joda Time (Java/Groovy) it says 30 days:
LocalDate start = new LocalDate("1582-10-01");
LocalDate end = new LocalDate("1582-10-31");
println Days.daysBetween(start, end).getDays();
Same for SQL
-- PostgreSQL
SELECT DATE_PART('day', '1582-10-31'::date - '1582-10-01'::timestamp);
-- MSSQL
SELECT DATEDIFF(dd, '1582-10-31', '1582-10-01');
So is there some agreement/specification to actually treat 1582-10-14 as if it would actually exist? Or is there some easy way to calculate correct diff for year 1582 and earlier?
I have not used Java in many years, but I am familiar with dealing with several calendars in other languages. From the "Key Concepts" subtab of the "Documentation" tab of the Joda Time website we find the "Chronology" page which states
The default chronology in Joda-Time is ISO. This calendar system is
the same as that used by business in the majority of the world today.
The ISO system is unsuitable for historical work before 1583 as it
applies the leap year rules from today back in time (it is a proleptic
calendar). As a result, users requiring a more historically accurate
calendar system are forced to think about their actual requirements,
which we believe is a Good Thing.
Proleptic means that from a known day and date that virtually everyone agrees about, such as the Meter Convention having been signed in Paris on 20 May 1875, the rules of the calendar are applied backward to find any date desired, even if it is before the calendar was created.
As for computing the interval in one calendar, such as the Julian calendar, to a date in a different calendar, such as the Gregorian calendar, a common approach is to convert them both to a count-of-days from a chosen epoch, such as the modified julian date, which counts from midnight universal time at the beginning of November 17, 1858. Then one simply subtracts one day count from the other to find the number of days between them. A quick glance at the Joda Time documentation did not show any facility for computing a day count.
I am currently not set up to program in Java. Ole V.V. comment about using the Gregorian-Julian chronology of Joda-Time seems useful, but I have not tried it:
LocalDate first = new LocalDate(1582, 10, 1, GJChronology.getInstance());
LocalDate last = new LocalDate(1582, 10, 31, GJChronology.getInstance());
int countOfDaysDiff = Days.daysBetween(first, last).getDays();
System.out.println(countOfDaysDiff);
Output according to Ole V.V.:
20
I think I will go ahead and close with that both answers are probably correct. October 1582 did and didn't have 31 days. I mean that 14th October didn't exist (as in no one was born on that day in Gregorian Calendar) and for the purpose of accounting all debts were pushed by ten days. So I guess the only way is to manually count days and don't use any libraries for that.
When establishing Gregorian Calendar it was said that:
we direct and ordain:
that ten days shall be removed from the month of October of the year 1582
But also:
But in order that nobody suffers prejudice by this our subtraction of ten days, in connection with any annual or monthly payments, the judges in any controversies that may arise over this, shall by reason of the said subtraction add ten days to the due date for any such payment.
Source: https://en.wikisource.org/wiki/Translation:Inter_gravissimas
I am writing my stubs in StubbyDB. And asserting the data in functional tests. This is something I am doing in my functional tests to calculate date for assertion (using joda datetime library)
DateTime now = DateTime.now();
DateTime future = now.plusMonths(6);
And this is something I am doing in my stubs;
{{TODAY+6m}}
But I am getting the difference of few days. Is this the bug or am I doing something wrong?
Edit
Consider today is "30 Sept 2016", and I add 5 months to it then
now.plusMonths(5) => 2017-02-28
{{TODAY+5m}} => 2017-03-02
Reason
As per joda-time documentation,
2007-03-31 plus one month cannot result in 2007-04-31, so the day of
month is adjusted to 2007-04-30.
However StubbyDB use javascript based date calculation which adjust date 2007-04-31 to 2007-05-01.
So this is not the bug but this is how these APIs work.
Solution
Found in sample application
use {{JODA_TODAY+6m}} instead of {{TODAY+6m}}
if you start with 30/09/2016 and add five months you get 30/02/2017.
But February only has 28 days.
It looks like Jodatime has "rounded down" to give you the maximum valid date for the month (i.e 28th Feb) whereas the other library/code is treating "30th Feb" as 2nd March (since that is technically two days past the 28th, which the 30th would also be).
Both are valid assumptions for handling dates IMHO and are a good lesson in why date handling is hard. You'll need to be explicit about which convention you want to follow and you may have to code your assertions to follow Jodatime's conventions.
See: DateTime::plusMonths(int)
Returns a copy of this datetime plus the specified number of months.
The calculation will do its best to only change the month field
retaining the same day of month. However, in certain circumstances, it
may be necessary to alter smaller fields. For example, 2007-03-31 plus
one month cannot result in 2007-04-31, so the day of month is adjusted
to 2007-04-30.
So, 30 Sept 2016 + 5 months = 28 Feb 2017 (according to Joda's logic) and it is not a bug
Here is sample code for adding months to given calendar date
public class Demo {
// create a calendar
Calendar cal = Calendar.getInstance()
// print current date
System.out.println("The current date is : " + cal.getTime());
// add 1 months from the calendar
cal.add(Calendar.MONTH, 1);
}
FYR How to add one month to a date and get the same day
If I try to set java.sql.Date as
new java.sql.Date(1582-1900,09,14)
It returns me
1582-10-24
So there is a difference of 10 days. How to solve this problem?
Are you sure that date exists?
Wikipedia says the Gregorian Calender (which is what you are probably using) started on October 15, 1582.
When the new calendar was put in use, the error accumulated in the 13 centuries since the Council of Nicaea was corrected by a deletion of 10 days. The Julian calendar day Thursday, 4 October 1582 was followed by the first day of the Gregorian calendar, Friday, 15 October 1582 (the cycle of weekdays was not affected).
If you need to deal with days before that, you probably have to write some more involved code.
This is due to the calendar being switched from Julian to Gregorian in that year. (The latter has the 100 and 400 leap year corrections that the Julian calendar lacks. This accounts for the 10 day difference that had accumulated.)
Note that some countries - in particular England - did not adopt that calendar until 1752. And Russia, for example, didn't adopt it until well into the 20th century!
As a rule of thumb, if you're working with dates before 1752 then you ought to consult an historian.
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.
I have the following code in a JUnit test, which seemed to work last week is failing this week:
Calendar cal = Calendar.getInstance();
cal.set(2011, Calendar.JULY, 12);
cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); // push the date to 15
System.out.println(cal.get(Calendar.DATE));
As you could probably infer from my comment, since the 12th is a Tuesday, I expect Date to be 15 after setting the DAY_OF_WEEK to Friday. However, the value that is printed is 22, and causes the test to fail.
If I, however change the code as follows, and add an additional call to get:
Calendar cal = Calendar.getInstance();
cal.set(2011, Calendar.JULY, 12);
System.out.println(cal.get(Calendar.DATE));
cal.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY); // push the date to 15
System.out.println(cal.get(Calendar.DATE));
I get the output that I expect, 12 and 15.
Can someone explain what is going on, and why this test was working last week?
The first thing to understand is that Month + Day + DayOfWeek does not mean anything to the Calendar. The Calendar will calculate the true value of the date based on
YEAR + MONTH + DATE
or
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
(Or some other combos like year + day of year etc.) So Date + DayOfWeek doesn't inherently mean much to it.
The second thing to understand is when you set on a Java Calendar it doesn't actually recompute the absolute time or update related fields until an operation that forces computation occurs.
After your first set, the calendar is in a conflicted state. The month and day say that it's July 12th, but the 'week of month' and 'day of week' still say that it's today, whatever today is. You then call set day of week to friday. So now year month and day say July 12th, but the 'week of month' and 'day of week' fields say it's Friday of 'this' week.
The rules of the calendar say that the most recently set field "wins" when there's a conflict, so the week of month and day of week combining to say Friday of this week are what's used to calculate the other fields.
Inserting a get in the middle 'fixes' it because it forces the entire internal state of the calendar to get recomputed to Tuesday July 12th before setting to Friday, so there are no internal conflicts. The 'week of month' got set to the week that contains July 12th by the recalculation prior to you setting day of week to Friday.
Edit: Sorry to make changes after two days, noticed this open in an old browser tab and thought I would expand for the hopeful help of future googlers:
The reason it worked for Jon in the comments is he lives in London. His computer thinks weeks start on Mondays. So when asked for Friday of 'this' week, it still responded July 15th when asked on Sunday July 17th. I bring this up because differing first days of the week in different Locales are just yet another way that trying to use the WEEK_OF fields in a calendar goes haywire.
There is Bug 4655637 (looks similar to your issue). I checked that code under latest JDK6 (Windows) and I have 15 in both cases. BTW: I am suggest to always use GregorianCalendar class explicitly unless you want something else (depending on your locale).
EDIT: official docs:
The following are the default combinations of the calendar fields. The
most recent combination, as determined by the most recently set single
field, will be used.
For the date fields:
YEAR + MONTH + DAY_OF_MONTH
YEAR + MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
YEAR + MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
YEAR + DAY_OF_YEAR
YEAR + DAY_OF_WEEK + WEEK_OF_YEAR
For the time of day fields:
HOUR_OF_DAY
AM_PM + HOUR
In addition to #Affe's clear answer, the following combinations seem to work (as of #GrzegorzSzpetkowski's bug report link)
Calendar expects the following combinations of the fields to determine
a date.
MONTH + DAY_OF_MONTH
MONTH + WEEK_OF_MONTH + DAY_OF_WEEK
MONTH + DAY_OF_WEEK_IN_MONTH + DAY_OF_WEEK
DAY_OF_YEAR
DAY_OF_WEEK + WEEK_OF_YEAR
When you set DAY_OF_WEEK, the calendar expects a week field
(WEEK_OF_MONTH, DAY_OF_WEEK_IN_MONTH or WEEK_OF_YEAR) has also been
set. So, avoid setting DAY_OF_WEEK without setting one of the week
fields.