简体   繁体   中英

Java: when setting a date in a Calendar object, can I use day/month/year references?

I'm pretty new to Java world, and I'm practicing a lot. My last exercize is about an Apartment Renting program. My question refers to the "booking" part in the Manager class, in which I have to check if the requested arrival date is linked to the low, medium or high season lot in the array double tariffs[].

Here is the portion of code with the bookApartment() method, where code and id are the keys in the HashMaps of Apartments and Clients (booking is correct only if the arrival date is a Saturday):

public Booking bookAppartment(String code, String id, int day, int month, int year, int nweeks) throws WrongDay {
    Calendar date = Calendar.getInstance();
    date.set(year, month-1, day);
    int weekday = date.get(Calendar.DAY_OF_WEEK);
    Booking book=null;
    if(code!="" && id!=""){
        if(weekday!=Calendar.SATURDAY)
            throw new WrongDay(date);
        else{
            for(Map.Entry<String , Apartment> apt : apts.entrySet()){
                for(Map.Entry<String, Client> client : clients.entrySet()){
                    if(apt.getKey()==code && client.getKey()==id && weekday==Calendar.SATURDAY){
                        book = new Booking(client.getValue(), apt.getValue(), d, m, y, nweeks);
                        bookings.add(book);
                        book.setPrice(d, m, y, apt.getValue().getTariffs(), nweeks);
                        break;
                    }
                }
            }
        }
    }   
    return book;
}

And here I attach the constructor of the Booking object and my personal override of the setPrice() method, which calculates the entire booking price selecting the correct tariffs[] lot:

public class Booking {

    private Client client;
    private Apartment apt;
    private double price;
    private int numweeks;
    private static int day, month, year;

    public Booking(Client client, Apartment apt, int day, int month, int year, int numweeks){
        this.client = client;
        this.apt = apt;
        Booking.day = day;
        Booking.month = month;
        Booking.year = year;
        this.numweeks = numweeks;
    }

    // other stuff 

    public void setPrice(int day, int month, int year, double[] tariff, int numweeks){
        tariff = apt.getTariffs();
        Booking.day=day;
        Booking.month=month;
        Booking.year=year;

        Calendar date = Calendar.getInstance();
        date.set(year, month-1, day);
        Calendar date1 = Calendar.getInstance();
        date1.set(2008, 6, 1);
        Calendar date2 = Calendar.getInstance();
        date2.set(2008, 6, 31);
        Calendar date3 = Calendar.getInstance();
        date3.set(2008, 7, 1);
        Calendar date4 = Calendar.getInstance();
        date4.set(2008, 7, 31);
        Calendar date5 = Calendar.getInstance();
        date5.set(2008, 11, 20);
        Calendar date6 = Calendar.getInstance();
        date6.set(2009, 0, 1);

        if(date.equals(date1) || date.equals(date2) || (date.after(date1) && date.before(date2))){
            this.price = tariff[1] * numweeks;
        } else if(date.equals(date3) || date.equals(date4) || (date.after(date3) && date.before(date4))){
            this.price = tariff[2] * numweeks;
        } else if(date.equals(date5) || date.equals(date6) || (date.after(date5) && date.before(date6))){   
            this.price = tariff[2] * numweeks;
        } else{
            this.price = tariff[0] * numweeks;
        }

    }    
}

I encounter the problem when setting the price of a Booking object with arrival date on the 20th December 2008 (considered high season): it skips the third if check (expected) and goes directly to the last else .

But if I run my own program to check if the dates are the same, passing directly the values to day, month and year, the test is passed.

So it seems to me that I cannot pass only references not pointing to an int value not manually setted. Is it possible I am right? If so, I really don't know how to go on.

Thanks in advance: I hope I used all the right words in the right places.

When you get a calendar instance, it defaults to using the current time (right down to the millisecond). Thus, when set your date in it:

Calendar date = Calendar.getInstance();
date.set(year, month-1, day);

... the date is still left with "random" values for the hours, minutes, seconds and milliseconds. The same goes for date1 through to date6 .

In your code, you create all the dates one right after the other, so the speed of executing those instructions may mean that the first few dates end up with identical values for hours, minutes, seconds and milliseconds. However there is no guarantee of this.

What you're finding is that when you do, for example, date.equals(date3) , the year month and day match, but the other fields potentially don't.

To solve this, call clear() first:

Calendar date = Calendar.getInstance();
date.clear();
date.set(year, month-1, day);

Also, you probably don't actually want to compare calendars for equality. You can, but if you look at the Javadoc for it, it says:

 * Compares this <code>Calendar</code> to the specified
 * <code>Object</code>.  The result is <code>true</code> if and only if
 * the argument is a <code>Calendar</code> object of the same calendar
 * system that represents the same time value (millisecond offset from the
 * <a href="#Epoch">Epoch</a>) under the same
 * <code>Calendar</code> parameters as this object.
 *
 * <p>The <code>Calendar</code> parameters are the values represented
 * by the <code>isLenient</code>, <code>getFirstDayOfWeek</code>,
 * <code>getMinimalDaysInFirstWeek</code> and <code>getTimeZone</code>
 * methods. If there is any difference in those parameters
 * between the two <code>Calendar</code>s, this method returns
 * <code>false</code>.
 *
 * <p>Use the {@link #compareTo(Calendar) compareTo} method to
 * compare only the time values.

You're probably better off using:

if (date.getTime().equals(date1.getTime()))
{
  ...
}

... and comparing the returned Date objects, or doing as the Javadoc suggests and using compareTo() :

if (date.compareTo(date1) == 0)
{
  ...
}

I understand you are doing an exercise, but you should know:

(a) Avoid java.util.Calendar

The java.util.Date and .Calendar classes bundled with Java are notoriously troublesome. Avoid them. Use either the new java.time package bundled with Java 8, or the Joda-Time library which inspired java.time. Both java.time and Joda-Time have some pros and cons over each other, both are active projects, and you can even use them both in a project.

(b) Date-Only

The old .Date & .Calendar classes lack a representation of date-only without a time-of-day. But that is what your Question demands, a class that is date-only without time and time zones. Fortunately both Joda-Time and java.time have such a class, both called LocalDate .

(c) Half-Open

The best approach to spans of time is called "Half-Open" where the beginning is inclusive and the ending exclusive. For example the month of June would be June 1 and going up to, but not including, July 1. This simplifies things whether doing date-only or date-time work. Joda-Time and java.time adopt this approach.

The other answer by Greg Kopff seems to be correct, the time-of-day portion is throwing you off.

Here is some example code in Joda-Time 2.4 to get you headed in the right direction.

LocalDate target = new LocalDate( 2008, 12, 20 );

LocalDate highSummerStart = new LocalDate( 2008, 6, 1 );  // Half-Open. Inclusive.
LocalDate highSummerStop = new LocalDate( 2008, 7, 1 );  // Exclusive.

LocalDate lateSummerStart = new LocalDate( 2008, 7, 1 );  // Half-Open. Inclusive.
LocalDate lateSummerStop = new LocalDate( 2008, 8, 1 );  // Exclusive.

LocalDate holidaysStart = new LocalDate( 2008, 11, 20 );  // Half-Open. Inclusive.
LocalDate holidaysStop = new LocalDate( 2009, 1, 2 );  // Exclusive.

if ( this.rangeContainsTarget( highSummerStart, highSummerStop, target ) ) {
    System.out.println( "Apply High Summer rates." );
} else if ( this.rangeContainsTarget( lateSummerStart, lateSummerStop, target ) ) {
    System.out.println( "Apply Late Summer rates." );
} else if ( this.rangeContainsTarget( holidaysStart, holidaysStop, target ) ) {
    System.out.println( "Apply Holidays rates." );
} else { // Else not in special season.
    System.out.println( "Apply default rates." );
}

And the comparison method.

private boolean rangeContainsTarget( LocalDate start, LocalDate stop, LocalDate target )
{
    // Half-Open approach. If the Target is GREATER THAN OR EQUAL TO Start AND Target is LESS THAN Stop.
    if ( start.isAfter( stop ) ) {
        return false; // Or throw exception.
    }
    boolean startGood = ( target.isEqual( start ) || target.isAfter( start ) );
    boolean stopGood = target.isBefore( stop );
    boolean containsTarget = ( startGood && stopGood );
    return containsTarget;
}

The old .Date/.Calendar classes lack a way to represent a span of time. Joda-Time offers three classes to define a span of time in various ways: Interval, Period, and Duration. Unfortunately they work only with DateTime, not LocalDate. So I did not use them in the example above, where Interval would have been handy.


By the way, if in Joda-Time you do need a date plus time-of-day yet want to focus on days, call the withTimeAtStartOfDay() method to get a DateTime object set to the first moment of the day. That first moment is not always the time 00:00:00.000 because of Daylight Saving Time and perhaps other anomalies.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM