17

I need to get the first date of the current quarter as a java.util.Date object and the last date of the current quarter as a java.util.Date object.

I'm using following methods to get this month first date and this month last date.

private   Date getThisMonthFirstDate(){
    Calendar calendar = new GregorianCalendar();
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    calendar.set(Calendar.DAY_OF_MONTH, 1);
    return calendar.getTime();
}

private   Date getThisMonthLastDate(){
    Calendar calandar = new GregorianCalendar();
    calendar.set(Calendar.HOUR_OF_DAY, 0);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    calendar.set(Calendar.MILLISECOND, 0);
    calendar.set(Calendar.DAY_OF_MONTH,1);
    calendar.add(Calendar.MONTH, 1);
    calendar.add(Calendar.DATE, -1);
   return calendar.getTime();
}

Is there a way to modify that function to achieve this or could anyone point out a better way?

Assume that Q1 = Jan Feb Mar, Q2 = Apr, May, Jun, etc.

Asanka sanjaya
  • 1,461
  • 3
  • 17
  • 35
  • If you are using zero=January months, like `Date` does, the start of the quarter is just `(year, 3*quarter, 1)`. – Andy Turner Apr 08 '16 at 07:15
  • 1
    currentMonth - (currentMonth % 3) gives you the first month of the current quarter. That said, you should use the new Java 8 time API or, if not on Java 8, joda-time. Calendar and Date are awful. – JB Nizet Apr 08 '16 at 07:16
  • Note that `Date` represents an instant in time, not a "day" (a 24-ish hour period in your local timezone). As such, the "end" of a quarter is best represented as the start of the next quarter, exclusive. So a quarter is defined by the timestamp range start of this quarter (inclusive) to start of next quarter (exclusive). – Andy Turner Apr 08 '16 at 07:18
  • 2
    Java 8 does it pretty easily, i.e. [this](http://www.leveluplunch.com/java/examples/last-day-of-quarter-java8-adjuster/) – dambros Apr 08 '16 at 07:18
  • 1
    I am a bit confused about your *getThisMonthLastDate()* method since by executing *calander.add(Calendar.DAY_OF_MONTH, -1);* you skip the complete last day of the month. Maybe decreasing by one second or millisecond would be better. – Alexander Apr 08 '16 at 07:22
  • @dambros unfortunately I'm using java 7 :) – Asanka sanjaya Apr 08 '16 at 09:00
  • @Alexander +1. Need to revise that method too :) – Asanka sanjaya Apr 08 '16 at 09:01

8 Answers8

47

Pure Java-8+ solution:

LocalDate localDate = LocalDate.now();
LocalDate firstDayOfQuarter = localDate.with(localDate.getMonth().firstMonthOfQuarter())
    .with(TemporalAdjusters.firstDayOfMonth());

LocalDate lastDayOfQuarter = firstDayOfQuarter.plusMonths(2)
    .with(TemporalAdjusters.lastDayOfMonth());
Pavel
  • 2,557
  • 1
  • 23
  • 19
  • 18
    firstDayOfQuarter can be even more enhanced `LocalDate firstDayOfQuarter = date.with(IsoFields.DAY_OF_QUARTER, 1L);` – JaXt0r Jan 30 '19 at 10:32
17

Here is solution in Java 7 or older (otherwise, I suggest to check other answers):

private static Date getFirstDayOfQuarter(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.set(Calendar.MONTH, cal.get(Calendar.MONTH)/3 * 3);
    return cal.getTime();
}

private static Date getLastDayOfQuarter(Date date) {
    Calendar cal = Calendar.getInstance();
    cal.setTime(date);
    cal.set(Calendar.DAY_OF_MONTH, 1);
    cal.set(Calendar.MONTH, cal.get(Calendar.MONTH)/3 * 3 + 2);
    cal.set(Calendar.DAY_OF_MONTH, cal.getActualMaximum(Calendar.DAY_OF_MONTH));
    return cal.getTime();
}

Note 1: Months in java.util.Calendar are represented as integers starting from 0 (Jan) through 11 (Dec)
Note 2: division of integers in Java results in floor value (another integer value). So 2/3 = 0, 4/3 = 1 and so forth. So, cal.get(Calendar.MONTH)/3 * 3 calculates zero-based index of quarter: 0(Q1), 1(Q2), 2(Q3), 3(Q4).
Example: Feb in Calendar is 1. So, 1 / 3 * 3 = 0.
If Feb date is supplied, then start of the quarter is 1st of Jan (because we got 0) and the end of quarter is last day of month 0 + 2 = 2 (Mar)

Volodymyr Masliy
  • 413
  • 4
  • 14
  • Thanks for the answer, I'll try this and get back. – Asanka sanjaya Apr 08 '16 at 09:01
  • 1
    Since this answer is working for my test cases I'll mark this as the correct answer. If anyone found a error please report here :) – Asanka sanjaya Apr 08 '16 at 12:03
  • Using this in a Groovy script, I wound up having to use Math.floor and type cast it to an int. Otherwise, works well, thanks! – Chris Baker Aug 07 '17 at 21:02
  • 1
    @Asankasanjaya there was an error in my code. When 31/05/2020 was passed to getLastDayOfQuarter then it would return 01/06/2020 instead of 30/06/2020. I've edit my answer with a fix. Before changing calendar instance to new month, it was required to set it to 1 day of month. Otherwise, 31/05 would become 31/06 which isn't valid and java will automatically convert it to 01/06 which was the case – Volodymyr Masliy May 05 '20 at 10:48
7

As others mentioned, you are using outmoded classes. Sun/Oracle decided to supplant the old date-time classes with the java.time framework, a vast improvement.

Avoid the old date-time classes. They really are poorly designed, confusing, and troublesome.

java.time

The java.time framework is built into Java 8. For Java 6 & 7, use the back-port, ThreeTen-Backport. For Android, the adaptation of that back-port, ThreeTenABP.

The java.time framework is inspired by the highly successful Joda-Time library, defined by JSR 310, and extended by the ThreeTen-Extra project. See Oracle Tutorial.

LocalDate

For a date-only value, without time-of-day and without time zone, use LocalDate built-in as part of java.time. To get the current date requires a time zone as the date varies around the world (a new day dawns earlier in the east).

ZoneId zoneId = ZoneId.of ( "America/Montreal" );
LocalDate today = LocalDate.now ( zoneId );

Quarter & YearQuarter

The ThreeTen-Extra project mentioned above serves as the proving ground for possible future features to be added to java.time. To use this library, add its jar file to your project (or use Maven etc.). If you really don’t want to add a library, see an alternate solution Answer by dheeran.

This library currently includes the Quarter and YearQuarter classes that you might find particularly useful. Safer to pass around objects of these types in your code rather than use strings and numbers to represent your quarters.

YearQuarter currentQuarter = YearQuarter.now ( zoneId );

The YearQuarter has many useful methods, including asking for its dates.

LocalDate start = currentQuarter.atDay ( 1 );
LocalDate stop = currentQuarter.atEndOfQuarter ();

Dump to console.

System.out.println ( "today: " + today + " currentQuarter: " + currentQuarter + " start: " + start + " stop: " + stop );

today: 2016-04-08 currentQuarter: 2016-Q2 start: 2016-04-01 stop: 2016-06-30

If you really do not want to add the ThreeTen-Extra library as I recommended, see the Answer by Pavel for another solution using only the built-in java.time classes. But if you are doing much work with quarters, I am sure you’ll find ThreeTen-Extra to be well worth the trouble of adding a library to your project.

Conversion

If you must use java.util.Date to work with old code not yet updated to java.time, you can convert back and forth. Find new methods added to the old classes for conversion.

Unfortunately, the old classes lack any clean way to represent a date-only value. So there is no perfectly clean way to go to/from a LocalDate.

java.sql.Date

The java.sql.Date class pretends to be a date-only but actually contains a time-of-day adjusted to 00:00:00.0. This class awkwardly inherits from java.util.Date but the doc clearly warns against using that fact; you are supposed to treat the two as separate unrelated classes.

java.sql.Date sqlDate_quarterStart = java.sql.Date.valueOf( start );

java.util.Date

A java.util.Date represents both a date and time-of-day, effectively in UTC. We can first adjust our LocalDate into the first moment of a day to get a date with time-of-day, a ZonedDateTime. From there we can ask for an Instant which is a moment on the timeline in UTC. The old .Date class has a static from( Instant ) conversion method.

ZonedDateTime zdt = start.atStartOfDay( zoneId ); // First moment of the day on a certain date.
Instant instant = zdt.toInstant(); // Moment on the timeline in UTC.
java.util.Date utilDate = java.util.Date.from( instant );

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.

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

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

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
4

Keep it simple

I think the following is most elegant because it doesn't have any magical numbers (like 92), TemporalAdjusters or non-obvious calculations, and can easily be adapted to get the start and end of any quarter (not just current quarter).

import static java.time.temporal.IsoFields.QUARTER_OF_YEAR;

LocalDate date = LocalDate.now(); // can be any other date

int year = date.getYear();
int quarter = date.get(QUARTER_OF_YEAR);
        
LocalDate start = YearMonth.of(year, 1)                    // January of given year
                           .with(QUARTER_OF_YEAR, quarter) // becomes first month of given quarter
                           .atDay(1);                      // becomes first day of given quarter

LocalDate end = YearMonth.of(year, 3)                      // March of given year
                         .with(QUARTER_OF_YEAR, quarter)   // becomes 3rd (last) month of given quarter
                         .atEndOfMonth();                  // becomes last day of given quarter

If you don't have a date, but just a year and quarter (e.g. Q2 2020), this becomes:

LocalDate start = YearMonth.of(2020, 1).with(QUARTER_OF_YEAR, 2).atDay(1);
LocalDate end = YearMonth.of(2020, 3).with(QUARTER_OF_YEAR, 2).atEndOfMonth();
herman
  • 11,740
  • 5
  • 47
  • 58
3

It's pretty simple with LocalDate

LocalDate inputDate = LocalDate.parse("2018-09-04");
LocalDate firstDayOfQuarter = inputDate.withMonth(inputDate.get(IsoFields.QUARTER_OF_YEAR) * 3 - 2).with(TemporalAdjusters.firstDayOfMonth());
LocalDate lastDayOfQuarter = inputDate.withMonth(inputDate.get(IsoFields.QUARTER_OF_YEAR) * 3).with(TemporalAdjusters.lastDayOfMonth());

Cheers!

Mohan
  • 4,755
  • 2
  • 27
  • 20
2

you can use java 8 LocaDate to get current quarter first and last day

The code below gives you.

public static LocalDate getCurrentQuarterStartDay(LocalDate date) {
        return date.with(IsoFields.DAY_OF_QUARTER, 1L);
    }

public static LocalDate getCurrentQuarterEndDate(LocalDate date) {
        return date.with(IsoFields.DAY_OF_QUARTER, 92L);
    }

Here in second method the 92 has partially lenient

The day-of-quarter has values from 1 to 90 in Q1 of a standard year, from 1 to 91in Q1 of a leap year, from 1 to 91 in Q2 and from 1 to 92 in Q3 and Q4

see ISOFields documentation for details here

Pavan
  • 103
  • 1
  • 6
  • 1
    I don't think this works, using 92L doesn't give the last day of the quarter for Q1 and Q2 but 1st (or 2nd) of April and 1st of July: " If the quarter has less than 92 days, then day 92, and potentially day 91, is in the following quarter." – ahelix Sep 29 '20 at 21:56
0

You can use joda:

Date start = date.withMonthOfYear(((date.getMonthOfYear() / 3) + 1) * 3 - 2)
            .withDayOfMonth(1)
            .withTimeAtStartOfDay()
            .toDate();
Date end = date.withMonthOfYear(((date.getMonthOfYear() / 3) + 1) * 3)
            .withDayOfMonth(Month.of(((date.getMonthOfYear() / 3) + 1) * 3).maxLength())
            .withTimeAtStartOfDay()
            .toDate();

When DateTime date = new DateTime(2016, 8, 12, 1, 1, 1);

I get:

Fri Jul 01 00:00:00 CEST 2016
Fri Sep 30 00:00:00 CEST 2016
g-t
  • 1,455
  • 12
  • 17
  • Unfortunately I'm using java.util.date for all my other functions, so I think its better to go with that :) – Asanka sanjaya Apr 08 '16 at 09:02
  • Ok I see. You should definitely consider switching to joda.java.util.Date api is just horrible and I've never worked in a project which uses it directly. Joda became some kind of standard. – g-t Apr 08 '16 at 09:07
  • Thanks for mentioning that :). For future works I'll use Joda time instead of java.util.Date :) – Asanka sanjaya Apr 08 '16 at 09:08
  • If using Java 8, use java.time. It is inspired from Joda time. – adarshr Apr 08 '16 at 19:08
  • @Asankasanjaya Some quick searching on Stack Overflow will show that the java.util.Date/.Calendar classes are a terrible mess and should be avoided. Even Sun/Oracle gave up on them, agreeing to supplant them with the java.time classes. – Basil Bourque Apr 09 '16 at 16:11
  • `((date.getMonthOfYear() / 3) + 1) * 3 - 2` gives 13 for December month. – whoami Dec 26 '16 at 11:01
0

Using the answer from Volodymyr Masliy using java.util.Date, there is some bug that came up in my tests If you enter "31/05/2000" it returns July 1st of that year, which is obviously not what we're looking for.

I used java.time.LocalDate as suggested by Basil Bourque and dheeran and the test case works ok.

My method is as follows:

public static Date lastDayOfQuarter(Date date) {
    LocalDate inputDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    LocalDate lastDayOfQuarter = inputDate.withMonth(inputDate.get(IsoFields.QUARTER_OF_YEAR) * 3).with(TemporalAdjusters.lastDayOfMonth());
    return Date.from(LastDayOfQuarter.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
}

My UnitTests using ZohhakRunner are as follows:

@TestWith({
    "01/03/2000, 31/03/2000",
    "05/04/1982, 30/06/1982",
    "31/05/1982, 30/06/1982",
    "09/07/1990, 30/09/1990",
    "11/11/2019, 31/12/2019",
    "31/03/2016, 31/03/2016"})
public void lastDayOfQuarter_useInputDate_returnExpectedEndOfQuarterDate(String inputDateAsString, String endOfQuarterAsString) throws ParseException{
    Date inputDate = new SimpleDateFormat("dd/MM/yyyy").parse(inputDateAsString);
    Date expectedDate = new SimpleDateFormat("dd/MM/yyyy").parse(endOfQuarterAsString);

    Date result = DateUtils.lastDayOfQuarter(inputDate);
    assertEquals(result, expectedDate);
}
DaithiG
  • 849
  • 1
  • 9
  • 15