Calendar.getActualMaximum(Calendar.WEEK_OF_YEAR) weirdness - java

Either I don't understand the method getActualMaximum(int) or the field WEEK_OF_YEAR, or there's a Sun bug involved (or all three)...could someone explain to me why (at least in a German locale...) the following code:
Locale.setDefault( Locale.GERMAN );
Calendar c = Calendar.getInstance();
c.set( Calendar.YEAR, 2010 );
c.set( Calendar.MONTH, 0 );
c.set( Calendar.DAY_OF_MONTH, 1 );
System.out.println("max: "+c.getActualMaximum( Calendar.WEEK_OF_YEAR ));
System.out.println("actual: "+c.get( Calendar.WEEK_OF_YEAR ));
produces the following output:
max: 52
actual: 53
Here's the Javadoc of getActualMaximum(int):
Returns the maximum value that the
specified calendar field could have,
given the time value of this Calendar.
For example, the actual maximum value
of the MONTH field is 12 in some
years, and 13 in other years in the
Hebrew calendar system.
Edit
The plot thickens. In an English locale (-Duser.language=en -Duser.country=us) the output is:
max: 52
actual: 1
Seems to point to it being a Sun bug for German locales?

This information is correct:
max: 52
actual: 53
The year 2010 has a maximum of 52 weeks. The actual week is 53, since 2009 has maximum 53 weeks and most weeks start on sunday or monday. Week 1 is in most cases the first week of the year with 4 days in january. Since the week of the january 1st has only 2 or 3 days in 2010, the week is considered part of 2009.
Most likely the english locale has different rules for determing week 1, like the first week is the week of january 1st.
Wikipedia explains it correctly: wikipedia week article

The problem is, that January 1st 2010 is in week 53 of 2009 (in Germany), but year 2010 only has 52 weeks (December 31st 2010 is in week 52). The Java Calendar object unfortunately does not have a field for the year, to which the week number relates.

Related

How can I find all week numbers of month that are the # week of month in a year with Java?

I am looking to build up a function that return an array with all week numbers of the previous months in a year that are the same week number of one particular month.
I am using as first day of week Monday and I am taking as first week of month week with the first Monday of current month.
Input: week of year and year. For example, 27 and 2019. The first week of July (7).
Output: array of week of months. For example, [2, 6, 10, 14, 19, 23, 27].
What I try:
private void getResult(int weekYear)
{
LocalDate date = LocalDate.now();
final int weekNumber = 27;
LocalDate newDate = date.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekNumber);
int month = newDate.getMonthValue();;
int weekMonth = LocalDate.from(newDate).get(WeekFields.ISO.weekOfMonth());
System.out.println(newDate);
System.out.println(month);
System.out.println(weekMonth);
ArrayList<Integer> list = new ArrayList<Integer>();
for (int i = 1; i <= month; i++)
{
LocalDate tempDate = date.withYear(2019).withMonth(i).with(WeekFields.ISO.weekOfMonth(), weekMonth);
int tempYear = LocalDate.from(tempDate).get(WeekFields.ISO.weekOfWeekBasedYear());
list.add(tempYear);
}
list.forEach((e) -> System.out.print(e + " "));
}
int weekYear = 27;
getResult(weekYear);
What I get: [1 6 10 14 18 23 27].
What I am looking for: I have two question:
First one: the results obtained are different from those expected. I think the problem is due to the fact that I didn't specify how to calculate the first week of the month (first Monday of the month). Is it right? How can I solve that?
Second one: What is a better solution?
The key here is understanding a few points:
You are numbering weeks in two different ways. For the week of year you are using ISO numbering: the first week of the year is the one that includes at least 4 days of the new year. For week of month you are counting the Mondays (you may say that the first week of the month is the one that includes seven days of the month, not four).
The week number may not always exist. If your starting point is in 0th or the 5th week of the month, a preceding month may not have that week in it.
the results obtained are different from those expected. I think the
problem is due to the fact that I didn't specify how to calculate the
first week of the month (first Monday of the month). Is it right? How
can I solve that?
You are correct. To count the Mondays of the month you may use:
LocalDate tempDate = date.withYear(2019)
.withMonth(i)
.with(ChronoField.DAY_OF_WEEK, DayOfWeek.MONDAY.getValue())
.with(ChronoField.ALIGNED_WEEK_OF_MONTH, weekMonth);
(DayOfWeek.MONDAY.getValue() is just of wordy way of saying 1, of course, but conveys the intention better, so I prefer it.)
With this change to your code the output is the expected:
2 6 10 14 19 23 27
The key is ChronoField.ALIGNED_WEEK_OF_MONTH. The aligned weeks of a month start from the 1st of the month and are always 7 days regardless of the days of the week. The first aligned week is from the 1st through the 7th of the month, the 2nd aligned week if from 8th through 14th, etc. Since we have set the day of week to Monday, setting the aligned week to 1 gives us the 1st Monday of the month, etc.
We’re not done yet. If I set weekNumber to 40, I get:
2 6 10 14 14 23 27 27 36 41
I had expected 40 to be the last number in the list, but it is not there. Week 40 of 2019 is from Monday September 30 through October 6, so if I understand correctly you want the 5th week of those months that have a 5th week. This brings us back to the issue of not all month having a week 5 (because they don’t have 5 Mondays). What happened was that since I ran your code on a Tuesday, it took Tuesday in week 40, which is October 1, as a starting point, and therefore gave me the 1st rather than the 5th week of every month.
are there better solutions? Can you suggest one?
I can’t really. What you’ve got is fine.
Only you’re not using the int weekYear parameter. You may want to use it in place of your weekNumber local variable. In any case you should delete one of them and use the other.
And this unrelated tip: Your use of LocalDate.from(someLocalDate) is redundant since it just gives you the same LocalDate again (either the same object or an equal one, I don’t know or care). Just use someLocalDate in those situations.

Why does java.util.Calendar return minimal WEEK_OF_MONTH value as zero?

I'm trying to set minimal value of WEEK_OF_MONTH field as follows:
calendar.set(WEEK_OF_MONTH, calendar.getActualMinimum(WEEK_OF_MONTH));
The call to
calendar.getActualMinimum(WEEK_OF_MONTH)
returns 0
But at calculation during get* operations this field becomes 5.
Moreover, without leniency mode, I get
java.lang.IllegalArgumentException: WEEK_OF_MONTH: 0 -> 5 // or MONTH: 9 -> 8
at java.util.GregorianCalendar.computeTime(GregorianCalendar.java:2829)
at java.util.Calendar.updateTime(Calendar.java:3393)
at java.util.Calendar.complete(Calendar.java:2265)
at java.util.Calendar.get(Calendar.java:1826)
at Main.main(Main.java:19)
If I set WEEK_OF_MONTH = 1, then I get it correctly as 1.
Check out an example
Can anyone clarify such a behavior? Thanks in advance.
java.time
Locale russia = Locale.forLanguageTag("ru-RU");
WeekFields wf = WeekFields.of(russia);
LocalDate date = LocalDate.now(ZoneId.of("Europe/Moscow"));
int minimumWeekOfMonth = date.with(TemporalAdjusters.firstDayOfMonth()).get(wf.weekOfMonth());
System.out.println("Minimum week of month: " + minimumWeekOfMonth);
LocalDate dateInFirstWeekOfMonth = date.with(wf.weekOfMonth(), minimumWeekOfMonth);
System.out.println("Date in first week of month: " + dateInFirstWeekOfMonth);
When running this snippet just now I got the following output:
Minimum week of month: 1
Date in first week of month: 2018-10-05
I have assumed that you are in Russian locale. Russia uses the international week numbering where Monday is the first day of the week and week one of a year or month is the first week that contains at least 4 days of the year or month. So week 1 of October 2018 was from Monday October 1 through Sunday October 7. This in turn means that the minumum week in this month is 1. Starting out from today (a Friday) and setting the week of month to 1 gives Friday in week 1, that is, Friday October 5.
If I start out from Wednesday September 12 instead I get:
Minimum week of month: 0
Date in first week of month: 2018-08-29
Week 1 of September was from Monday September 3 through September 9. This means that September 1 and 2 were in week 0, so 0 is the minimum week of month for September. And when starting from a Wednesday I set week number to 0, I get the Wednesday of that week, which happens to lie in August: August 29. If we ask for the week of month of that date, do we get 0?
System.out.println("Week of month: " + dateInFirstWeekOfMonth.get(wf.weekOfMonth()));
Output:
Week of month: 5
Since the date is in August, we now get which week of August the date is in, which happens to be week 5.
What happened in your code?
It seems to me that GregorianCalendar.getActualMinimum(Calendar.WEEK_OF_MONTH) always returns 0. I cannot make sense of this observation. Since Russia uses the Gregorian calendar, an instance of GregorianCalendar is what you really get from Calendar.getInstance.
I wouldn’t want to bother. As I said in a comment already, the Calendar class is long outdated and has a range of design problems with it, so I recommend you don’t use it. I’d certainly prefer java.time, the modern Java date and time API, any time.
Link
Oracle tutorial: Date Time explaining how to use java.time.

How to convert months to exact number of days in java

I have a requirement where I have to compare 2 variables. One is difference between 2 dates i.e., "Purchase Date" and "Date Call Received" which is coming as days. And the other one is Warranty length which is coming from UI as Months. Now, I am not able to compare these 2, as one is in months and other is in days. Could someone please help me how to convert months to days so that I can move forward.
for (ModelWarranty warr : modelWarranties)
{
if (null != warr.getWarrantyType()
&& warr.getWarrantyType().equals("WARR")
&& warr.getWarrantyPeriod().equals("0"))
//WARRANTY_PERIOD "0" means value from UI saves in DB as days
{
}
if (null != warr.getWarrantyType()
&& warr.getWarrantyType().equals("WARR")
&& warr.getWarrantyPeriod().equals("1"))
//WARRANTY_PERIOD "1" means value from UI saves in DB as months
{
Integer months = warr.getWarrantyLength();
//how to convert this months into days?
}
if (null != warr.getWarrantyType()
&& warr.getWarrantyType().equals("WARR")
&& warr.getWarrantyPeriod().equals("2"))
//WARRANTY_PERIOD "2" means value from UI saves in DB as years
{
Integer years = warr.getWarrantyLength();
//how to convert this years into days?
}
}
If all you’ve got are those two numbers, you can’t. Say you’ve got 29 days until call was received, and 1 month warranty length. The 29 days could be from January 15 to February 13, less than 1 month. Or they could be from February 15, 2018, to March 16, more than a month. You need to know the purchase date or something else to anchor your days and month to the calendar.
If that were me, I might hand code a conversion table that shows the maximum number of days in a certain number of months, so as to be sure always to give the customer the credit they are entitled to. 1 month can be 31 days. Two months may be 62 days (for example July and August). Three months cannot be more than 92 days (31 + 31 + 30). 12 months may be 366 days, but 24 months can only be 731 days since there are never two leap years in a row. Fill out the rest yourself, please.
Nerdy edit: I believe that you can build your conversion table by counting backward from January 2017, inclusive. So 1 month is January = 31 days. Two months are December 2016 + January 2017 = 31 + 31 = 62. Three months are November 2016 through January 2017. The trick about this way is: You get a group of two 31 days months first. You get two such groups as early as possible (July–August 2016 and December 2016–January 2017). You get the short month, February, as late as possible, and the first time you get it, it’s in a leap year (February 2016). Count up to 48 months. If the warranty is longer, say, 100 months, take that as 48 + 48 + 4 months, look those month counts up individually and sum. Because the leap year cycle is 48 months long (= 4 years). This is not always true, for example year 2100 will not be a leap year; but if the warranty cannot be longer than 199 years, the numbers you get will be correct.
In order to take in count that months can have 30/31/28 days, I would make the comparison in months in the next way:
double numMonths; //number of months you get from your program
double numDays; //number of days you get from your program for the second date
double daysInMonths = numDays * (12/365.25);
double difference = Math.abs(daysInMonths-numMonths);
That way, you can compare the date in months with the date in days without any kind of problem.
Note that a year has 365,25 days, exactly!

Setting October 14 ,1582 fails in java.sql.Date

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.

DateTimeFormatter not working all the time, bug?

I encountered a weird bug while running some code. Here is a simple version to showcase the same.
public class DateTest {
public static void main(String[] args) {
LocalDate decLast = LocalDate.of(2015, 12, 31);
LocalDate novLast = LocalDate.of(2015, 11, 30);
LocalDate octLast = LocalDate.of(2015, 10, 31);
System.out.println(decLast+" "+novLast+" "+octLast);
System.out.println(decLast.format(DateTimeFormatter.ofPattern("dd M YY"))+" "
+novLast.format(DateTimeFormatter.ofPattern("dd M YY"))+" "
+octLast.format(DateTimeFormatter.ofPattern("dd M YY")));
}
}
This returned the following results
2015-12-31 2015-11-30 2015-10-31
31/12/16 30/11/15 31/10/15
Somehow, the 31st Dec 2015 was converted to 31st Dec 2016.
I wrote a for loop to do the same for different years and found that there is variation in many years. The error doesn't exist for any dates below 26th December. Is this a bug or am I missing something here?
The upper case Y is the "week based year", you are looking for the lower case y instead.
From the linked Wikipedia article (emphasis by me):
An ISO week-numbering year (also called ISO year informally) has 52 or 53 full weeks. That is 364 or 371 days instead of the usual 365 or 366 days. The extra week is referred to here as a leap week, although ISO 8601 does not use this term. Weeks start with Monday. The first week of a year is the week that contains the first Thursday of the year (and, hence, always contains 4 January). ISO week year numbering therefore slightly deviates from the Gregorian for some days close to 1 January.
[...]
For example, 29 December 2014 is ISO 2015-W1-1, i.e., it is in year 2015 instead of 2014.
Cf. also: https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html

Categories

Resources