5

I am having quarter end date of last quarter let it be 30-09-20 , the requirement is to find end date of next quarter i.e 31-12-20. I am using below code to do the same but is it giving wrong output in some scenarios. This solution should be correct for all quarters.

String str = "30-09-20";
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yy");
Date date = format.parse(str);
Date newDate = DateUtils.addMonths(date, 3);
System.out.println(newDate);//Dec 30 - It should be 31 Dec
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
Loren
  • 320
  • 1
  • 10
  • 25
  • How are your quarters defined? Are they fix dates? – deHaar Jan 28 '20 at 10:44
  • January, February, and March (Q1)- Will pick last date of March April, May, and June (Q2)-Will pick last date of June July, August, and September (Q3) October, November, and December (Q4) – Loren Jan 28 '20 at 10:46
  • 2
    wouldn't the quarter always be constant? There are only 4 quarters and they all have fixed end dates i.e., 31st March, 30th June, 30th Sept and 31st Dec? – Neeraj Dwivedi Jan 28 '20 at 10:48
  • If you have if `01-09-20` what should be the result? – Youcef LAIDANI Jan 28 '20 at 10:50
  • But year won't be constant . If I hit on 31st Dec , 2020 it should return 31stMarch,2021. – Loren Jan 28 '20 at 10:51
  • So the problem is execution on a day that already is the end of a quarter and you want the end of the upcoming one? – deHaar Jan 28 '20 at 10:53
  • 1
    I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `LocalDate` and `DateTimeFormatter`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Jan 28 '20 at 22:33
  • 1
    Don’t represent your date as a string. Represent it as a `LocalDate`. A string is fine for printing a result to the user, but not for processing inside your program. So only format into a string when you need to give output. – Ole V.V. Jan 28 '20 at 22:39

5 Answers5

10

To answer your question, I think you are looking for this :

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yy");
LocalDate end = LocalDate.parse("30-09-20", formatter)
    .plusMonths(3)                             // add three months to your date
    .with(TemporalAdjusters.lastDayOfMonth()); // with the last day of the month

Note: don't use the legacy Date library, you tagged your question Java-8 which mean you can use java-time API.


Get last day of current quarter

@deHaar have reason, to get the end date of curent quarter, I would suggest to use :

public LocalDate lastDayFromDateQuarter(String date) {
    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yy");
    LocalDate ld = LocalDate.parse(date, formatter);
    int quarter = ld.get(IsoFields.QUARTER_OF_YEAR); // Get the Quarter, 1, 2, 3, 4
    // Then create a new date with new quarter * 3 and last day of month
    return ld.withMonth(quarter * 3).with(TemporalAdjusters.lastDayOfMonth());
}

Get last day of next quarter

To get the last day of the next quarter, then you just can add three months to your date like so :

public static LocalDate lastDayFromDateQuarter(String date) {
    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yy");
    LocalDate ld = LocalDate.parse(date, formatter);
    int quarter = ld.get(IsoFields.QUARTER_OF_YEAR);
    return ld.withMonth(quarter * 3)
            .plusMonths(3)
            .with(TemporalAdjusters.lastDayOfMonth());
}
Youcef LAIDANI
  • 55,661
  • 15
  • 90
  • 140
  • 1
    Wouldn't that return a wrong date when the current one isn't in April, July, September or December because you always add 3 months? – deHaar Jan 28 '20 at 11:03
  • 1
    @deHaar I agree, I gives you an answer in my edit, hope this is what you are looking for – Youcef LAIDANI Jan 28 '20 at 11:18
  • 3
    Yes, `IsoFields.QUARTER_OF_YEAR` is the way to go... Had upvoted before, cannot do again ;-) – deHaar Jan 28 '20 at 11:20
  • Nice. As a detail I’d use `return ld.plus(1, IsoFields.QUARTER_YEARS).with(TemporalAdjusters.lastDayOfMonth());`. I would probably also add validation that the incoming date is indeed the last day of a quarter. – Ole V.V. Jan 28 '20 at 22:42
  • @Boris I think your test is not correct, In fact `30-12-20` is the last day of the 3rd quarter for that it return the same result, I can say that the first solution answer the OP problem in a part and the correct general solution should be the second – Youcef LAIDANI Jan 29 '20 at 17:06
  • @YCF_L, your proposed solution fails this test: `@ParameterizedTest @CsvSource({"30-09-20,2020-12-31"}) public void testLastDayFromDateQuarter(String input, LocalDate expected) { LocalDate result = timeUtils.lastDayFromDateQuarter(input); assertEquals(expected, result); }` **Test result:** AssertionFailedError: expected: <2020-12-31> but was: <2020-09-30> – Boris Jan 29 '20 at 17:07
  • @Boris check this https://www.motif.com/faq/what-are-the-beginning-and-end-dates-for-each-quarter – Youcef LAIDANI Jan 29 '20 at 17:07
  • @YCF_L, from the question: "the requirement is to find end date of next quarter i.e 31-12-20", is it not right? – Boris Jan 29 '20 at 17:09
  • @Boris I will check when I back at home :) – Youcef LAIDANI Jan 29 '20 at 17:13
  • @OleV.V. I test your code, but it doesn't return the correct result, I don't know what did you mean exactly or how should I use your code – Youcef LAIDANI Jan 29 '20 at 21:00
  • @Boris I edit my answer, can you check it now please, thank you for your test :) – Youcef LAIDANI Jan 29 '20 at 21:01
  • @YCF_L It’s not important, your answer is accepted and everything, but since you ask (at least to me it sounded like a question) [this is what I meant](https://ideone.com/O62Qx0). – Ole V.V. Jan 30 '20 at 02:36
  • 2
    @YCF_L, now it's fine, the test passed. – Boris Jan 30 '20 at 12:38
5

tl;dr

Use YearQuarter class from ThreeTen-Extra.

YearQuarter                                       // A class available in the ThreeTen-Extra library.
.from(                                            // Factory method rather than calling `new`. 
    LocalDate.of( 2020 , Month.SEPTEMBER , 30 )   // Returns a `LocalDate` object, represent a date-only value without a time-of-day and without a time zone.
)                                                 // Returns a `YearQuarter` object.
.plusQuarters( 1 )                                // Perform date-math, resulting in a new `YearQuarter` object (per immutable objects pattern). 
.atEndOfQuarter()                                 // Determine the date of last day of this year-quarter.
.toString()                                       // Generate text in standard ISO 8601 format.

2020-12-31

org.threeten.extra.YearQuarter

The ThreeTen-Extra library provides classes that extend the functionality of the java.time classes built into Java 8 and later. One of its classes is YearQuarter to represent a specific quarter in a specific year. The quarters are defined by calendar-year: Jan-Mar, Apr-June, July-Sept, Oct-Dec.

LocalDate localDate = LocalDate.of( 2020 , Month.SEPTEMBER , 30 ) ;
YearQuarter yearQuarter = YearQuarter.from( localDate ) ;

Move to the next quarter by adding one quarter to our current year-quarter.

The java.time and ThreeTen-Extra classes use immutable objects. So rather than alter ("mutate") the original object, when adding we produce a new object.

YearQuarter followingYearQuarter = yearQuarter.plusQuarters( 1 ) ;

Determine the last day of that quarter.

LocalDate lastDateOfFollowingYearQuarter = followingYearQuarter.atEndOfQuarter() ;

About java.time

The java.time framework is built into Java 8 and later. These classes supplant the troublesome old legacy date-time classes such as java.util.Date, Calendar, & SimpleDateFormat.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.

The Joda-Time project, now in maintenance mode, advises migration to the java.time classes.

You may exchange java.time objects directly with your database. Use a JDBC driver compliant with JDBC 4.2 or later. No need for strings, no need for java.sql.* classes.

Where to obtain the java.time classes?

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval, YearWeek, YearQuarter, and more.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
2

Here is my version (hopefully more readable) of finding the last day of next quarter for any date:

public LocalDate lastDayOfNextQuarter(LocalDate date) {
  Month firstMonthOfCurrentQuarter = date.getMonth().firstMonthOfQuarter();
  LocalDate lastMonthOfCurrentQuarter = date.with(firstMonthOfCurrentQuarter.plus(2));
  LocalDate lastMonthOfNextQuarter = lastMonthOfCurrentQuarter.plusMonths(3);
  return lastMonthOfNextQuarter.with(lastDayOfMonth());
}

And a corresponding test method:

@ParameterizedTest
@CsvSource({"2020-01-01,2020-06-30", "2020-02-01,2020-06-30", "2020-03-01,2020-06-30", "2020-04-10,2020-09-30",
  "2020-05-10,2020-09-30", "2020-06-10,2020-09-30", "2020-07-20,2020-12-31", "2020-08-20,2020-12-31",
  "2020-09-30,2020-12-31", "2020-10-30,2021-03-31", "2020-11-30,2021-03-31", "2020-12-31,2021-03-31"})
public void testLastDayOfNextQuarter(LocalDate input, LocalDate expected) {
  LocalDate result = timeUtils.lastDayOfNextQuarter(input);
  assertEquals(expected, result);
}
Boris
  • 22,667
  • 16
  • 50
  • 71
1

You can manipulate quarter easily with TemporalAdjusters. See below:

    LocalDate localDate = LocalDate.now();
    LocalDate firstDayOfQuarter = localDate.with(IsoFields.DAY_OF_QUARTER, 1);
    System.out.println(firstDayOfQuarter);

    LocalDate lastDayOfQuarter = firstDayOfQuarter.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
    System.out.println(lastDayOfQuarter);

    LocalDate firstDateOfNextQuarter = lastDayOfQuarter.plusDays(1);

    LocalDate lastDayOfNextQuarter = firstDateOfNextQuarter.plusMonths(2).with(TemporalAdjusters.lastDayOfMonth());
    System.out.println(lastDayOfNextQuarter);

Output:

2020-01-01
2020-03-31
2020-06-30
Shafiul
  • 1,452
  • 14
  • 21
  • You can also have a LocalDate on the first day of next quarter like this `LocalDate.now().plus(1, IsoFields.QUARTER_YEARS).with(IsoFields.DAY_OF_QUARTER, 1)`. I find this method very convenient if you want to "move" by multiple quarters at once and it will handle the change of year. – horex Aug 05 '22 at 12:33
0

You can use a Calendar instance to get the last day of the month.

String str = "30-12-20";
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yy");
Date date = format.parse(str);
Date newDate = DateUtils.addMonths(date, 3);

Calendar cal = new GregorianCalendar();
cal.setTime(newDate);

System.out.println(cal.getActualMaximum(Calendar.DAY_OF_MONTH));
david
  • 852
  • 1
  • 7
  • 22