Need to identify number of months between two dates using java 8 but not getting proper number of months. Please find below program
public static void main(String[] args) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy", Locale.ENGLISH);
YearMonth startDate = YearMonth.parse("12/31/2020", formatter);
YearMonth endDate = YearMonth.parse("12/01/2021", formatter);
System.out.println(startDate.plusYears(1) + " :: " + endDate + " :: " + startDate.plusYears(1).isBefore(endDate));
long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate);
System.out.println(monthsBetween);
}
Output ::
2021-12 :: 2021-12 :: false
12
So from above program we are getting 12 months between this 2 dates "12/31/2020" and "12/01/2021"
But actually no of months between above dates is 13 so how we are getting 12 instead of 13?
May be I am wrong but can someone please explain
Note : date format is MM/dd/yyyy
The reason why you are getting 12 months because 'To' month is excluded from the counting.
Here is the between method's implementation,
//-----------------------------------------------------------------------
#Override
public long between(Temporal temporal1Inclusive, Temporal temporal2Exclusive) {
return temporal1Inclusive.until(temporal2Exclusive, this);
}
The answer by pratap is fully correct. From the documentation of between():
The calculation returns a whole number, representing the number of
complete units between the two temporals. For example, the amount in
hours between the times 11:30 and 13:29 will only be one hour as it is
one minute short of two hours.
Another way to say it is that the end month is not inclusive in the calculation. Which in turn means that the solution is to add one month to the end month:
long monthsBetween = ChronoUnit.MONTHS.between(startDate, endDate.plusMonths(1));
Now output is what you wanted:
13
Related
I'm having a problem comparing the date range. I have to validate dates that are within a certain month and year. The month and year are integer values.
NOTE: I´m using OUTSYSTEMS aggregates using Oracle DataBase
Example for two results of a query:
Start Date End Date
1 2020-08-16 2020-10-14
2 2019-11-01 2020-08-15
Case 1
Input:
Month = 9
Year = 2020
Expected Result:
Start Date End Date
1 2020-08-16 2020-10-14
Case 2
Input:
Month = 8
Year = 2020
Expected Result:
Start Date End Date
1 2020-08-16 2020-10-14
2 2019-11-01 2020-08-15
Case 3
Input:
Month = 3
Year = 2020
Expected Result:
Start Date End Date
2 2019-11-01 2020-08-15
Case 4
Input:
Month = 10
Year = 2019
Expected Result: No Row
The selection is in Java Way. I´m using a system function like Month() and Year() to convert the rows to the integers.
Like this
((Month(StartDate) <= Month and Month(EndDate) = Month)
and
(Year(StartDate) <= Year and Year(EndDate) = Year))
or
((Month(StartDate) <= Month and Month(EndDate) = Month)
and
(Year(StartDate) <= Year and Year(EndDate) = Year))
The code above won't work. I try many combinations without success. I have no special comparison functions. For my analysis, I have four scenarios to create to bring the dates that are included in the month and year that I am researching. But I'm not getting the code to work. Someone can light the way for me
A simple approach uses arithmetics:
where year * 100 + month
between year(startdate) * 100 + month(startdate)
and year(enddate) * 100 + month(enddate)
However this probably isn't the most efficient method. In general, you want to avoid applying functions on the column you filter on. A better alternative woul be to convert the year/month parameter to a date - unfortunately you did not tag your database, and date functions are highly vendor-specific, so it is not really possible to suggest.
If you don't want between:
where year * 100 + month >= year(startdate) * 100 + month(startdate)
and year * 100 + month <= year(enddate) * 100 + month(enddate)
Does this work? Considering your inputs m for month and y for year:
StartDate <= AddDays(AddMonths(NewDate(Year(y), Month(m), 1),1)-1)
and
EndDate >= NewDate(Year(y), Month(m), 1))
The thinking is like: filter by all start dates that are lower than the last day of input month and all the end dates that are greater than the first day of input month.
Regarding performance, with this approach you don't have to do any logic/filter on the columns you're filtering on.
The vendor-independent solution
The answer by GMB is nice, I might go with it if it were me. As GMB says, it is vendor specific because the date functions are. If you want a solution that works across database vendors, do the date math in Java so you only need simple date comparisons in the database.
int month = 8;
int year = 2020;
YearMonth ym = YearMonth.of(year, month);
LocalDate monthStart = ym.atDay(1);
LocalDate monthEnd = ym.atEndOfMonth();
When you pass these dates to your query, your search condition may be put simply:
where startDate <= monthEnd and endDate >= monthStart
Here is my code. This method works only for 1 ( current year). When week number is more than 53, dates start from dates in first week in current year but not the next.
Year has 52 or 53 weeks, so when week is last (52or53) i have 2020-12-28 - 2021-01-03, but when I want get first week of next year (2021-01-04 - 2021-01-10) it outputs me first week from 2020 ( 2019-12-30 - 2020-01-05).
public void showPreviousNextWeekDays(DataBaseHelper dataBaseHelper, long weekChange) {
if (weekChange >= 0) {
LocalDate ld = LocalDate.now().plusWeeks(weekChange);
final long weekNumber = ld.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); //takes week from current date
LocalDate firstDayOfWeek = ld.ofYearDay(ld.getYear(), (int)weekNumber)
.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekNumber)
.with(DayOfWeek.MONDAY);
LocalDate lastDayOfWeek = ld.ofYearDay(ld.getYear(), (int)weekNumber)
.with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekNumber)
.with(DayOfWeek.SUNDAY);
String firstAndLastDayOfWeek = firstDayOfWeek.toString() + " - " + lastDayOfWeek.toString();
daysArrayAdapter = new ArrayAdapter<DayModel>(getActivity(), android.R.layout.simple_list_item_1, db.NextWeek(weekChange));
daysList.setAdapter(daysArrayAdapter);
How can i go forward with years also by adding weeks?
You are correct that your code is not correct. When I set weekChange to 14 and run today (September 29), I get 2019-12-30 - 2020-01-05 where expected result would be 2021-01-04 - 2020-01-10. Also for 15 and 16 weeks your code goes back to a week in the beginning of this year rather than finding a week in the beginning of next year as it should.
I suggest the following, which is also a bit simpler:
int weekChange = 14;
LocalDate ld = LocalDate.now(ZoneId.systemDefault()).plusWeeks(weekChange);
LocalDate firstDayOfWeek = ld.with(DayOfWeek.MONDAY);
LocalDate lastDayOfWeek = ld.with(DayOfWeek.SUNDAY);
String firstAndLastDayOfWeek = firstDayOfWeek.toString() + " - " + lastDayOfWeek.toString();
System.out.println(firstAndLastDayOfWeek);
Output when run today:
2021-01-04 - 2021-01-10
LocalDate uses the ISO calendar system including ISO weeks if not explicitly instructed otherwise. So we can be sure that ld.with(DayOfWeek.MONDAY) gives us Monday at the start of the same ISO week. Similarly ld.with(DayOfWeek.SUNDAY) gives us Sunday at the end of the ISO week. So there is nothing more to it.
What happened in your code?
Sticking to the case of weekChange 14 and running today.
ld becomes 2021-01-05 (Tuesday 14 weeks from now). This date is in week 1 of 2021, so weekNumber will be 1.
ofYearDay is a static method of LocalDate. So ld.ofYearDay(ld.getYear(), (int)weekNumber) gives you the same date as you would have got from LocalDate.ofYearDay(ld.getYear(), (int)weekNumber). It does not use ld for anything. Since weekNumber is 1, you get day 1 of 2021, so 2021-01-01. Passing the week number as the day of year to ofYearDay() is meaningless. 2021-01-01 happens to fall in week 53 of 2020. So with(IsoFields.WEEK_OF_WEEK_BASED_YEAR, weekNumber) adjusts the date back to week 1 of 2020. This is where we go a year wrong. The result is 2020-01-03. The Monday of that week is 2019-12-30, which explains why you got this as the first day of week in the output.
The last day of week is similar. And week numbers 2 and 3 are similar since you use the week number as a day of year, and January 2 and 3 fall in the last week of the previous year, 2020, too.
How did I find out? I ran your code in my debugger. There it was clear to see. If you haven’t yet learned to use a debugger. I suggest that now is the best of all times.
This question already has answers here:
Convert day of the year to a date in java
(2 answers)
Closed 3 years ago.
In the below Java class, I need to write the logic of converting an int to Date as explained below. And this program is not related to adding number of days to current date.
If endDay value is 1, then date should print as Jan 1st 2020.
If endDay value is 28, then date should print as Jan 28th 2020.
If endDay value is 35, then date should print as Feb 4th 2020.
If endDay value is 60, then date should print as Feb 29th 2020.
If endDay value is 70, then date should print as March 10th 2020.
Note: value of endDay (1) always starts from January 1st of every year.
import java.util.Date;
public class TestDate
{
public void startEndDate(int endDay)
{
Date date=new Date();
//logic to print the date here
System.out.println("For the given end day of "+endDay+" the date returned is : "+date);
}
public static void main(String[] args)
{
startEndDate(35);
startEndDate(49);
startEndDate(70);
}
}
Can any one suggest me the ideas on how to write the logic for above one ?
java.time and Year.atDay()
public static void startEndDate(int endDay)
{
LocalDate date = Year.of(2020).atDay(endDay);
System.out.println("For the given end day of " + endDay
+ " the date returned is : " + date);
}
Let’s try it out:
startEndDate(35);
startEndDate(49);
startEndDate(70);
Output is:
For the given end day of 35 the date returned is : 2020-02-04
For the given end day of 49 the date returned is : 2020-02-18
For the given end day of 70 the date returned is : 2020-03-10
The documentation of Year.atDay() explains:
Combines this year with a day-of-year to create a LocalDate.
Please fill in your desired year where I put 2020.
I recommend you don’t use Date. That class is poorly designed and long outdated. Instead I am using Year and LocalDate, both from java.time, the modern Java date and time API.
A comment suggested regarding your question as a question of adding a number of days to December 31 of the previous year. IMO regarding it as finding a date from the number of the day-of-year gives a somewhat more elegant solution.
Links
Oracle tutorial: Date Time explaining how to use java.time.
Documentation of Year.atDay(int dayOfYear)
Documentation of LocalDate.ofYearDay(), a good alternative
NOTE THIS IS NOT A DUPLICATE OF EITHER OF THE FOLLOWING
Calculating the difference between two Java date instances
calculate months between two dates in java [duplicate]
I have two dates:
Start date: "2016-08-31"
End date: "2016-11-30"
Its 91 days duration between the above two dates, I expected my code to return 3 months duration, but the below methods only returned 2 months. Does anyone have a better suggestion? Or do you guys think this is a bug in Java 8? 91 days the duration only return 2 months.
Thank you very much for the help.
Method 1:
Period diff = Period.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30"));
Method 2:
long daysBetween = ChronoUnit.MONTHS.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30"));
Method 3:
I tried to use Joda library instead of Java 8 APIs, it works. it loos will return 3, It looks like Java duration months calculation also used days value. But in my case, i cannot use the Joda at my project. So still looking for other solutions.
LocalDate dateBefore= LocalDate.parse("2016-08-31");
LocalDate dateAfter = LocalDate.parse("2016-11-30");
int months = Months.monthsBetween(dateBefore, dateAfter).getMonths();
System.out.println(months);
Since you don't care about the days in your case. You only want the number of month between two dates, use the documentation of the period to adapt the dates, it used the days as explain by Jacob. Simply set the days of both instance to the same value (the first day of the month)
Period diff = Period.between(
LocalDate.parse("2016-08-31").withDayOfMonth(1),
LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(diff); //P3M
Same with the other solution :
long monthsBetween = ChronoUnit.MONTHS.between(
LocalDate.parse("2016-08-31").withDayOfMonth(1),
LocalDate.parse("2016-11-30").withDayOfMonth(1));
System.out.println(monthsBetween); //3
Edit from #Olivier Grégoire comment:
Instead of using a LocalDate and set the day to the first of the month, we can use YearMonth that doesn't use the unit of days.
long monthsBetween = ChronoUnit.MONTHS.between(
YearMonth.from(LocalDate.parse("2016-08-31")),
YearMonth.from(LocalDate.parse("2016-11-30"))
)
System.out.println(monthsBetween); //3
Since Java8:
ChronoUnit.MONTHS.between(startDate, endDate);
//Backward compatible with older Java
public static int monthsBetween(Date d1, Date d2){
if(d2==null || d1==null){
return -1;//Error
}
Calendar m_calendar=Calendar.getInstance();
m_calendar.setTime(d1);
int nMonth1=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
m_calendar.setTime(d2);
int nMonth2=12*m_calendar.get(Calendar.YEAR)+m_calendar.get(Calendar.MONTH);
return java.lang.Math.abs(nMonth2-nMonth1);
}
The documentation of Period#between states the following:
The start date is included, but the end date is not.
Furthermore:
A month is considered if the end day-of-month is greater than or equal to the start day-of-month.
Your end day-of-month 30 is not greater than or equal to your start day-of-month 31, so a third month is not considered.
Note the parameter names:
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
To return 3 months, you can increment the endDateExclusive by a single day.
In case you want stick to java.time.Period API
As per java.time.Period documentation
Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)
where
#param startDateInclusive the start date, inclusive, not null
#param endDateExclusive the end date, exclusive, not null
So it is better to adjust your implementation to make your end date inclusive and get your desired result
Period diff = Period.between(LocalDate.parse("2016-08-31"),
LocalDate.parse("2016-11-30").plusDays(1));
System.out.println("Months : " + diff.getMonths());
//Output -> Months : 3
You have to be careful, never use LocalDateTime to calculate months between two dates the result is weird and incorrect, always use LocalDate !
here's is some code to prove the above:
package stack.time;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
public class TestMonthsDateTime {
public static void main(String[] args) {
/**------------------Date Time----------------------------*/
LocalDateTime t1 = LocalDateTime.now();
LocalDateTime t2 = LocalDateTime.now().minusMonths(3);
long dateTimeDiff = ChronoUnit.MONTHS.between(t2, t1);
System.out.println("diff dateTime : " + dateTimeDiff); // diff dateTime : 2
/**-------------------------Date----------------------------*/
LocalDate t3 = LocalDate.now();
LocalDate t4 = LocalDate.now().minusMonths(3);
long dateDiff = ChronoUnit.MONTHS.between(t4, t3);
System.out.println("diff date : " + dateDiff); // diff date : 3
}
}
My 2%
This example checks to see if the second date is the end of that month. If it is the end of that month and if the first date of month is greater than the second month date it will know it will need to add 1
LocalDate date1 = LocalDate.parse("2016-08-31");
LocalDate date2 = LocalDate.parse("2016-11-30");
long monthsBetween = ChronoUnit.MONTHS.between(
date1,
date2);
if (date1.isBefore(date2)
&& date2.getDayOfMonth() == date2.lengthOfMonth()
&& date1.getDayOfMonth() > date2.getDayOfMonth()) {
monthsBetween += 1;
}
After the short investigation, still not totally fix my question, But I used a dirty solution to avoid return the incorrect duration. At least, we can get the reasonable duration months.
private static long durationMonths(LocalDate dateBefore, LocalDate dateAfter) {
System.out.println(dateBefore+" "+dateAfter);
if (dateBefore.getDayOfMonth() > 28) {
dateBefore = dateBefore.minusDays(5);
} else if (dateAfter.getDayOfMonth() > 28) {
dateAfter = dateAfter.minusDays(5);
}
return ChronoUnit.MONTHS.between(dateBefore, dateAfter);
}
The Java API response is mathematically accurate according to the calendar. But you need a similar mechanism, such as rounding decimals, to get the number of months between dates that matches the human perception of the approximate number of months between two dates.
Period period = Period.between(LocalDate.parse("2016-08-31"), LocalDate.parse("2016-11-30"));
long months = period.toTotalMonths();
if (period.getDays() >= 15) {
months++;
}
My team is looking to switch from Joda time to java.time, but we're seeing different behavior in formatting using the same pattern. The issue arises when we're using the week-of-week-year w symbol:
final String dateString = "2016-01-04 00:00:00";
final String inputPattern = "yyyy-MM-dd HH:mm:ss";
// parse the input string using Joda
final org.joda.time.format.DateTimeFormatter jodaInputFormatter = org.joda.time.format.DateTimeFormat.forPattern(inputPattern);
final org.joda.time.DateTime jodaDateTime = jodaInputFormatter.parseDateTime(dateString);
// parse the input string using two different java.time classes
final java.time.format.DateTimeFormatter javaTimeInputFormatter = java.time.format.DateTimeFormatter.ofPattern(inputPattern).withZone(java.time.ZoneOffset.UTC);
final java.time.LocalDateTime localDateTime = java.time.LocalDateTime.parse(dateString, javaTimeInputFormatter);
final java.time.ZonedDateTime zonedDateTime = java.time.ZonedDateTime.parse(dateString, javaTimeInputFormatter);
final String outputPattern = "'week' w - dd/MM/yyyy HH:mm:ss";
final org.joda.time.format.DateTimeFormatter jodaOutputFormatter = org.joda.time.format.DateTimeFormat.forPattern(outputPattern);
final java.time.format.DateTimeFormatter javaTimeOutputFormatter = java.time.format.DateTimeFormatter.ofPattern(outputPattern);
// output: week 1 - 04/01/2016 00:00:00
System.out.println("With joda: " + jodaOutputFormatter.print(jodaDateTime));
// output: week 2 - 04/01/2016 00:00:00
System.out.println("With LocalDateTime: " + javaTimeOutputFormatter.format(localDateTime));
// output: week 2 - 04/01/2016 00:00:00
System.out.println("With ZonedDateTime: " + javaTimeOutputFormatter.format(zonedDateTime));
For some reason, the output from the w symbol is off-by-one across the two implementations.
What is causing this inconsistency? Is the w symbol inconsistently implemented across Joda time and java.time?
Well, it is a little bit speculative, but since you told me that your system timezone is EST (-05:00) I assume that you are sitting in US (New York?). And US does not apply ISO-8601-week rules. Weeks start on Sunday, and the first week of the year does not need to contain at least 4 days (even one day is enough to be counted as first week of year).
So let's look at your example date of 4th of January. It is a Monday. The first US-week is from 2016-01-01 until 2016-01-02 (2 days - enough for US). And the second US-week starts on Sunday the 3rd of January, so the fourth of January is in the second week, too.
And now the critical point: java.time (JSR-310) uses a localized week of week-based-year for the pattern symbol w, see also its backport which should have the same code. Code excerpt:
} else if (cur == 'w') {
if (count > 2) {
throw new IllegalArgumentException("Too many pattern letters: " + cur);
}
appendInternal(new WeekFieldsPrinterParser('w', count));
...
static final class WeekFieldsPrinterParser implements DateTimePrinterParser {
private final char letter;
private final int count;
public WeekFieldsPrinterParser(char letter, int count) {
this.letter = letter;
this.count = count;
}
#Override
public boolean print(DateTimePrintContext context, StringBuilder buf) {
WeekFields weekFields = WeekFields.of(context.getLocale());
DateTimePrinterParser pp = evaluate(weekFields);
return pp.print(context, buf);
}
The use of WeekFields.of(context.getLocale()) for the pattern symbol "w" is evident.
In contrast, Joda-Time only uses ISO-8601-week-definition which let weeks start on Monday and count that week as first week of year which contains at least four days in current calendar year. So the Monday 4th of January is the start of the first week-of-year because the three days before are not enough for ISO-8601 to be counted as week. Those preceding days are instead considered as last week of previous year.
Consequently, Joda-Time displays week 1 for 4th of January while java.time uses the US-week 2.
Solution of your problem is to specify the locale such that the formatter will use ISO-weeks so you get the same result as in Joda-Time. For example, you could choose Locale.UK which also uses English but other week rules. Don't rely on your default locale. This can fool you.
Edit: As Richard points out, I'm wrong—Java SE actually does say that the first week of a week-based year is the first Monday-based week containing at least four days, just like Joda-Time.
From the documentation of Java SE's IsoFields.WEEK_OF_WEEK_BASED_YEAR:
The week-of-week-based-year has values from 1 to 52, or 53 if the week-based-year has 53 weeks.
No mention is made of excluding any weeks, so it makes sense to assume that all weeks are counted.
From the Joda-Time Fields overview:
Weeks run from 1 to 52-53 in a week based year. The first day of the week is defined as Monday and given the value 1. The first week of a year is defined as the first week that has at least four days in the year.
January 1 and 2 comprise the first partial week of 2016, and since that's fewer than four days, Joda-Time does not count it as a week at all. January 4 is in the first week which contains four or more days.