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
Related
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.
NOTE: search Google before marking this question as duplicate. I did search and browse this question and all answers that I found were either for LocalDate, Joda or legacy Java Date.
It took me quite some time to investigate this so I've decided to share this as an answer.
I'd like a way to calculate the (approximate) number of months and days between two Java Instants (objects of java.time.Instant)?
First, what you are asking is not well-defined. For example between the instants 2020-03-01T06:00:00Z and 2020-03-31T05:00:00Z could be:
29 days 23 hours in Australia/Melbourne time zone;
30 days in Europe/Paris time zone;
1 month 1 day in America/Los_Angeles time zone.
Accurate result in a given time zone
ZoneId zone = ZoneId.of("America/Los_Angeles");
Instant start = Instant.parse("2020-03-01T06:00:00Z");
Instant end = Instant.parse("2020-03-31T05:00:00Z");
ZonedDateTime startZdt = start.atZone(zone);
LocalDate startDate = startZdt.toLocalDate();
ZonedDateTime endZdt = end.atZone(zone);
LocalDate endDate = endZdt.toLocalDate();
Period p = Period.between(startDate, endDate);
if (startZdt.plus(p).isAfter(endZdt)) {
// The time of day on the end date is earlier, so don’t count a full date
endDate = endDate.minusDays(1);
p = Period.between(startDate, endDate);
}
System.out.println(p);
Output:
P1M1D
Read as a period of 1 month 1 day.
Approximate result independent of time zone
Prefer to leave as much of the calculation to java.time as possible. This includes the estimate of the length of a month.
Duration diff = Duration.between(start, end);
Duration durationOfAMonth = ChronoUnit.MONTHS.getDuration();
long months = diff.dividedBy(durationOfAMonth);
diff = diff.minus(durationOfAMonth.multipliedBy(months));
long days = diff.toDays();
System.out.println("" + months + " months " + days + " days");
0 months 29 days
I've opted out to approximate solution (it assumes all months have 30.44 days). I've opted out to use something like this:
Duration duration = Duration.between(instant1, instant2).abs(); /* if want negative values remove .abs() */
long hours = duration.toHours();
double daysAndMonthsInDays = hours / 24.0;
int months = daysAndMonthsInDays / 30.44; //average number of days per month
int days = daysAndMonthsInDays - months * 30.44;
Please post another answer if there is a better solution using Duration class or something else. I've decided not to convert Instant to LocalDate and to perform the conversion on that level. That would not use an approximation of 30.44 days in a month, but rather the actual number.
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 want to create a calendar with Java 8. So far I have this:
YearMonth yearMonthObject = YearMonth.of(year, month);
int daysOfCurrentMonth = yearMonthObject.lengthOfMonth();
int i = 1;
ArrayList<Integer> Dayes = new ArrayList<Integer>();
for(i=1; i<=daysOfCurrentMonth; i++){
Dayes.add(i);
}
Dayes.forEach(value -> System.out.print(value));
which prints the days of the current month (for example May).
How can I determine that 1 is Sunday, 2 is Monday, 3 is Tuesday, ..., 8 is Sunday (next week), etc.?
You have a YearMonth object. For each day of the month, you can call atDay(dayOfMonth) to return a LocalDate at that specific day of month. With that LocalDate, you can then call:
getDayOfMonth() to get back the day of the month as an int;
getDayOfWeek() to get the day of the week as a DayOfWeek. This is an enumeration of all the days of the week.
As such, you should change your Dayes list to hold LocalDates instead of Integers, and then you can have, for example:
YearMonth yearMonthObject = YearMonth.of(year, month);
int daysOfCurrentMonth = yearMonthObject.lengthOfMonth();
ArrayList<LocalDate> dayes = new ArrayList<LocalDate>();
for(int i = 1; i <= daysOfCurrentMonth; i++){
dayes.add(yearMonthObject.atDay(i));
}
dayes.forEach(value -> System.out.println(value.getDayOfMonth() + " " + value.getDayOfWeek()));
This will print each day of that month followed by the corresponding day of the week.
As a side-note, you can get a real display value for the day of week (instead of the name() of the enum like above) by calling getDisplayName(style, locale). The style represents how to write the days (long form, short form...) and the locale is the locale to use for the display name. An example would be:
value.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH)
which would output the full text of the day of the week in English. Sample output for 04/2016 with the above change:
1 Friday
2 Saturday
3 Sunday
4 Monday
5 Tuesday
This may be a bit of a 'hack' solution, but if you are trying to make a calendar for any year, you may have to use an 'anchor date' (Such as January 1, 1800 as a Wednesday). You then could calculate the number of days that happened between January 1st, 1800 and your current year/month/day. Once you figured out how many days have passed, using Modular 7 you could determine what day it is, and then populate the calendar for the month from there.
When calculating years between two dates, where the second date is calculated from the first one (this is a simplified example of what I'm working on), LocalDate and Period seem to calculate a year slightly differently.
For example,
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
while
LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plusYears(1);
System.out.println(Period.between(date, plusYear).getYears());
Despite having explicitly added a year, first Period return the years as 0, while the second case returns 1.
Is there a neat way around this?
This question has a philosophical nature and spans few problems like time measurements, and date format conventions.
LocalDate is an implementation of ISO 8601 date exchange standard.
Java Doc states explicitly that this class does not represent time but provides only standard date notation.
The API provides only simple operations on the notation itself and all calculations are done by incrementing the Year, or Month, or Day of a given date.
In other words, when calling LocalDate.plusYears() you are adding conceptual years of 365 days each, rather than the exact amount of time within a year.
This makes Day the lowest unit of time which one can add to a date expressed by LocalDate.
In human understanding, date is not a moment in time, but it is a period.
It starts with 00h 00m 00s (...) and finishes with 23h 59m 59s (...).
LocalDate however avoids problems of time measurement and vagueness of human time units (hour, day, month, and a year can all have different length) and models date notation simply as a tuple of:
(years, months within a year, days within a month )
calculated since the beginning of the era.
In this interpretation, it makes sense that Day is the smallest unit affecting the date.
As an example following:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusSecond = date.plus(1, ChronoUnit.SECONDS);
returns
java.time.temporal.UnsupportedTemporalTypeException: Unsupported unit: Seconds
... which shows, that using LocalDate and adding the number of seconds (or smaller units to drive the precision), you could not overcome the limitation listed in your question.
Looking at the implementation you find that LocalDate.plusYears() after adding the years, calls resolvePreviousValid(). This method then checks for leap year and modifies the day field in the following manner:
day = Math.min(day, IsoChronology.INSTANCE.isLeapYear((long)year)?29:28);
In other words it corrects it by effectively deducting 1 day.
You could use Year.length() which returns the number of days for given year and will return 366 for leap years. So you could do:
LocalDate plusYear = date.plus(Year.of(date.getYear()).length(), ChronoUnit.DAYS);
You will still run into following oddities (call to Year.length() replaced with the day counts for brevity):
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-02-28
0y 11m 30d
then
LocalDate date = LocalDate.of(1996, 3, 29);
LocalDate plusYear = date.plus(365, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns
1997-03-29
1y 0m 0d
and finally:
LocalDate date = LocalDate.of(1996, 2, 29);
LocalDate plusYear = date.plus(366, ChronoUnit.DAYS);
System.out.println(plusYear);
Period between = Period.between(date, plusYear);
System.out.println( between.getYears() + "y " +
between.getMonths() + "m " +
between.getDays() + "d");
returns:
1997-03-01
1y 0m 1d
Please note that moving the date by 366 instead of 365 days increased the period from 11 months and 30 days to 1 year and 1 day (2 days increase!).