2

I'm trying to create a function in Java that generates a quarterly date sequence, given a start and end date.

In R, for example, I can do this as follows:

generateQuarterlySequence = function(startDate, endDate)
{
  require(zoo)

  # Generate date sequence
  dateSequence = seq.Date(from = as.Date(startDate), 
                          to = as.Date(endDate), 
                          by = "quarter")

  # Convert to quarters
  dateSequence = as.yearqtr(dateSequence, format = "%Y-%m-%d") 

  # Get rid of extra white space
  dateSequence = gsub(" ", "", dateSequence) 

  return(dateSequence)

}

generateQuarterlySequence(startDate = "2017-06-30", endDate = "2017-12-31")
[1] "2017Q2" "2017Q3" "2017Q4"

Any rock stars care to show how this is done in Java? You'd be making this Java beginner very happy!

Cheers, Joe

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Mr. Vanderstelt
  • 126
  • 1
  • 8
  • It sounds to me like you want the [`YearQuarter`](http://www.threeten.org/threeten-extra/apidocs/org/threeten/extra/YearQuarter.html) class of ThreeTen-Extra. – Ole V.V. Dec 01 '17 at 22:13
  • 1
    FYI, the troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/9/docs/api/java/util/Date.html) are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [java.time](https://docs.oracle.com/javase/9/docs/api/java/time/package-summary.html) classes built into Java 8 & Java 9. See [Tutorial by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Dec 02 '17 at 00:57

4 Answers4

5

I know that in a comment I suggested ThreeTen-Extra. However, here’s a solution using pure java.time as built-in in Java 8 and later and available in Java 6 and 7 through ThreeTen Backport.

public static List<String> generateQuarterlySequence(LocalDate startDate, LocalDate endDate) {
    // first truncate startDate to first day of quarter
    int startMonth = startDate.getMonthValue();
    startMonth-= (startMonth - 1) % 3;
    startDate = startDate.withMonth(startMonth).withDayOfMonth(1);

    DateTimeFormatter quarterFormatter 
            = DateTimeFormatter.ofPattern("uuuuQQQ", Locale.ENGLISH);
    List<String> quarterSequence = new ArrayList<>();

    // iterate thorough quarters
    LocalDate currentQuarterStart = startDate;
    while (! currentQuarterStart.isAfter(endDate)) {
        quarterSequence.add(currentQuarterStart.format(quarterFormatter));
        currentQuarterStart = currentQuarterStart.plusMonths(3);
    }
    return quarterSequence;
}

Trying it with your example arguments:

    System.out.println(generateQuarterlySequence(LocalDate.of(2017, Month.JUNE, 30),
            LocalDate.of(2017, Month.DECEMBER, 31)));

prints

[2017Q2, 2017Q3, 2017Q4]
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
2

tl;dr

YearQuarter.from( LocalDate.parse( "2017-06-30" ) )
           .plusQuarters( 1 )

org.threeten.extra.YearQuarter

The other Answer by Ole V.V. using java.time is good. Alternatively, here is code using the YearQuarter class from the ThreeTen-Extra project that extends java.time with additional functionality. If you are doing much work with quarters, you will find it well worth the bother to add the ThreeTen-Extra library to your project.

The LocalDate class represents a date-only value without time-of-day and without time zone.

LocalDate start = LocalDate.parse( "2017-06-30" );
LocalDate stop = LocalDate.parse( "2017-12-31" );

From those, determine the year-quarter in the ISO 8601 calendar system, meaning Q1 is January to March, Q2 is April to June, Q3 is July to September and Q4 is October to December.

YearQuarter yqStart = YearQuarter.from( start );
YearQuarter yqStop = YearQuarter.from( stop );

Collect the series of quarters as a List.

int initialCapacity = ( int ) ( ChronoUnit.YEARS.between( start , stop ) + 1 ) * 4;
List < YearQuarter > quarters = new ArrayList <>( initialCapacity );

Loop each quarter, incrementing by calling plusQuarters, and collect into the list.

YearQuarter yq = yqStart;
while ( ! yq.isAfter( yqStop ) )
{
    quarters.add( yq );
    // Setup next loop.
    yq = yq.plusQuarters( 1 );
}

Dump to console.

System.out.println( start + "/" + stop + " = " + quarters );

2017-06-30/2017-12-31 = [2017-Q2, 2017-Q3, 2017-Q4]


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.

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
  • Basil, thanks so much for taking your time to answer my question. I'll keep note of this in case I start working with quarters more often! – Mr. Vanderstelt Dec 03 '17 at 14:17
2

My library Time4J enables following very simple solution using the streaming-API of Java-8:

DateInterval range = 
    DateInterval.between(
        PlainDate.of(2017, 6, 30),
        PlainDate.of(2017, 12, 31)
    );
range.stream(Duration.of(1, CalendarUnit.QUARTERS))
    .map(CalendarQuarter::from)
    .forEach(System.out::println);

// 2017-Q2
// 2017-Q3
// 2017-Q4

The code first constructs a date interval which is streamable. You can define a stream by iteratively adding a quarter year to the start date of the interval. The map-operation maps the generated (gregorian) dates in the stream to the desired year-quarter, called CalendarQuarter in Time4J and finally calls its toString()-method to produce the output.

If you prefer to eliminate the hyphen in the output then you can either apply a simple string-replace-method in another map-method or use a suitable formatter (which should best be stored in a static constant since it is immutable):

ChronoFormatter<CalendarQuarter> f =
  ChronoFormatter.ofPattern(
      "uuuuQQQ", PatternType.CLDR, Locale.ROOT, CalendarQuarter.chronology());
range.stream(Duration.of(1, CalendarUnit.QUARTERS))
    .map(CalendarQuarter::from)
    .map(cq -> f.format(cq))
    .forEach(System.out::println);

// 2017Q2
// 2017Q3
// 2017Q4

The last piece of code could even be simplified by leaving out the intermediate type CalendarQuarter because a gregorian date (here: PlainDate) can also be formatted as quarterly date in the same way (one map-operation less).

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
1

Here is solution using plain Java 8 streams and java.time api:

public static void main(String[] args) {
    LocalDate startDate = LocalDate.of(2000, 12, 25);
    LocalDate endDate = LocalDate.of(2002, 4, 1);
    Stream<LocalDate> quarterBounds = Stream.iterate(
            startDate.with(IsoFields.DAY_OF_QUARTER, 1), date -> date.plus(3, MONTHS));

    DateTimeFormatter quarterFormatter = 
            DateTimeFormatter.ofPattern("uuuuQQQ", Locale.ENGLISH);
    quarterBounds
            .filter(d -> !endDate.isBefore(d))
            .peek(System.out::println)
            .map(quarterFormatter::format)
            .forEach(System.out::println);
}

Sample output:

2000-10-01
2000Q4
2001-01-01
2001Q1
2001-04-01
2001Q2
2001-07-01
2001Q3
2001-10-01
2001Q4
2002-01-01
2002Q1
2002-04-01
2002Q2

Via How to get the first date and last date of current quarter in java.util.Date.

Vadzim
  • 24,954
  • 11
  • 143
  • 151