1

Given a year and a month; I want to get two Date Objects. one for startDate of the month and one for the end Date of the month. I have it implemented here and it works. but this looks too verbose, and I am wondering if there is a neat solution to this;

Eg given March 2014, start Date will be March 01 and end Date will be March 31 ( as Date objects with millisecond precision)

public setDates(int month,int year) {

        Calendar calendar = Calendar.getInstance();


        // Use the calendar to get the startDate and endDate of this Invoice.
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH,month);

        //set start date
        calendar.set(Calendar.DAY_OF_MONTH,
                     calendar.getActualMinimum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY,
                     calendar.getActualMinimum(Calendar.HOUR_OF_DAY));
        calendar.set(Calendar.MINUTE,
                     calendar.getActualMinimum(Calendar.MINUTE));
        calendar.set(Calendar.SECOND,
                     calendar.getActualMinimum(Calendar.SECOND));
        calendar.set(Calendar.MILLISECOND,
                     calendar.getActualMinimum(Calendar.MILLISECOND));
        this.startDate = calendar.getTime();

        //endDate start date
        calendar.set(Calendar.DAY_OF_MONTH,
                     calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
        calendar.set(Calendar.HOUR_OF_DAY,
                     calendar.getActualMaximum(Calendar.HOUR_OF_DAY));
        calendar.set(Calendar.MINUTE,
                     calendar.getActualMaximum(Calendar.MINUTE));
        calendar.set(Calendar.SECOND,
                     calendar.getActualMaximum(Calendar.SECOND));
        calendar.set(Calendar.MILLISECOND,
                     calendar.getActualMaximum(Calendar.MILLISECOND));
        this.endDate = calendar.getTime();
}
brain storm
  • 30,124
  • 69
  • 225
  • 393
  • Can you use Joda Time? It'll make things a lot simpler. Also, consider what time zone you're interested in - the month will start and end at different times for different time zones. Finally, consider using an *exclusive* upper bound instead of an inclusive one - so the start of the next month, basically. – Jon Skeet Feb 04 '14 at 20:50
  • in what way using exclusive upper bound superior? I dont know what Joda Time is. so that would take some time for me to read and understand.. – brain storm Feb 04 '14 at 20:54
  • I think Joda Time will be the basis for this: http://download.java.net/jdk8/docs/api/java/time/package-summary.html – peter.petrov Feb 04 '14 at 20:55
  • It is not so complex (for your task), you can try it. But if you stick with JDK 7's built in capabilities, you're also OK. – peter.petrov Feb 04 '14 at 20:56
  • @user1988876: With an exclusive upper bound, you don't need to worry about whether you're taking the *start* of the last day of the month or the *end* of the last day of the month, etc - and the exclusive upper bound for one interval is the inclusive lower bound for the next interval. – Jon Skeet Feb 04 '14 at 20:59
  • @JonSkeet: so If I am looking March 1, my exclusive upper bound will be April 1 correct? basically I need this to check if a given date falls between startDate and endDate; I am doing a `return !((date.before(startDate)) || (date.after(endDate)));` check – brain storm Feb 04 '14 at 21:04
  • @user1988876: You'd want to change that to `return !date.before(startDate) && endDate.after(date);`. That would then be an exclusive upper bound. And yes, that's what the exclusive upper bound would be. You don't need to worry about granularity etc. – Jon Skeet Feb 04 '14 at 21:05
  • @user1988876 I would recommend using `compareTo` instead of `before` and `after`, it gives you more control to make your comparaison inclusive or not. – Jonathan Drapeau Feb 04 '14 at 21:13
  • How is your question not already answered by [this](http://stackoverflow.com/q/10828398/642706), [this](http://stackoverflow.com/q/14241836/642706), [this](http://stackoverflow.com/q/8997228/642706), [this](http://stackoverflow.com/q/14475489/642706), [this](http://stackoverflow.com/q/3083781/642706), [this](http://stackoverflow.com/q/2261480/642706) or the many other duplicate questions? – Basil Bourque Feb 05 '14 at 12:43

3 Answers3

5

You can make this code considerably simpler by making some assumptions:

  • The first day of the month is always day 1
  • The minimum hour will always be 0
  • ... etc

You can then find the last millisecond of the month by adding one month and subtracting a millisecond.

So the code could look like this:

// Note year/month reversal: try to consistently use larger units first. It
// makes for a cleaner API.
public setDates(int year, int month, TimeZone zone) {
    Calendar calendar = Calendar.getInstance(zone);

    // Do you really want 0-based months, like Java has? Consider month - 1.
    calendar.set(year, month, 1, 0, 0, 0);
    calendar.clear(Calendar.MILLISECOND);
    startDate = calendar.getTime();

    // Get to the last millisecond in the month
    calendar.add(Calendar.MONTH, 1);
    calendar.add(Calendar.MILLISECOND, -1);
    endDate = calendar.getTime();
}

To use an exclusive upper bound (as I'd recommend), just get rid of the calendar.add(Calendar.MILLISECOND, -1) near the end.

Oh, and I'd thoroughly recommend using Joda Time instead of java.util.Date etc - it's a much cleaner API.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • as I commented below, you end up changing calendar object when you add month. what if you want to have reference to this object intact and not modify it? and why `calendar.clear(Calendar.MILLISECOND);` is needed? – brain storm Feb 04 '14 at 21:09
  • Make another `Calendar` instance to get your lower and upper bounds. – Jonathan Drapeau Feb 04 '14 at 21:10
  • @user1988876: Then don't do use that object! This method creates a new `Calendar` object, and after the method has completed it will be eligible for garbage collection. If you've got a `Calendar` that you don't want to change, just don't change it... – Jon Skeet Feb 04 '14 at 21:11
  • After you call `getTime()` on the `Calendar`, and have yourself a `Date`, subsequent changes to the `Calendar` will not affect that `Date`... just FYI. – dcsohl Feb 04 '14 at 21:16
  • ok got it. why did you use `calendar.clear(Calendar.MILLISECOND)` and `calendar.set(year, month, 1, 0, 0, 0);` instead of `calendar.set(Calendar.YEAR, year); calendar.set(Calendar.MONTH,month);` – brain storm Feb 04 '14 at 21:16
  • @user1988876: Because it's two calls instead of 6 - just as clear (if not clearer) but simpler IMO. – Jon Skeet Feb 04 '14 at 21:21
  • @user1988876 The reason is to make sure the `Calendar` instance contains the date to the millisecond. If you don't clear them, you'll have the milliseconds that the instance got when it was created, there's a very slim chance it will be 0. Better to clear them since you want millisecond precision. – Jonathan Drapeau Feb 04 '14 at 21:31
0

Take March 1st. Add 1 to the month field. Then subtract 1 day.
Here is your last day of the month.

The first day is clear, it is 1st of month of year.

Verbose is OK, there's no much less verbose code version
(in JDK <= 7) if you stick to Java's built-in libraries.

peter.petrov
  • 38,363
  • 16
  • 94
  • 159
0

use JODA, please.

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;


public class Dates {

    /**
     * @param args
     */
    public static void main(String[] args) {
        //without JODA

        Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Europe/Helsinki"));

        calendar.set(1921, 4, 1, 0, 0, 0);
        calendar.clear(Calendar.MILLISECOND);
        Date startDate = calendar.getTime();

        System.out.println(startDate);

        calendar.add(Calendar.MONTH, 1);
        calendar.add(Calendar.MILLISECOND, -1);
        Date endDate = calendar.getTime();

        System.out.println(endDate);

        /*
         * Sat Apr 30 19:20:08 BRT 1921
         * Tue May 31 19:20:07 BRT 1921
         */

        //with JODA
        DateTimeZone zone = DateTimeZone.forTimeZone(TimeZone.getTimeZone("Europe/Helsinki"));
        DateTime dt = new DateTime(1921, 4, 1, 0, 0, 0, 0, zone);
        DateTime plusPeriod = dt.plus(Period.months(1)).minus(Period.millis(1));

        System.out.println(dt);
        System.out.println(plusPeriod);

        /*
         * 1921-04-01T00:00:00.000+01:39:52
         * 1921-04-30T23:59:59.999+01:39:52
         */
    }
}
Leo
  • 6,480
  • 4
  • 37
  • 52