How to handle LocalDate Period.between results? - java

I use LocalDate (ThreeTenABP) to calculate the period between to given dates. When I use 01/01/2018 as the first and 12/31/2018 as the second date, Period.between gives the following result:
00 years
11 months
30 days
IMHO this is wrong, since a full year has 12 months.
I tried to add one day. Then I get:
00 years
11 months
31 days
I would need a reliable method to get the real amount of full months within a given period.

If you look at the Javadoc for Period.between(), it tells you that the end date is exclusive (meaning: not counted).
The start date is included, but the end date is not. The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign.
I wrote this code, and it appears to function as I would expect...
LocalDate d1 = LocalDate.of(2018, 1, 1);
LocalDate d2 = LocalDate.of(2018, 12, 31);
Period p1 = Period.between(d1, d2);
System.out.println(p1.getYears() + " years, " + p1.getMonths() + " months, " + p1.getDays() + " days");
// Prints: 0 years, 11 months, 30 days
Adding one day, making it 2019-01-01, gives me one year:
LocalDate d3 = LocalDate.of(2019, 1, 1);
Period p2 = Period.between(d1, d3);
System.out.println(p2.getYears() + " years, " + p2.getMonths() + " months, " + p2.getDays() + " days");
// Prints: 1 years, 0 months, 0 days
Edit:
If you just add one day to the calculated Period object, you are really just adding a day, not recalculating the period as the between method would. Here's the code from Period which does plusDays():
public Period plusDays(long daysToAdd) {
if (daysToAdd == 0) {
return this;
}
return create(years, months, Math.toIntExact(Math.addExact(days, daysToAdd)));
}
If you follow the create call, it really just adds one day to the days counter, it doesn't recalculate anything. To properly add a day to a period, recalculate it with different endpoints as I have above.

The reliable method is:
LocalDate begin = LocalDate.of(2018, Month.JANUARY, 1);
LocalDate end = LocalDate.of(2018, Month.DECEMBER, 31);
Period inclusive = Period.between(begin, end.plusDays(1));
System.out.println(inclusive);
This prints
P1Y
Voilà, a period of one year. Adding 1 day to the end date makes the end date inclusive (since now it’s the day after the end date that is exclusive).

Related

Add decimal month to Date in java

i need to add a decimal amount of month to a java date :
-> i can use this code with joda time api to add a natural amount of months to a date. But how can i add a decimal amount of month ( for example 3.5) to a date ?
Date date = new Date();
DateTime dateTime = new DateTime(date);
dateTime = dateTime.plusMonths(3);
Date newDate = dateTime.toDate();
This will give you an approximation. It’s the best you can get.
long oneMonthInNanos = ChronoUnit.MONTHS.getDuration().toNanos();
ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Phnom_Penh"));
System.out.println("Now " + now);
System.out.println("In 3.5 months " + now.plusNanos(Math.round(3.5 * oneMonthInNanos)));
System.out.println("In 4.5 months " + now.plusNanos(Math.round(4.5 * oneMonthInNanos)));
System.out.println("In 12 months " + now.plusNanos(Math.round(12.0 * oneMonthInNanos)));
Output when I ran the code just now, was:
Now 2018-11-06T22:31:36.460573+07:00[Asia/Phnom_Penh]
In 3.5 months 2019-02-21T11:13:27.460573+07:00[Asia/Phnom_Penh]
In 4.5 months 2019-03-23T21:42:33.460573+07:00[Asia/Phnom_Penh]
In 12 months 2019-11-07T04:20:48.460573+07:00[Asia/Phnom_Penh]
As has been said in comments, there is no really good definition of a fractional number of months. When you compare the first and the last date-time you also clearly see that 12 months don’t add up to a year precisely, though pretty close. Please check yourself whether the results are good enough for your purpose.
I am using java.time. I din’t know whether something similar is possible in Joda-Time.

Remaining years, months, days using joda time (Android)

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

Calculating years between from a leap year

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!).

Get next week and previous week staring and ending dates in java

I want to get the starting and ending dates of a week
for example
2012-05-06 to 2012-05-12
2012-05-13 to 2012-05-19
The code I have written is
currWeekCalender.add(Calendar.WEEK_OF_YEAR, 1);
String dateStart = currWeekCalender.get(Calendar.YEAR) + "-" + addZero((currWeekCalender.get(Calendar.MONTH) + 1)) + "-" + addZero(currWeekCalender.getFirstDayOfWeek());
currWeekCalender.add(Calendar.DAY_OF_MONTH,7);
String dateEnd = currWeekCalender.get(Calendar.YEAR) + "-" + addZero((currWeekCalender.get(Calendar.MONTH) + 1)) + "-" + addZero(currWeekCalender.get(Calendar.DAY_OF_MONTH));
but the results are not correct, also I want previous weeks date.
Thanks
Hello to all coders :)
I work on little app to dive some data from database. To calculate previous weeks start and end date i use this code:
// Calendar object
Calendar cal = Calendar.getInstance();
// "move" cal to monday this week (i understand it this way)
cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
// calculate monday week ago (moves cal 7 days back)
cal.add(Calendar.DATE, -7);
Date firstDateOfPreviousWeek = cal.getTime();
// calculate sunday last week (moves cal 6 days fwd)
cal.add(Calendar.DATE, 6);
Date lastDateOfPreviousWeek = cal.getTime();
Hope, that helps.
Your problem is that getFirstDayOfWeek() returns the first day of the week; e.g., Sunday in US, Monday in France. It does not return a day of the month. See javadoc.
The first day in a month that is the start of the week is (in pseudo-code)
((7 + (firstDayOfWeek - dayOfWeek(firstOfMonth))) % 7) + 1
You can translate that into java.util.Calendar code if you like, but I would suggest using Joda time instead.
also I want previous weeks date.
Just subtract seven days maybe using add
currCalendar.add(Calendar.DAY_OF_MONTH, -7)
This may involve underflow, but add deals with that.
add(f, delta)
adds delta to field f. This is equivalent to calling set(f, get(f) + delta) with two adjustments:
Add rule 1. The value of field f after the call minus the value of field f before the call is delta, modulo any overflow that has occurred in field f. Overflow occurs when a field value exceeds its range and, as a result, the next larger field is incremented or decremented and the field value is adjusted back into its range.
Java 8 version
This prints previous 10 weeks
final ZonedDateTime input = ZonedDateTime.now();
for(int i = 1; i < 10; i++) {
final ZonedDateTime startOfLastWeek = input.minusWeeks(i).with(DayOfWeek.MONDAY);
System.out.print(startOfLastWeek.format(DateTimeFormatter.ISO_LOCAL_DATE));
final ZonedDateTime endOfLastWeek = startOfLastWeek.plusDays(6);
System.out.println(" - " + endOfLastWeek.format(DateTimeFormatter.ISO_LOCAL_DATE));
}

Approximate Time Period with Joda-Time

I am using Joda library to get time period passed since a given timestamp:
public static String getTimePassedSince(Date initialTimestamp){
DateTime initDT = new DateTime(initialTimestamp.getTime());
DateTime now = new DateTime();
Period p = new Period(initDT, now);
PeriodFormatter formatter = new PeriodFormatterBuilder()
.appendYears().appendSuffix(" year, ", " years, ")
.appendMonths().appendSuffix(" month, ", " months, ")
.appendDays().appendSuffix(" day, ", " days, ")
.appendHours().appendSuffix(" hour, ", " hours, ")
.appendMinutes().appendSuffix(" minute, ", " minutes, ")
.appendSeconds().appendSuffix(" second, ", " seconds")
.printZeroNever()
.toFormatter();
return formatter.print(p);
}
The function returns exact time period strings for given timestamps. For example:
3 minutes, 23 seconds
1 hour, 30 minutes, 57 seconds
1 day, 23 hours, 21 minutes, 19 seconds
Is there any way that I can get approximate time instead of exact? For example, if one minute and 30 seconds have passed since the initialTimestamp, it only returns 1.5 minutes. Similarly, if an hour and 35 minutes have passed, it returns about 1.5 hours instead of 1 hour, 35 minutes, xy seconds.
I know the string returned can be parsed and manipulated but I am looking for something more sophisticated.
Take a look on PrettyTime.
I think you'll need to create your own formatter for this, which looks at the period and determines what granularity you want to format it in, say for 63 seconds "1 minute", or for 3 hours 48 minutes: "3 hours". Sounds to me like you want to report only the one largest unit of time, and ignore the more granular ones. You'll need to define the rounding behavior and how to render times in days: "44 days ago" or "one month ago" or "1 month and 2 weeks ago".
I am not aware of any generic utility which does this, but I haven't looked for one either.

Categories