I am trying to display number of days in every month of the year
LocalDate start = LocalDate.of(2016, 01, 01);
LocalDate end = start.plusYears(1);
Period everyMonth = Period.ofMonths(1);
for (;start.isBefore(end); start = start.plus(everyMonth)) {
System.out.println(Period.between(start, start.plus(everyMonth)).getDays());
}
Why do I get 12 0s?
You are not using correctly the Period class here. start represents the date 01/01/2016 (in dd/MM/yyyy format). When you are adding a period of 1 month, the result is the date 01/02/2016.
The period between those two dates, as defined by the Period class is "1 month". If you print the period, you will have "P1M", which is the pattern to say that:
A date-based amount of time in the ISO-8601 calendar system, such as '2 years, 3 months and 4 days'.
As such, getDays(), which return the amount of days in the period, will return 0. The result is different than the number of days between the two dates. You can convince yourself of that by printing the result of getMonths, it would return 1:
public static void main(String[] args) {
LocalDate start = LocalDate.of(2016, 01, 01);
Period period = Period.between(start, start.plus(Period.ofMonths(1)));
System.out.println(period.getDays()); // prints 0
System.out.println(period.getMonths()); // prints 1
}
Now, in your question, you want to print the number of days in every month. You can simply have the following:
for (Month month : Month.values()) {
System.out.println(month.length(Year.now().isLeap()));
}
In Java Time, there is an enum Month for all the months, and the method length(leapYear) return the length of this month, i.e. the number of days in the month. Since this depends on whether the current year is a leap year or not, there is a boolean argument for that.
To check for the current year, we can call Year.now() and return if it's a leap year or not with isLeap().
As a side-note, if you truly wanted to print the number of days between two dates, you would need to use ChronoUnit.DAYS.between(start, end).
You are doing everything correctly except one thing. You try to print days in the period, but since you always add 1 month to the date the period is 0 years, 1 month, 0 days. When you call getDays() it returns number of days in period which is 0.
final Period period = Period.between(start, start.plus(everyMonth);
System.out.println(period.getDays()); // 0
System.out.println(period.getMonths()); // 1
I think what you are looking for is:
System.out.println(ChronoUnit.DAYS.between(start, start.plus(everyMonth)));
Related
I'm trying to find how many months are between 2 dates. My code is something like this right now
ChronoUnit.MONTHS.between(d1, d2)
The problem is that the result is a long. For example if the dates differ only in a few days I should get a result something like 0.34 instead of 0.
Also I need my code to account for the calendar, I cant assume each month has 31 days.
Diff between 1999-05-12 and 1999-08-24
Assuming all months have 31 days for simplicity
result = (19/31 + 31/31 + 31/31 + 24/31) = 2.793
According to the calendar we replace the 31s with the correct number of days for that specific year and month
Here is my solution:
public static double monthsBetween(LocalDate start, LocalDate end) {
if (start.isAfter(end)) throw new IllegalArgumentException("Start must be before end!");
var lastDayOfStartMonth = start.with(TemporalAdjusters.lastDayOfMonth());
var firstDayOfEndMonth = end.with(TemporalAdjusters.firstDayOfMonth());
var startMonthLength = (double)start.lengthOfMonth();
var endMonthLength = (double)end.lengthOfMonth();
if (lastDayOfStartMonth.isAfter(firstDayOfEndMonth)) { // same month
return ChronoUnit.DAYS.between(start, end) / startMonthLength;
}
long months = ChronoUnit.MONTHS.between(lastDayOfStartMonth, firstDayOfEndMonth);
double startFraction = ChronoUnit.DAYS.between(start, lastDayOfStartMonth.plusDays(1)) / startMonthLength;
double endFraction = ChronoUnit.DAYS.between(firstDayOfEndMonth, end) / endMonthLength;
return months + startFraction + endFraction;
}
The idea is that you find the last day of start's month (lastDayOfStartMonth), and the first day of end's month (firstDayOfEndMonth) using temporal adjusters. These two dates are very important. The number you want is the sum of:
the fractional number of a month between start and lastDayOfStartMonth
the whole number of months between lastDayOfStartMonth and firstDayOfEndMonth.
the fractional number of a month between firstDayOfEndMonth and end.
Then there is the edge case of when both dates are within the same month, which is easy to handle.
By using this definition, the nice property that the number of months between the first day of any two months is always a whole number is maintained.
Note that in the first calculation, you have to add one day to lastDayOfStartMonth, because ChronoUnit.between treats the upper bound as exclusive, but we actually want to count it as one day here.
To approach this problem, you need to consider the following cases:
dates belong to the same year and month;
dates belong to different year and/or month;
dates are invalid.
When dates belong to the same year and month, then the result would be the difference in days between the two dates divided by the number of days in this month, which can be found using LocalDate.lengthOfMonth().
In the general case, the range of dates can be split into three parts:
two fractional parts at the beginning and at the end of the given range of dates (both could be evaluated using the approach for the simplest case when both data belong to the same year/month)
the whole part, we can use ChronoUnit.MONTHS.between() to calculate it.
Here's how implementation might look like (d1 - inclusive, d2 - exclusive):
public static double getFractionalMonthDiff(LocalDate d1, LocalDate d2) {
if (d1.isAfter(d2)) throw new IllegalArgumentException(); // or return a value like -1
if (d1.getYear() == d2.getYear() && d1.getMonth() == d2.getMonth()) { // dates belong to same month and year
return getFractionalPartOfMonth(d2.getDayOfMonth() - d1.getDayOfMonth(), d1.lengthOfMonth());
}
int monthLen1 = d1.lengthOfMonth();
return getFractionalPartOfMonth(monthLen1 - (d1.getDayOfMonth() - 1), monthLen1) // from the given day of month of the First Date d1 Inclusive to the Last day of month
+ getFractionalPartOfMonth(d2.getDayOfMonth() - 1, d2.lengthOfMonth()) // from the First day of month to given day of month of the Second Date d2 Exclusive (for that reason 1 day was subtracted, and similarly on the previous line 1 day was added)
+ ChronoUnit.MONTHS.between(d1.withDayOfMonth(monthLen1), d2.withDayOfMonth(1));
}
public static double getFractionalPartOfMonth(int daysInterval, int monthLength) {
return daysInterval / (double) monthLength;
}
I using JodaTime and I need to check if 2 dates are in in range of 3 months difference. So I wrote simple method to check this
private boolean inRangeOf3Months(Pair<BusinessData, BusinessData> pair) {
return pair.getKey().getDateTimeValue() != null && pair.getValue().getDateTimeValue() != null ?
new Period(pair.getKey().getDateTimeValue(), pair.getValue().getDateTimeValue()).getMonths() <= 3 : false;
}
Now I'm writing tests and this one is fine
#Test
public void shouldReturnTrueWhenInRangeOf3Months() {
BusinessData closingDateFrom = businessData("closingDateFrom");
closingDateFrom.setDateTimeValue(DateTime.now());
BusinessData closingDateTo = businessData("closingDateTo");
closingDateTo.setDateTimeValue(DateTime.now().plusMonths(3));
ReportingSearchCriteria criteria = criteriaOf(closingDateFrom, closingDateTo);
Assert.assertTrue(validator.isSufficient(criteria));
}
But that one is not, I'm setting first date to now() and second one to now().plusMonths(3).plusDays(1). So its over my range, it shouldn't be allowed.
#Test
public void shouldReturnFalseWhenOverRangeOf3Months() {
BusinessData closingDateFrom = businessData("closingDateFrom");
closingDateFrom.setDateTimeValue(DateTime.now());
BusinessData closingDateTo = businessData("closingDateTo");
closingDateTo.setDateTimeValue(DateTime.now().plusMonths(3).plusDays(1));
ReportingSearchCriteria criteria = criteriaOf(closingDateFrom, closingDateTo);
Assert.assertFalse(validator.isSufficient(criteria));
}
Period of 3 months and a few days still have a difference of 3 months.
So if you want to make it 3 months inclusive you can add one day to last date and use just less than (instead of less than or equal):
new Period(pair.getKey().getDateTimeValue(), pair.getValue().getDateTimeValue().plusDays(1)).getMonths() < 3
Also, pay attention that the second date should be later the first one (otherwise it won't work).
If i understand your comparison must be on total days not on months because for example let's consider today's date as first date and second date with addition of 3 months plus 25 days
DateTime date1 = DateTime.now();
System.out.println(date1);
DateTime date2 = DateTime.now().plusMonths(3).plusDays(25);
System.out.println(date2);
Period p = new Period(date1,date2);
System.out.println(p.getMonths());
Output
2019-08-09T04:31:07.400-05:00
2019-12-04T04:31:07.449-06:00
3
Still it returns difference of months is 3 because the difference is 3 months and 25 days were 4 months not completed yet. Now if you add 3 months plus 30 days to second date it will return difference as 4 months (some times you might need to add 31 days because it depends on month that has 31 days)
DateTime date1 = DateTime.now();
System.out.println(date1);
DateTime date2 = DateTime.now().plusMonths(3).plusDays(30);
System.out.println(date2);
Period p = new Period(date1,date2);
System.out.println(p.getMonths());
Output
2019-08-09T04:34:29.530-05:00
2019-12-09T04:34:29.579-06:00
4
From the above approach you can't even take the months of two days and check whether difference is 3 months or not, because second date may return third month in some cases (like above if date is middle of month and if you add couple of days to it).
I will suggest compare the total days, for example total no of days between two days > 90 or > 91 for this you can use Duration
DateTime date1 = DateTime.now();
System.out.println(date1);
DateTime date2 = DateTime.now().plusMonths(3).plusDays(1);
System.out.println(date2);
Duration d = new Duration(date1, date2);
System.out.println(d.getStandardDays());
Output
2019-08-09T04:40:47.334-05:00
2019-11-10T04:40:47.381-06:00
93
Seems like the easiest is to just compare the 3 month away marks (buth past and future) to the other date.
public boolean areWithin3Months(DateTime dateTime1, DateTime dateTime2) {
return !dateTime1.minusMonths(3).isAfter(dateTime2)
&& !dateTime1.plusMonths(3).isBefore(dateTime2);
}
I would like to reach date that is -1month +1day, which should be 0month difference from start date.
Using joda-time 2.10:
int day = 29;
LocalDate date1 = new LocalDate(new GregorianCalendar(2019, Calendar.JUNE, day).getTime());
LocalDate date2 = date1.plusMonths(-1).plusDays(1);
Months.monthsBetween(date1,date2).getMonths(); // returns 0 <- it's OK
but the same code with input int day = 30; returns -1 which is bad.
That looks like an inconsequence in Joda library.
That's a case: shift by -1month change date by shift month number and keep day number no greater than in input, but month-difference between dates are depend on day of month.
Do you know any alternative and working solution?
I have found JSR-310 with ChronoUnit - that solves the problem, BUT it needs Java8. I would like to stay on Java7.
For a span of time running from one date to another date, how to get the number of calendar months containing one or more days of my span?
So for example:
2016-01-23/2016-01-23 = 1 calendar month (January)
2016-01-31/2016-02-01 = 2 calendar months (January, February)
2016-01-23/2016-02-28 = 2 calendar months (January, February)
2016-01-15/2016-03-15 = 3 calendar months (January, February, March)
2016-01-15/2017-03-15 = 15 calendar months (Jan-Dec of 2016 plus January, February, March of 2017)
I do not define a month as “30 days”. I am asking about calendar months, January-December.
Similar to this Question but that asks about PHP/MySQL.
Calculate the "epoch" month of both dates, then subtract them and add 1.
Using LocalDate like in the other answer, an epochMonth() helper method makes it easy:
private static int monthsTouched(LocalDate fromDate, LocalDate toDate) {
return epochMonth(toDate) - epochMonth(fromDate) + 1;
}
private static int epochMonth(LocalDate date) {
return date.getYear() * 12 + date.getMonthValue();
}
Like the results in the question, both dates are inclusive.
Note: Validation skipped for brevity, e.g. what is result if fromDate > toDate?
Test
public static void main(String[] args) {
test("2016-01-23", "2016-01-23");
test("2016-01-31", "2016-02-01");
test("2016-01-23", "2016-02-28");
test("2016-01-15", "2016-03-15");
test("2016-01-15", "2017-03-15");
}
private static void test(String fromDate, String toDate) {
System.out.println(monthsTouched(LocalDate.parse(fromDate), LocalDate.parse(toDate)));
}
Output (matches results from question)
1
2
2
3
15
Use ChronoField.PROLEPTIC_MONTH, which returns a count of months from year zero:
import static java.time.temporal.ChronoField.PROLEPTIC_MONTH;
long monthsTouched = date2.getLong(PROLEPTIC_MONTH) - date1.getLong(PROLEPTIC_MONTH) + 1;
Adjust the start and end dates
The key is to adjust your dates.
Move the starting date to the first of the month
Move the ending date to the first of the following month
We move the ending to the next month after because the Half-Open approach is commonly used when considering spans of time. Half-Open means the beginning is inclusive while the ending is exclusive. So lunch hour runs from 12:00 to 13:00 but does not include the 61st minute of 1 PM. A week runs from Monday to Monday, for seven days not including that second Monday.
So a span running from the first of January to the first of March is two months rather than three because we run up to, but do not include, that last date, the first of March.
java.time
The java.time classes built into Java 8 and later make easier work of this.
For date-only values, without a time-of-day and without a time zone, use the LocalDate class.
LocalDate start = LocalDate.parse( "2016-01-31" );
LocalDate stop = LocalDate.parse( "2016-02-01" );
To adjust, use a TemporalAdjuster. Implementations can be found in the TemporalAdjusters class (note the plural 's'). We need firstDayOfMonth and firstDayOfNextMonth.
LocalDate startAdjusted = start.with( TemporalAdjusters.firstDayOfMonth() );
LocalDate stopAdjusted = stop.with( TemporalAdjusters.firstDayOfNextMonth() );
Now use the ChronoUnit class to calculate elapsed whole months.
long calendarMonthsTouched = ChronoUnit.MONTHS.between( startAdjusted , stopAdjusted );
span: 2016-01-31/2016-02-01
calendarMonthsTouched: 2
See this code live in IdeOne.com.
I am trying to obtaining remaining years, months, and days between two dates:
So I have used Joda Time to do so:
DateTime endDate = new DateTime(2018,12,25,0,0);
DateTime startDate = new DateTime();
Period period = new Period(startDate,endDate,PeriodType.yearMonthDay());
PeriodFormatter formatter = new PeriodFormatterBuilder().appendYears().appendSuffix(" Year ").
appendMonths().appendSuffix(" Month ").appendDays().appendSuffix(" Day ").appendHours()..toFormatter();
String time = formatter.print(period);
This gives me string time: 2 Year 4 Month 22 Day
However, I want integer values of each number of remaining years, months, days.
So, Instead of "2 Year 4 Month 22 Day", I want to set my variables:
int year = 2
int month = 4
int day = 22
Is there any way to obtain these values separately instead of obtaining one string? Thank you so much! :)
i had the same requirement once ,here is the code snippet
LocalDate d=LocalDate.of(yy,mm,dd);
LocalDate d2=LocalDate.of(yy, mm, dd);
Period p=Period.between(d, d2);
long day,month,year;
day=p.getDays();
month=p.getMonths();
year=p.getYears();
System.out.println(day+" : "+month+" : "+year);
Invoke the methods provided by the DateTime class and just subtract them. An example for years is below:
int year = (int) dateTime#year#getField() - (int) dateTime2#year#getField()
UNTESTED code!! I'll be looking into it later but the general idea is the same, get the field information then just subtract it to get a value