Fractional month difference between 2 dates (Scala or Java) - java

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;
}

Related

Get correct difference between two selected dates in years, months and days in Android

How can i get difference between two selected dates from calendar in Android in days, months and years ? We know all that the months sometimes have 30 days and sometimes 31 days and in February 28 days just every 4 years come 29 days. I want to get the result for example like this: 2 years and 6 months and 24 days.
I try to use LocalDate so this is my code:
public void onSelectedDayChange(#NonNull CalendarView view,
final int year, final int month, final int dayOfMonth) {
textView.setText(String.valueOf(dateDiff(year,month,dayOfMonth)));
}
public Period dateDiff(int year,int month,int day){
final int Day = c.get(Calendar.DAY_OF_MONTH);
final int Month = c.get(Calendar.MONTH);
final int Year = c.get(Calendar.YEAR);
LocalDate localDate1 = LocalDate.of(year,month,day);
LocalDate localDate2 = LocalDate.of(Year,Month,Day);
Period period = Period.between(localDate2,localDate1);
return period;
}
I tested the code but i got wrong result. When i test it with days (02/05/2020) i got 8 days but the difference is 7 days because "April" has 30 days and for the selected day (02/07/2020) i got 2 months and 8 days or the correct answer are 2 months and 7 days.
I try to get first a correct result, if there is a function or a calculation formula to solve this wrong result, this will help me.
Finally i got in result each time something like (P2M8D) that's meaning 2 months and 8 days, or for example (P7D) that's mean 7 days, so how can i change this text to an understood one like 2 months and 8 days or 7 days like the 2 examples because i found problem in result because we have mixed between numbers and characters.
You’re well on the right way.
Follow the Java naming conventions. A variable or parameter name begins with a lower case letter. Always. It’s particularly confusing that you’ve got parameter year and variable Year. This is bound to lead to errors at some point (though this doesn’t seem to be the reason at the moment).
Since you can use LocalDate and Period from java.time, the modern Java date and time API, don’t mix in the poorly designed and outdated Calendar class too, it just complicates things. Your use of LocalDate and Period is basically correct. For getting the current date as a LocalDate use LocalDate.now(ZoneId.systemDefault()) (and we no longer need the uppercase variables, two problems solved in one shot).
I don’t know your date picker, but I suspect that it may use 0-based month numbers: 0 for January through 11 for December. If this is so, you need to add 1 to the month number when creating the LocalDate object.
For a more readable text use the methods of the Period object to get numbers and assemble your own string. A simple example is:
String periodText = "" + period.getYears() + " years "
+ period.getMonths() + " months " + period.getDays() + " days";
You will want to modify it to leave out the years if they are 0 and the months if they are 0. Consider what text you want to write if all the numbers are 0. You will want to write something, or the user will be confused. A further possible refinement will be to use singular of the words if there is exactly 1 (1 year rather than 1 years, etc.). You may look into using a StringJoiner and into writing an auxiliary method that returns a string with a number and the correct form of a noun, for example 1 month but 2 months.
Edit: Code for formatting the Period
Spelling out my suggestion in code, here’s a way to format your Period:
StringJoiner joiner = new StringJoiner(" ");
joiner.setEmptyValue("No time at all");
if (period.getYears() != 0) {
joiner.add(singularOrPlural(period.getYears(), "year", "years"));
}
if (period.getMonths() != 0) {
joiner.add(singularOrPlural(period.getMonths(), "month", "months"));
}
if (period.getDays() != 0) {
joiner.add(singularOrPlural(period.getDays(), "day", "days"));
}
String periodText = joiner.toString();
System.out.println(periodText);
The code is using this auxiliary method:
private static String singularOrPlural(int count, String singular, String plural) {
if (count == -1 || count == 1) {
return "" + count + ' ' + singular;
} else {
return "" + count + ' ' + plural;
}
}
Example outputs:
No time at all
1 day
2 days
1 month
1 month 1 day
2 months
3 years 4 months 5 days
-1 year -1 month -6 days
I can't understand the problem you have with the "wrong result" of the returned period, clarify it better to me if you can.
By the way, you can try this to convert a format like P2M8D to 2 months and 8 days:
public static void main(String[] args) {
int year = 2020, month = 7, day = 2;
int yearDiff = dateDiff(year, month, day).getYears();
int monthDiff = dateDiff(year, month, day).getMonths();
int dayDiff = dateDiff(year, month, day).getDays();
System.out.println("Period -> yyyy:" + yearDiff + " mm:" + monthDiff + " dd:" + dayDiff); // OUTPUT: Period -> yyyy:0 mm:3 dd:7
}
static Period dateDiff(int year, int month, int day) {
Calendar c = Calendar.getInstance();
final int Day = c.get(Calendar.DAY_OF_MONTH);
final int Month = c.get(Calendar.MONTH);
final int Year = c.get(Calendar.YEAR);
LocalDate localDate1 = LocalDate.of(year, month, day);
LocalDate localDate2 = LocalDate.of(Year, Month, Day);
return Period.between(localDate2, localDate1);
}
You have to use getDays, getMonths and getYears methods.

Calculate the difference between two dates, "cannot find symbol"

I want to use the difference method.
It gets a certain date parameter, then calculates the difference between two dates (this and other)
The calculateDate is the way to get the days passed since the Christian counting. I wanted to use it inside the difference method, but I get the following error while trying to compile:
cannot find symbol - variable calculateDate
The difference has to be an absolute value, so I added the Math.abs.
public int difference (Date other) {
return Math.abs(this.calculateDate-other.calculateDate);
}
//computes the day number since the beginning of the Christian counting of years
private int calculateDate (int day, int month, int year)
{
if (month < 3)
{
year--;
month = month + 12;
}
return 365 * year + year/4 - year/100 + year/400 + ((month+1) * 306)/10 + (day - 62);
}
It would be easier to use java.time library instead of writing the day counting code by hand, unless you have a very specific requirement:
private int difference(LocalDate date) {
LocalDate start = LocalDate.of(0, 1, 1); // 1 Jan 0000
return ChronoUnit.DAYS.between(start, date);
}
You can map from java.util.Date to java.time.LocalDate with:
Date date = ...
LocalDate ld = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

Display the number of days in every month with Java Time

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)));

Getting full "monday to sunday" weeks between two dates [duplicate]

This question already has answers here:
Get the number of weeks between two Dates.
(19 answers)
Closed 8 years ago.
Let's see if you can help me. I want to get the number of weeks (starting on monday and finishing on sunday) between two dates, d1 and d2. Let's suppose that d2 is earlier of d1. That part of the code is already programmed and working. My problem now is that I'm not being able to code correctly the week program. This is what I made for now:
public static void getFullWeeks(Calendar d1, Calendar d2){
int Weeks = 0;
Calendar date2 = d2;
Calendar addWeek = GregorianCalendar.getInstance();
addWeek.setTime(date2.getTime());
addWeek.add(Calendar.DATE, 6);
while(addWeek.before(d1) || addWeek.equals(d1)){
if(date2.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY){
Weeks++;
}
date2.add(Calendar.DATE,1);
}
System.out.println(
"The number of weeks (from monday to sunday) between both dates are: "
+ Weeks);
}
But the output is "0 weeks", so the program is not working. What's wrong? I'm not encountering the error and I'm not being capable to find a working solution for this problem.
Thank you!!
In Java 8 without external libraries you can do something like the following:
*Edited to account for week starting on Monday
// TechTrip - ASSUMPTION d1 is earlier than d2
// leave that for exercise
public static long getFullWeeks(Calendar d1, Calendar d2){
// In Java the week starts on Sunday from an integral perspective
// public final static int SUNDAY = 1;
// SEQUENTIAL UP TO
// public final static int SATURDAY = 7;
// make the starting date relative to the Monday we need to calculate from
int dayOfStartWeek = d1.get(Calendar.DAY_OF_WEEK);
// IF we have a partial week we should add an offset that moves the start
// date UP TO the next Monday to simulate a week starting on Monday
// eliminating partial weeks from the calculation
// NOTE THIS METHOD WILL RETURN NEGATIVES IF D1 < D2 after adjusting for
// offset
if (dayOfStartWeek == Calendar.SUNDAY) {
// add an offset of 1 day because this is a partial week
d1.add(Calendar.DAY_OF_WEEK, 1);
} else if (dayOfStartWeek != Calendar.MONDAY){
// add an offset for the partial week
// Hence subtract from 9 accounting for shift by 1
// and start at 1
// ex if WEDNESDAY we need to add 9-4 (WED Int Val) = 5 days
d1.add(Calendar.DAY_OF_WEEK, 9 - dayOfStartWeek);
}
Instant d1i = Instant.ofEpochMilli(d1.getTimeInMillis());
Instant d2i = Instant.ofEpochMilli(d2.getTimeInMillis());
LocalDateTime startDate = LocalDateTime.ofInstant(d1i, ZoneId.systemDefault());
LocalDateTime endDate = LocalDateTime.ofInstant(d2i, ZoneId.systemDefault());
return ChronoUnit.WEEKS.between(startDate, endDate);
}
Here's the main:
public static void main(String[] args) {
Calendar d1 = Calendar.getInstance();
Calendar d2 = Calendar.getInstance();
d2.add(Calendar.WEEK_OF_YEAR, 6);
System.out.println(
"The number of weeks (from monday to sunday) between both dates are: "
+ getFullWeeks(d1, d2));
}
The output is as follows if the start date is a MONDAY:
The number of weeks (from monday to sunday) between both dates are: 6
Note, I did not assign date d2 to d1, making it the same reference. In that case you would get 0.
*The ChronoUnit takes a Temporal which is simply a date, time or offset. They must be of the same type. Temporals can be manipulated with plus and minus.
It seems to me there are couple of little mistakes you made:
1.) Your output is 0 weeks because elsewhere in your code you must have called this method with your first argument, d1, earlier than the second, while in the method body, you assume that d2 is earlier than d1. Such mistakes are easily avoided by using meaningful argument names. Appropriate argument names in this case are for example start and end.
2) If you execute this method using arguments in which d2 is earlier than d1, your method would fall into an infinite loop. It looks to me that adding to date2 does not change the date in addWeek.
3) Your method counts the number of weeks from Tuesday to Monday instead of Monday to Sunday. To fix this, add seven days to addWeek instead of six, or change the while loop to check only if addWeek is before d1 and increment weeks on sunday.
Putting all this together, I believe this will give you what you're looking for:
public static void getFullWeeks(Calendar start, Calendar end)
{
System.out.println("The number of weeks (from monday to sunday) between both dates are: " + getNrWeeksBetween(start, end));
}
private static int getNrWeeksBetween(Calendar start, Calendar end)
{
int weeks = 0;
Calendar counter = new GregorianCalendar();
counter.setTime( start.getTime() );
counter.add(Calendar.DATE, 6);
while( counter.before(end) )
{
if(counter.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) weeks++;
counter.add(Calendar.DATE, 1);
}
return weeks;
}
Your algorithm's wrong; your while loop needs to compare the same Calendar object you're modifying, but you don't do that, so you either have an infinite loop or you exit immediately with a return value of 0, depending on whether addDate is before or after d1 (since neither one changes). Your code should be:
public static void getFullWeeks(Calendar d1, Calendar d2){
int Weeks = 0;
Calendar addWeek = GregorianCalendar.getInstance();
addWeek.setTime(d2.getTime());
addWeek.add(Calendar.DATE, 6);
while(addWeek.before(d1) || addWeek.equals(d1)){
if(addWeek.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY){
Weeks++;
}
addWeek.add(Calendar.DATE,1);
}
System.out.println(
"The number of weeks (from monday to sunday) between both dates are: "
+ Weeks);
}

Rolling forward/backward a number of months on a calendar object (JAVA)

I have to create my own calendar class (I know one exists, but we're being tested on our class creating skills, so I can't use any existing methods from the existing calendar class).
One of the methods we have to provide is the ability to roll the Calendar date forward or backwards by a given number of months.
Firstly, my constructor for a Calendar object takes a day, month, and year value. The values are then validated (e.g. makes sure given days is within the number of days in the given month, makes sure months is between 1 and 12).
Now, for the roll back method, I've written this:
public void rollMonths(int m) {
if(month + m > 12){ // Checks if the given months plus the current month will need to increase the year
year = year + 1; // Increases the year by one if it goes over.
month = m - (12 - month); } // Finds the number of months till the end of the year, and subtracts this from the given months. Then sets the month to the new value.
else if(month + m < 0) { // Checks if the year will need to be decreased.
year = year - 1; // Decreases the year by 1;
month = 12 - (Math.abs(m) - month); } // Finds the months between the start of the year and the current month, and subtracts this from the given month value. Then sets the month to this value.
else
month = month + m; // If current month plus given months is within the same year, adds given months to current month.
}
This method works, but only if the calendar doesn't increase by more than one year. For example, if I have a calendar for august 2011, and I roll forward 6 months, I correctly get February 2012. However, if I try to roll forward 20 months, the month becomes 16..
Similarly for when I try to roll back a given number of months. I get a correct answer when rolling back within one year.
Sorry if that didn't seem to make sense, I'm not too sure how to explain it. If it doesn't let me know and I'll try to write a better explanation.
Clearly a homework, so i'll just point out the flaw...
Here's you're problem
if(month + m > 12){ // Checks if the given months plus the current month will need to increase the year
year = year + 1; // Increases the year by one if it goes over.
month = m - (12 - month); }
You're incrementing year by just 1 year, no matter how many months we are adding...it could be 20,40 or whatever...
Years will increase by m/12 and months will increase by whatever is left after subtracting the years.
Hint: You'll need to use the Mod operator.
First, you can use implementations of Calendar such as GregorianCalendar.
You need a loop to keep the month number valid. Something like this:
month += m;
while(month < 1){
month += 12;
year -= 1;
}
while(month > 12){
month -= 12;
year += 1;
}
Alternatively, you can do it in one go:
month += m;
if(month < 1)
year -=1;
if(month > 12 || month < 1){
month = month % 12 + 1;
year += month / 12;
}

Categories