I know there's a lot of questions about this but I couldn't find one that match with my case.
I have to know if a date is exactly x months after another.
With ChonoUnit it's just not possible because 2020/05/25 and 2020/07/27or 2020/07/25 will return the same number of month and I can't handle the difference with the days. I have the same type of response with JodaTime
I tried with Perdiod and it's not relevant either.
LocalDate localDate = LocalDate.of(2020,1,30);
LocalDate toTest = localDate.plusMonths(1L); // returns 2020/02/29
Period.between(localDate, toTest);
returns months 0, days 30
I read that the endDate is exclusive, so I tried adding a day
LocalDate localDate = LocalDate.of(2020,1,30);
LocalDate toTest = localDate.plusMonths(1L); // returns 2020/02/29
Period.between(localDate, toTest.plusDays(1L));
returns months 1, days 1
So my question is : Is there a method to get this information that is compatible with the way LocalDate handles the months ?
Edit
Ok, I was not precise enough in my request. I know a month is not an absolute value, and my conception of a month doesn't matter.
In my case I have a LocalDate input that I can't contraint. I have to generate outputs every X months minus Y number of days.
Knowing that I test in a schedule if today is exactly X months after my input minus Y days (to be precise, I test if the number of month between the two dates modulo X equals 0). But if my input is the 31st of the month, Period will never return me 1 months, 0 days. If it's the 30st and my output should trigger in february it will never be neither.
Given that if on the short months the output occurs one day before the inexistant logic date it's not a problem.
The exemple I gave with LocalDate is just extracted from my tests but it's the way I thank I could do the thing, thus without doing myself a complicated algorithm with many conditions.
I think you in your description you tried with January and February. That might be causing problem. So you have to manually handle this case. Otherwise your code is working.
A month is not exact time unit. Think about it: we can tell exactly how many seconds are in 1 minute, in 1 hour, 1 day and 1 week. But the number of seconds for different months would be different. So you need to define the logic what do you see as two dates that differ exactly one month? Once you define all the expected behaviors (including such cases as what is exactly one month from January 30th? Feb 28/29? or march 1st?) then you can see if there is anything out of the box that works for you or you need to implement your own logic
I have to know if a date is exactly x months after another.
That is an unclear request. One obvious interpretation would appear to be:
int monthA = a.getYear() * 12 + a.getMonthValue();
int monthB = b.getYear() * 12 + b.getMonthValue();
// Months.between can probably also be used instead of this math.
return a.getDayOfMonth() == b.getDayOfMonth() && (monthA - monthB) == x;
But that does mean it is -impossible- for a date to exist that is 'exactly 1 month after' March 30th: There is no feb 30th and there never will be.
Is that what you really wanted? Given that the length of a month depends on which month you ask, 'exactly 1 month later' is not a sensible question to ask. Hence: What do you actually mean?
Same day-of-month, X months later? (And realizing that there are many dates for which there cannot be such a date).
X days afterwards, where X is some formulaic approach based on which month we are in.
I probably still haven’t got it. Allow me a shot in spite, elaborating on my idea from the comment: If you like the way LocalDate.plusMonths() does, then use that method.
int monthsBetween = 3; // X
int daysBefore = 11; // Y
LocalDate originalDate = LocalDate.of(2020, Month.JANUARY, 30);
int nextTimeCount = 1; // next time to generate output will be the 1st time
LocalDate nextDate = originalDate.plusMonths(nextTimeCount * monthsBetween).minusDays(daysBefore);
LocalDate today = LocalDate.now(ZoneId.systemDefault());
while (! nextDate.isAfter(today)) {
System.out.format("Generating output no. %d for %s%n", nextTimeCount, nextDate);
nextTimeCount++;
nextDate = originalDate.plusMonths(nextTimeCount * monthsBetween).minusDays(daysBefore);
}
Output when I ran just now (December 30):
Generating output no. 1 for 2020-04-19
Generating output no. 2 for 2020-07-19
Generating output no. 3 for 2020-10-19
Assuming that your program will not be running uninterrupted for months you will need some way to persist the data, of course. So my code will not work as it stands, but if the behaviour is otherwise as expected, you can probably use the same mthod calls that I am using.
Related
I only found a solution for Joda Time.
My solution works only if the last day is not in the first week:
LocalDate.now() // or any other LocalDate
.withDayOfMonth(31)
.withMonth(12)
.get(weekFields.weekOfWeekBasedYear())
So what is the correct way in Java Time (like in Joda Time)?
This information is available directly using the java.time.* API.
The key method is rangeRefinedBy(Temporal) on TemporalField. It allows you to obtain a ValueRange object that provides the minimum and maximum values for the field, refined by the temporal object passed in.
To find out how many ISO weeks there are in the year, do the following:
LocalDate date = LocalDate.of(2015, 6, 1);
long weeksInYear = IsoFields.WEEK_OF_WEEK_BASED_YEAR.rangeRefinedBy(date).getMaximum();
System.out.println(weeksInYear);
Note that the date you pass in is used to determine the answer. So when passing in dates in early January or late December ensure you understand how the ISO week-based calendar works, and the difference between the calendar year and the week-based year.
If one wants to get the week number based on 7 days no matter when the week starts and how many days the first partial week of the year has, ChronoField.ALIGNED_WEEK_OF_YEAR might be helpful.
For example, the 1st of January 2016 based on the ISO-8601 definition (where a week starts on Monday and the first week has a minimum of 4 days) falls into week number 0, but in the aligned it is week number 1.
LocalDate date = LocalDate.of(2016, 1, 1);
int iso8601 = date.get(WeekFields.ISO.weekOfYear()); // result is 0
int aligned = date.get(ChronoField.ALIGNED_WEEK_OF_YEAR); // result is 1
It seems that when the last day is in the first week, you don't want to get 1 as an answer but 52/3/4, in which case you may be looking for:
LocalDate.of(2017, 12, 31).get(WeekFields.ISO.weekOfYear());
There are several ways to define week numbers - if that doesn't do what you want you need to clarify which method you want to use.
The correct and best solution is given by #JodaStephen. Here are some alternatives anyways.
December, 28th is always in the last week of a year, because the remaining three days after can not form a major part of another week:
int weeks = LocalDate.of(2017, 12, 28).get(WeekFields.ISO.weekOfYear());
A year has 53 weeks if it starts or ends with a thursday:
Year year = Year.of(2017);
DayOfWeek firstDay = year.atDay(1).getDayOfWeek();
DayOfWeek lastDay = year.atDay(year.length()).getDayOfWeek();
int weeks = firstDay == DayOfWeek.THURSDAY || lastDay == DayOfWeek.THURSDAY ? 53 : 52;
And finally this will give you the "week number" of the last day of year. It's 53 also in cases where the last week's number is 52 iff the major part of the last day's week lies in the next year (the week is claimed by the next year).
// This will not give the correct number of weeks for a given year
Year year = Year.of(2018);
year.atDay(year.length()).get(WeekFields.ISO.weekOfYear()); // 53
That's what you actually did.
So Im working on an Calendar programm and to change the Months im asking for the int of the month.
If I type calendar.get(Calendar.JANUARY) i get 1 as return, which is correct because first month. But if I type calendar.get(Calendar.FEBUARY) i get 2018 in respond which of course is wrong. Does anyone know what the problem could be?
Calendar cal1 = new GregorianCalendar();
int month = cal1.get(Calendar.JANUARY);
int month2 = cal1.get(Calendar.FEBRUARY);
int year = cal1.get(Calendar.YEAR);
java.time
T.J. Crowder’s answer is correct. Allow me to supply the real, good and modern solution to your problem.
ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Atlantic/Stanley"));
// 2018-05-21T05:02:23.942763-03:00[Atlantic/Stanley]
Month monthEnum = zdt.getMonth(); // MAY
int monthNumber = zdt.getMonthValue(); // 5
int year = zdt.getYear(); // 2018
I ran the code just now and have given results in comments after each code line. Please put your desired time zone where I have put Atlantic/Stanley since the month does not change at the same point in time in different time zones. Do you agree with me that this code is clearer and more natural and leaves less room for confusion? BTW, if you’re only interested in the date, not the time of day, use LocalDate instead of ZonedDateTime, the rest of the code will be the same.
To get the numbers of specific months:
int januaryNumber = Month.JANUARY.getValue(); // 1
int februaryNumber = Month.FEBRUARY.getValue(); // 2
What happened in your code?
As T.J. Crowder has already said, Calendar.get expects a field number, for example Calendar.MONTH or Calendar.YEAR. Calendar.JANUARY is 0. Zero?! It’s another confusing thing about Calendar, month numbers are 0-based, that is, they go from 0 for January through 11 for December. Anyway, 0 is also the field number of Calendar.ERA. GregorianCalendar uses 0 for BC (or more religion neutral: BCE for before common era) and 1 for AD (CE, common era). Since your date is in the common era, you get 1. Which, by the way, would have been incorrect for month of January since months are 0-based.
Similarly, Calendar.FEBRUARY equals 1 and coincides with field number Calendar.YEAR, which I why you got the year.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Question: Why is January month 0 in Java Calendar?
Calendar#get gets a field value. Calendar.JANUARY is not a field number, it's a potential field value. The only static fields in Calendar that you use with get (and set) are the ones whose descriptions start with "Field number for get and set...".
To get the month, use cal1.get(Calendar.MONTH).
The Calendar API is...unfortunate, not least because it predates enums and so int was used for get/set's field identifier parameter where an enum was really needed. While get takes an int, and Calendar.JANUARY is an int, they're ints in different domains. See Ole V.V.'s answer for the modern, dramatically-improved alternative: java.time.
I only found a solution for Joda Time.
My solution works only if the last day is not in the first week:
LocalDate.now() // or any other LocalDate
.withDayOfMonth(31)
.withMonth(12)
.get(weekFields.weekOfWeekBasedYear())
So what is the correct way in Java Time (like in Joda Time)?
This information is available directly using the java.time.* API.
The key method is rangeRefinedBy(Temporal) on TemporalField. It allows you to obtain a ValueRange object that provides the minimum and maximum values for the field, refined by the temporal object passed in.
To find out how many ISO weeks there are in the year, do the following:
LocalDate date = LocalDate.of(2015, 6, 1);
long weeksInYear = IsoFields.WEEK_OF_WEEK_BASED_YEAR.rangeRefinedBy(date).getMaximum();
System.out.println(weeksInYear);
Note that the date you pass in is used to determine the answer. So when passing in dates in early January or late December ensure you understand how the ISO week-based calendar works, and the difference between the calendar year and the week-based year.
If one wants to get the week number based on 7 days no matter when the week starts and how many days the first partial week of the year has, ChronoField.ALIGNED_WEEK_OF_YEAR might be helpful.
For example, the 1st of January 2016 based on the ISO-8601 definition (where a week starts on Monday and the first week has a minimum of 4 days) falls into week number 0, but in the aligned it is week number 1.
LocalDate date = LocalDate.of(2016, 1, 1);
int iso8601 = date.get(WeekFields.ISO.weekOfYear()); // result is 0
int aligned = date.get(ChronoField.ALIGNED_WEEK_OF_YEAR); // result is 1
It seems that when the last day is in the first week, you don't want to get 1 as an answer but 52/3/4, in which case you may be looking for:
LocalDate.of(2017, 12, 31).get(WeekFields.ISO.weekOfYear());
There are several ways to define week numbers - if that doesn't do what you want you need to clarify which method you want to use.
The correct and best solution is given by #JodaStephen. Here are some alternatives anyways.
December, 28th is always in the last week of a year, because the remaining three days after can not form a major part of another week:
int weeks = LocalDate.of(2017, 12, 28).get(WeekFields.ISO.weekOfYear());
A year has 53 weeks if it starts or ends with a thursday:
Year year = Year.of(2017);
DayOfWeek firstDay = year.atDay(1).getDayOfWeek();
DayOfWeek lastDay = year.atDay(year.length()).getDayOfWeek();
int weeks = firstDay == DayOfWeek.THURSDAY || lastDay == DayOfWeek.THURSDAY ? 53 : 52;
And finally this will give you the "week number" of the last day of year. It's 53 also in cases where the last week's number is 52 iff the major part of the last day's week lies in the next year (the week is claimed by the next year).
// This will not give the correct number of weeks for a given year
Year year = Year.of(2018);
year.atDay(year.length()).get(WeekFields.ISO.weekOfYear()); // 53
That's what you actually did.
Now I just count number of Thursdays in period from 1st to last date.
Is there one line solutions using Java 8 Date & Time API?
I've tried:
date.range(WeekFields.ISO.weekOfMonth()).getMaximum();
but it gives incorrect result, for example for March,5,2014 it returns 5 while March has only 4 weeks according to ISO8601.
Lets state the question as "how many Thursdays are there in a given month"?
(ISO-8601 does not describe an approach for weeks within months - it only describes an approach for weeks within years).
We can use the approach to find the 5th occurrence of Thursday, and see if it is in the same month:
LocalDate date = ...
LocalDate fifthThu = date.with(TemporalAdjusters.dayOfWeekInMonth(5, THURSDAY));
int thursdays = (fifthThu.getMonth() == date.getMonth() ? 5 : 4);
I have a little question about internal java working and Calendar, i dont understand why when i use for example:
Calendar c = Calendar.getInstance();
mesInt = c.get(Calendar.MONTH);
System.out.println(mesInt);
If then i print mesInt, if the actually month is february i obtain mesInt = 1;
is there any special reason for that? I would like to know because i had problems before with my database, and i would like if it is possible to know the solution of start by 0 the month.
Thanks for all, and sorry if the questions is duplicated i haven not found it.
PD: i know that is zero-based, but i would like to know the real reason for that.
PD2: sry for my bad english. I know the solution i would like to know the reason of that.
The documentation states the first month is 0.
i would like to know the real reason for that.
I suspect starting at 0 is easier to calculate. I am not sure any one person will admit to designing Calendar. Perhaps it made sense at the time as many of the numberings for years, AM/PM etc start at 0. BTW days of the week start at 1.
know i would like if it is possible to know the solution.
Use
int mesInt = c.get(Calendar.MONTH) + 1;
BTW For me the strange one is UNIDECIMBER when uni-deci mean 11 not 13.
Months are 0 based. I'm not sure of the reason why...
So when using either get or set with month you need to remember to either +1 or -1 as required.
Calendar c = Calendar.getInstance();
int mesInt = c.get(Calendar.MONTH) + 1;
c.set(Calendar.MONTH, mesInt - 1);
Months in Calendar starts from zero it means that January -> 0 February -> 1 etc.
The Specification of Calendar.MONTH is as follows:
Field number for get and set indicating the month. This is a calendar-specific value. The first month of the year in the Gregorian and Julian calendars is JANUARY which is 0; the last depends on the number of months in a year.
Essentially for all practical purposes : January is 0 and December is 11.
I bet it's for the same reason array indices start from 0. It's just how it is. February is often displayed as "2", but displaying or printing values is different from using them in code.