A Bug in java time calculating months between 2 dates - java

When using java.time in Scala I experienced a strange behavior. I want to calculate the number of months between two dates like this:
import java.time._
Period.between(LocalDate.parse("2015-03-31"), LocalDate.parse("2015-04-30"))
// java.time.Period = P30D
// I would expect java.time.Period = P1M
Period.between(LocalDate.parse("2015-03-31"), LocalDate.parse("2015-05-01"))
// java.time.Period = P1M1D
Is this a bug or do I have got it all wrong?
org.joda.time works as I would expect it:
import org.joda.time.DateTime
import org.joda.time.Months
Months.monthsBetween( new DateTime().withDate(2015, 3, 31), new DateTime().withDate(2015, 4, 30))
//org.joda.time.Months = P1M
When adding months to a java.time.LocalDate it works fine:
java.time.LocalDate.parse("2015-03-31").plusMonths(1)
// java.time.LocalDate = 2015-04-30

This is not a bug, and it is behaving like expected (see also JDK-8152384 and JDK-8037392, which were closed as "Not An Issue"). Joda Time and the Java Time API have different behaviour regarding this. Quoting Stephen Colebourne from the previous bug report:
The OP appears to want a rule where the days are calculated based on the original month length, not the one that results once the month-year difference is applied. The OP is not wrong, its just that its not how we choose to make the calculation in java.time.
Indeed, from Period.between:
The period is calculated by removing complete months, then calculating the remaining number of days, adjusting to ensure that both have the same sign. [...] A month is considered to be complete if the end day-of-month is greater than or equal to the start day-of-month.
Between the 31st of March, and the 30th of April, no complete month has elapsed. As such, you have a period containing the number of days between the two dates, which is 30. To have the complete month of April elapsed, you need to add one day to the end date, and make it the 1st of June.
Joda has a different way of calculating the month period. From Months.monthsBetween:
This method calculates by adding months to the start date until the result is past the end date. As such, a period from the end of a "long" month to the end of a "short" month is counted as a whole month.
Joda explicitly takes the variable number of days in a month into account when calculating the number of months between the two dates. Java Time doesn't.

I agree that it is a bit unexpected, but it is the correct result if you take into account the javadoc.
From the javadoc
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. The number of months is then split into years and months based on a 12 month year. A month is considered if the end day-of-month is greater than or equal to the start day-of-month. For example, from 2010-01-15 to 2011-03-18 is one year, two months and three days.
The difference comes from what a "complete month" means.
In this case 1st April to 1st May (exclusive) is considered a complete month while 31st March to 30th April (exclusive) is not.

I believe the Period.between is returning P30D in the first example because the second parameter is exclusive. This is according to https://docs.oracle.com/javase/8/docs/api/java/time/Period.html#between-java.time.LocalDate-java.time.LocalDate-
public static Period between(LocalDate startDateInclusive, LocalDate endDateExclusive)

Related

Is there anything in the java.time package that does the same thing as the roll method in java.util.Calendar? [duplicate]

I was studying the old Calendar API to see how bad it was, and I found out that Calendar has a roll method. Unlike the add method, roll does not change the values of bigger calendar fields.
For example, the calendar instance c represents the date 2019-08-31. Calling c.roll(Calendar.MONTH, 13) adds 13 to the month field, but does not change the year, so the result is 2019-09-30. Note that the day of month changes, because it is a smaller field.
Related
I tried to find such a method in the modern java.time API. I thought such a method has to be in LocalDate or LocalDateTime, but I found nothing of the sort.
So I tried to write my own roll method:
public static LocalDateTime roll(LocalDateTime ldt, TemporalField unit, long amount) {
LocalDateTime newLdt = ldt.plus(amount, unit.getBaseUnit());
return ldt.with(unit, newLdt.get(unit));
}
However, this only works for some cases, but not others. For example, it does not work for the case described in the documentation here:
Consider a GregorianCalendar originally set to Sunday June 6, 1999.
Calling roll(Calendar.WEEK_OF_MONTH, -1) sets the calendar to Tuesday
June 1, 1999, whereas calling add(Calendar.WEEK_OF_MONTH, -1) sets the
calendar to Sunday May 30, 1999. This is because the roll rule imposes
an additional constraint: The MONTH must not change when the
WEEK_OF_MONTH is rolled. Taken together with add rule 1, the resultant
date must be between Tuesday June 1 and Saturday June 5. According to
add rule 2, the DAY_OF_WEEK, an invariant when changing the
WEEK_OF_MONTH, is set to Tuesday, the closest possible value to Sunday
(where Sunday is the first day of the week).
My code:
System.out.println(roll(
LocalDate.of(1999, 6, 6).atStartOfDay(),
ChronoField.ALIGNED_WEEK_OF_MONTH, -1
));
outputs 1999-07-04T00:00, whereas using Calendar:
Calendar c = new GregorianCalendar(1999, 5, 6);
c.roll(Calendar.WEEK_OF_MONTH, -1);
System.out.println(c.getTime().toInstant());
outputs 1999-05-31T23:00:00Z, which is 1999-06-01 in my timezone.
What is an equivalent of roll in the java.time API? If there isn't one, how can I write a method to mimic it?
First, I cannot remember having seen any useful application of Calendar.roll. Second, I don’t think that the functionality is very well specified in corner cases. And the corner cases would be the interesting ones. Rolling month by 13 months would not be hard without the rollmethod. It may be that similar observations are the reasons why this functionality is not offered by java.time.
Instead I believe that we would have to resort to more manual ways of rolling. For your first example:
LocalDate date = LocalDate.of(2019, Month.JULY, 22);
int newMonthValue = 1 + (date.getMonthValue() - 1 + 13) % 12;
date = date.with(ChronoField.MONTH_OF_YEAR, newMonthValue);
System.out.println(date);
Output:
2019-08-22
I am using the fact that in the ISO chronology there are always 12 months in the year. Since % always gives a 0-based result, I subtract 1 from the 1-based month value before the modulo operation and add it back in afterwards And I am assuming a positive roll. If the number of months to roll may be negative, it gets slightly more complicated (left to the reader).
For other fields I think that a similar approach will work for most cases: Find the smallest and the largest possible value of the field given the larger fields and do some modulo operation.
It may become a challenge in some cases. For example, when summer time (DST) ends and the clock is turned backward from 3 to 2 AM, so the day is 25 hours long, how would you roll 37 hours from 6 AM? I’m sure it can be done. And I am also sure that the functionality is not built in.
For your example with rolling the week of month, another difference between the old and the modern API comes into play: a GregorianCalendar not only defines a calendar day and time, it also defines a week scheme consisting of a first day of the week and a minimum number of days in the first week. In java.time the week scheme is defined by a WeekFields object instead. So while rolling the week of month may be unambiguous in GregorianCalendar, without knowing the week scheme it isn’t with LocalDate or LocalDateTime. An attempt may be to assume ISO weeks (start on Monday, and the first week is the on that has at least 4 days of the new month in it), but it may not always be what a user had intended.
Week of month and week of year are special since weeks cross month and year boundaries. Here’s my attempt to implement a roll of week of month:
private static LocalDate rollWeekOfMonth(LocalDate date, int amount, WeekFields wf) {
LocalDate firstOfMonth = date.withDayOfMonth(1);
int firstWeekOfMonth = firstOfMonth.get(wf.weekOfMonth());
LocalDate lastOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());
int lastWeekOfMonth = lastOfMonth.get(wf.weekOfMonth());
int weekCount = lastWeekOfMonth - firstWeekOfMonth + 1;
int newWeekOfMonth = firstWeekOfMonth
+ (date.get(wf.weekOfMonth()) - firstWeekOfMonth
+ amount % weekCount + weekCount)
% weekCount;
LocalDate result = date.with(wf.weekOfMonth(), newWeekOfMonth);
if (result.isBefore(firstOfMonth)) {
result = firstOfMonth;
} else if (result.isAfter(lastOfMonth)) {
result = lastOfMonth;
}
return result;
}
Try it out:
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.SUNDAY_START));
System.out.println(rollWeekOfMonth(LocalDate.of(1999, Month.JUNE, 6), -1, WeekFields.ISO));
Output:
1999-06-01
1999-06-30
Explanation: The documentation you quote assumes that Sunday is the first day of the week (it ends “where Sunday is the first day of the week”; it was probably written in the USA) so there is a week before Sunday June 6. And rolling by -1 week should roll into this week before. My first line of code does that.
In the ISO week scheme, Sunday June 6 belong to the week from Monday May 31 through Sunday June 6, so in June there is no week before this week. Therefore my second line of code rolls into the last week of June, June 28 through July 4. Since we cannot go outside June, June 30 is chosen.
I have not tested whether it behaves the same as GregorianCalendar. For comparison,the GregorianCalendar.roll implementation uses 52 code lines to handle the WEEK_OF_MONTH case, compared to my 20 lines. Either I have left something out of consideration, or java.time once again shows it superiority.
Rather my suggestion for the real world is: make your requirements clear and implement them directly on top of java.time, ignoring how the old API behaved. As an academic exercise, your question is a fun and interesting one.
TL;DR
There is no equivalent.
Think about whether you really need the behavior of roll of java.util.Calendar:
/**
* Adds or subtracts (up/down) a single unit of time on the given time
* field without changing larger fields. For example, to roll the current
* date up by one day, you can achieve it by calling:
* roll(Calendar.DATE, true).
* When rolling on the year or Calendar.YEAR field, it will roll the year
* value in the range between 1 and the value returned by calling
* getMaximum(Calendar.YEAR).
* When rolling on the month or Calendar.MONTH field, other fields like
* date might conflict and, need to be changed. For instance,
* rolling the month on the date 01/31/96 will result in 02/29/96.
* When rolling on the hour-in-day or Calendar.HOUR_OF_DAY field, it will
* roll the hour value in the range between 0 and 23, which is zero-based.
*
* #param field the time field.
* #param up indicates if the value of the specified time field is to be
* rolled up or rolled down. Use true if rolling up, false otherwise.
* #see Calendar#add(int,int)
* #see Calendar#set(int,int)
*/
public void roll(int field, boolean up);

get number of days in month with time4j

Is there a way to get number of days in a month using time4j lib?
in android default calendar, we can get it so simple like below
Calendar calendar=Calendar.getInstance();
int numOfDaysInMonth=calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
I mean a standard way, not crazy ways like going to the first Day of next month then come back one day and get day of month.
so can we do that in time4j calendars like "PersianCalendar"
The answer of #محمد علی using the default maximum has a problem: It does not use any calendar context so the maximum in leap years cannot be determined for the last month ESFAND. But the old comment given by #Tunaki is already a good and simple answer:
PersianCalendar today = PersianCalendar.nowInSystemTime();
int lengthOfCurrentMonth = today.lengthOfMonth();
Alternatively, you can also use the element PersianCalendar.DAY_OF_MONTH but then you should determine the contextual maximum, not the default maximum:
PersianCalendar today = PersianCalendar.nowInSystemTime();
int lengthOfCurrentMonth = today.getMaximum(PersianCalendar.DAY_OF_MONTH);
Both expressions will yield the same results in all ways and are completely equivalent.
For standard months (FARVARDIN (1) until BAHMAN (11)) the results will agree with the default maximum. But the last month ESFAND has either 29 days in normal years or 30 days in leap years. Both methods presented here will take this into account (but not the default maximum method).

Adding and subtracting Period from LocalDate doesn't produce the same date

i use java 8 LocalDate and Period classes to add and remove years, months and days. Why in some cases if add Period to date and remove the same period java 8 return another date?
LocalDate date = LocalDate.of(2023, 1, 30);
Period period = Period.of(6, 1, 1);
System.out.println(date.plus(period).minus(period));
why the result is 2023-01-31 not 2023-01-30
Why in some cases if add Period to date and remove the sane period java 8 return another date?
Because that's how calendrical arithmetic works - months are uneven lengths, and it makes things tricky to say the least.
You're adding "six years, one month, one day" to January 30th 2023. What do you expect the result of that to be? There are potentially multiple different options... logically it sounds like you mean "February 31st 2029" which doesn't exist... so the API rolls it over to March 1st 2029.
Now subtracting six years, one month and one day from March 1st 2029 is also somewhat ambiguous, but it sounds reasonable to make it January 31st 2023 - if you subtract 6 years to get to March 1st 2023, then 1 month to get to February 1st 2023, then 1 day you get to January 31st.
Fundamentally: don't expect calendrical arithmetic to behave like regular maths. It just doesn't work that way.

How to calculate sequential (ordinal) number of a specific weekday in a month in Java

I am trying to determine the sequential ordinal number of a weekday in a month in Java. i.e. if a Friday is the first or 3rd friday of a month.
I can not find a simple way after reading all the things I can find on Java Calendar and posts here. One way I can think of is to determine how many days the first week of this month have in this month and then adjust week_of_month based on what day the day in question is. However, it requires a little complicated calculation. Anyone knows a simple solution?
Just take the day of month, subtract 1, divide by 7, then add 1. The first seven days of the month are always the first (Tuesday, Wednesday, ...) whatever day of the week the actual 1st of the month is.
Personally I'd use Joda Time:
public int getWeekOfWeekDay(LocalDate date) {
return ((date.getDayOfMonth() - 1) / 7) + 1;
}
... but you could do the same using Calendar and fetching the value of the Calendar.DAY_OF_MONTH field.
EDIT: Actually, I've just noticed that for a change, java.util.Calendar is actually simpler than Joda Time - there's a particular field for it! All you need is:
int weekOfWeekDay = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH);
From the docs for DAY_OF_WEEK_IN_MONTH:
Field number for get and set indicating the ordinal number of the day of the week within the current month. Together with the DAY_OF_WEEK field, this uniquely specifies a day within a month. Unlike WEEK_OF_MONTH and WEEK_OF_YEAR, this field's value does not depend on getFirstDayOfWeek() or getMinimalDaysInFirstWeek(). DAY_OF_MONTH 1 through 7 always correspond to DAY_OF_WEEK_IN_MONTH 1; 8 through 14 correspond to DAY_OF_WEEK_IN_MONTH 2, and so on.
I think I'd probably still use the Joda Time version because it's just a much nicer API all round, but if you're forced to use Calendar, at least you can do this in one shot.

Subtract two days and calculate working days only (no Saturdays or Sundays)

I am looking for an external JAR or a method that will supply me the following abilities:
Subtract two dates.
Define holidays as Saturday and Sunday, or Friday and Saturday.
Calculate the difference with holidays or without them.
Can anyone recommend an external JAR before I go into Gregorian calendar calculations?
Take a look at this site, they provide you a java program that helps you with that:
The wdnum() method returns the number of weekdays (excluding weekends) that have passed since Monday, 29 December 1969. It works by calculating the number of days since 1 January, 1970 (getTime() divided by the number of milliseconds in a day), adding 3 and returning the number of week days in full weeks and possibly a partial week that have passed since then.
Have a look at Joda Time it may be helpful with what you're trying to do.
However, more than likely you're going to have to use the methods in the Calendar class such as getTimeInMillis() to subtract the dates and fields in the Calendar class such as Calendar.DAY_OF_WEEK or Calendar.DAY_OF_MONTH to determine the day of the week if you want to exclude certain days of the week from your calculations.

Categories

Resources