Java Parking fee calculations

2.8k views Asked by At

It seems like, I couldn't find the answer for my problem, so here I am, first on Stackoverflow :)

The If statement tree that will be mentioned:

buttonSzamol.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {

            //Változók

                int StartHour = 18;
                int StartMin = 50;
                int StopHour = 20;
                int StopMin = 49;
                int DayTimeIntervalStart = 6;
                int DayTimeIntervalStop = 17;
                int NightTimeIntervalLateStart = 18;
                int NightTimeIntervalLateStop = 23;
                int NightTimeIntervalEarlyStart = 0;
                int NightTimeIntervalEarlyStop = 5;
              int DayHoursTotal = 0;
                int NightHoursTotal = 0;
                int DayTimePricePerHour = Integer.parseInt(NappaliOraDij.getText());
                int NightTimePricePerHour = Integer.parseInt(EjszakaiOraDij.getText());

                int StartDay = Integer.parseInt((DatumStart.getText()).replace(".", ""));
                int StopDay = Integer.parseInt((DatumStart.getText()).replace(".", ""));

                //1 started hour
                if( (StartDay == StopDay) && ( ( (StartHour == StopHour) && (StartMin < StopMin) ) || ( ((StartHour + 1) == StopHour) && (StartMin >= StopMin) ) ) ) {
                    if((DayTimeIntervalStart <= StartHour) && (StopHour <= DayTimeIntervalStop)) {
                        DayHoursTotal++;
                    }
                    if((NightTimeIntervalLateStart <= StartHour) && (StopHour <= NightTimeIntervalLateStop)) {
                        NightHoursTotal++;
                    }
                } else/*More hours*/if( (StartDay == StopDay) && ( ( (StartHour < StopHour) && (StartMin <= StopMin) ) || ( (StartHour < StopHour) && (StartMin > StopMin) ) ) ) {
                    if( (StartHour < StopHour) && (StartMin < StopMin) ) {
                        if((DayTimeIntervalStart <= StartHour) && (StopHour <= DayTimeIntervalStop)) {
                            DayHoursTotal = DayHoursTotal + (StopHour - StartHour);
                            DayHoursTotal++;
                        }
                        if((NightTimeIntervalLateStart <= StartHour) && (StopHour <= NightTimeIntervalLateStop)) {
                            NightHoursTotal = NightHoursTotal + (StopHour - StartHour);
                            NightHoursTotal++;
                        }
                    }else if(( (StartHour < StopHour) && (StartMin >= StopMin) )) {
                        if((DayTimeIntervalStart <= StartHour) && (StopHour <= DayTimeIntervalStop)) {
                            DayHoursTotal = DayHoursTotal + (StopHour - StartHour);
                            if(StartMin != StopMin) {
                                DayHoursTotal--;
                            }
                        }
                        if((NightTimeIntervalLateStart <= StartHour) && (StopHour <= NightTimeIntervalLateStop)) {
                            NightHoursTotal = NightHoursTotal + (StopHour - StartHour);
                            if(StartMin != StopMin) {
                                NightHoursTotal--;
                            }
                        }
                    }
                }

            NappaliOrak.setText(Integer.toString(DayHoursTotal));
            EjszakaiOrak.setText(Integer.toString(NightHoursTotal));
            OrakOsszesen.setText(Integer.toString(DayHoursTotal + NightHoursTotal));
            NappaliOsszeg.setText(Integer.toString(DayHoursTotal * DayTimePricePerHour));
            EjszakaiOsszeg.setText(Integer.toString(NightHoursTotal * NightTimePricePerHour));
            VegOsszeg.setText(Integer.toString((DayHoursTotal * DayTimePricePerHour) + (NightHoursTotal * NightTimePricePerHour)));
        }
    });

So, the problem in a nutshell is. I've tried to create a parking fee calculator for my colleague at work. The main idea is, that it needs to calculate how many Daytime and how many Nighttime hours the client started, and it needs to calculate the price of those hours. I've changed the StartHour/Min-StopHour/Min fields to straight integers to be more understanable. I don't know if there is a module for this, but I started doing this with a lot of If statements, where I just got tangled up. In the included pastebin, there is starting time 18:50 and stop time 20:49. If we input this data, the output should be 2 started day hours. Now if the minute is the same, it does not count as a started hour. But if we change the input to 20:51, then it started an another hour, so the DayHoursTotal should be equal to 3.

Thank you in advance, for any help. If you have more questions about my code or idea, just ask.

4

There are 4 answers

0
George Derpi On BEST ANSWER

It seems that you are trying to calculate the started hours not just between 2 times, but also between different dates.

For this it is best to use the java.time package and more specifically the LocalDateTime class.

LocalDateTime.of(startYear, startMonth, startDay, startHour, startMinute) 

LocalDateTimes in conjuction with the between() method from the Java 8 ChronoUnit class gets exactly what you need.

ChronoUnit.MINUTES.between(Temporal t1, Temporal t2)

PS: You don't need that many 'interval' variables.
Just the start hour of the day (dayTimeIntervalStart) and night (nightTimeIntervalLateStart) rate is enough.
The hours rates before and after can be derived from those two intervals.


Spoiler!! look away if you want to investigate further yourself! ;)

Here is a runnable code sample that shows the parking logic for >1 day:
(I have omitted the user input parsing/logic, because that depends on your implementation)

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

public class ParkingFee {

    private static long hoursDifference(LocalDateTime ldt1, LocalDateTime ldt2) {
        long minutesDiff = ChronoUnit.MINUTES.between(ldt1, ldt2);
        long hoursDiff = Math.round(Math.ceil(minutesDiff/60.0));
        return hoursDiff;
    }

    public static long hoursDifference(
                                int startDay, int startMonth, int startYear, int startHour, int startMinute, 
                                int endDay, int endMonth, int endYear, int endHour, int endMinute) {
        return hoursDifference(
                    LocalDateTime.of(startYear, startMonth, startDay, startHour, startMinute), 
                    LocalDateTime.of(endYear, endMonth, endDay, endHour, endMinute));
    }

    public static int determineDayCycle(int dayTimeIntervalStart, int nightTimeIntervalLateStart) {
        return nightTimeIntervalLateStart - dayTimeIntervalStart;
    }

    public static void main(String[] args) {
        // Hourly rates
        int dayTimePricePerHour = 5;
        int nightTimePricePerHour = 10;

        // Rate intervals
        int dayTimeIntervalStart = 6;
        int nightTimeIntervalLateStart = 18;

        // Counted hours per rate
        int dayHoursTotal = 0;
        int nightHoursTotal = 0;

        // Start date and time
        int startYear = 2019;
        int startMonth = 1;
        int startDay = 1;
        int startHour = 20;
        int startMinute = 50;

        // End date and time
        int endYear = 2019;
        int endMonth = 1;
        int endDay = 3;
        int endHour = 2;
        int endMinute = 49;

        // Calculate the hours difference
        long hourDiff = hoursDifference(
                startDay, startMonth, startYear, startHour, startMinute, 
                endDay, endMonth, endYear, endHour, endMinute);
        System.out.println("Hour difference found: "+ hourDiff);

        // Handle parking for full days
        if (hourDiff > 24) {
            int dayCycle = determineDayCycle(dayTimeIntervalStart, nightTimeIntervalLateStart);
            long fullDays = hourDiff / 24;
            nightHoursTotal += (24-dayCycle)*fullDays;
            dayHoursTotal += dayCycle*fullDays;
            hourDiff = hourDiff % 24;
        }

        // Handle the parking for less than full day
        while (hourDiff > 0) {
            if (startHour < dayTimeIntervalStart) { // Before the day interval -> night
                nightHoursTotal++;
            } else if(startHour < nightTimeIntervalLateStart) { // Before the night interval -> day
                dayHoursTotal++;
            } else { // After the day interval -> night
                nightHoursTotal++;
            }
            startHour++;
            if (startHour > 23) // At midnight reset the hour to 0
                startHour = 0;
            hourDiff--;
        }

        System.out.println("Day hours: "+ dayHoursTotal);
        System.out.println("Night hours: "+ nightHoursTotal);
        System.out.println("Total hours: "+ (dayHoursTotal + nightHoursTotal));
        System.out.println("Day rate charged at "+ dayTimePricePerHour +": "+ (dayHoursTotal * dayTimePricePerHour));
        System.out.println("Night rate charged at "+ nightTimePricePerHour +": "+ (nightHoursTotal * nightTimePricePerHour));
        System.out.println("Total rate charged: "+ ((dayHoursTotal * dayTimePricePerHour) + (nightHoursTotal * nightTimePricePerHour)));
    }
}

This outputs:

Hour difference found: 30
Day hours: 12
Night hours: 18
Total hours: 30
Day rate charged at 5: 60
Night rate charged at 10: 180
Total rate charged: 240

2
Wisthler On

divide et impera

Cutting down the big logic in small blocks make it easier to achieve

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;

class Scratch {
    static int StartHour = 18;
    static int StartMin = 50;
    static int StopHour = 20;
    static int StopMin = 48;
    static int DayTimeIntervalStart = 6;
    static int DayTimeIntervalStop = 17;
    static int NightTimeIntervalLateStart = 18;
    static int NightTimeIntervalLateStop = 23;
    static int NightTimeIntervalEarlyStart = 0;
    static int NightTimeIntervalEarlyStop = 5;
    static int DayTimePricePerHour = 10;
    static int NightTimePricePerHour = 5;
    static LocalTime dayStart = LocalTime.of(DayTimeIntervalStart, 0);
    static LocalTime dayStop = LocalTime.of(DayTimeIntervalStop, 0);
    static LocalTime nightEarlyStart = LocalTime.of(NightTimeIntervalEarlyStart, 0);
    static LocalTime nightEarlyStop = LocalTime.of(NightTimeIntervalEarlyStop, 0);
    static LocalTime nightLateStart = LocalTime.of(NightTimeIntervalLateStart, 0);
    static LocalTime nightLateStop = LocalTime.of(NightTimeIntervalLateStop, 0);


    public static void main(String[] args) {
        LocalDateTime start = LocalDateTime.of(2019, 1, 1, StartHour, StartMin);
        LocalDateTime stop = LocalDateTime.of(2019, 1, 1, StopHour, StopMin);

        for(int i = 0; i < 3; i++){
            stop = stop.plusMinutes(1L);
            System.out.println(process(start, stop));
            System.out.println("******");
        }

        stop = stop.plusDays(1L);
        System.out.println(process(start, stop));
        System.out.println("******");

    }


    public static int process(LocalDateTime start, LocalDateTime stop){
        System.out.println(String.format("checking between %s and %s", start, stop));
        if(stop.toLocalDate().isAfter(start.toLocalDate())){
            // start and stop not on the same date
            // split the computation, first currentDay then the rest
            LocalDateTime endOfDay = LocalDateTime.of(start.toLocalDate(), LocalTime.MAX);
            int resultForCurrentDay = process(start, endOfDay);

            // not for the rest
            LocalDateTime startOfNextDay = LocalDateTime.of(start.toLocalDate().plusDays(1L), LocalTime.MIN);
            int resultForRest = process(startOfNextDay, stop);
            return resultForCurrentDay + resultForRest;
        }else{
            // start and stop on the same date
            return processIntraDay(start, stop);
        }
    }

    private static int processIntraDay(LocalDateTime start, LocalDateTime stop) {
        int result = 0;
        LocalTime startTime = start.toLocalTime();
        LocalTime stopTime = stop.toLocalTime();

        // step 1: check early morning
        result += checkBoundaries(startTime, stopTime, nightEarlyStart, nightEarlyStop, NightTimePricePerHour);

        // step 2: check day time
        result += checkBoundaries(startTime, stopTime, dayStart, dayStop, DayTimePricePerHour);


        // step 3: check late night
        result += checkBoundaries(startTime, stopTime, nightLateStart, nightLateStop, NightTimePricePerHour);

        return result;
    }

    private static int checkBoundaries(LocalTime startTime, LocalTime stopTime, LocalTime lowerBoundary, LocalTime upperBoundary, int priceRatePerHour) {
        // check if the period [start;stop] is crossing the period [lowerBoundary;upperBoundary]
        if(stopTime.isAfter(lowerBoundary) && startTime.isBefore(upperBoundary)){
            // truncate start time to not be before lowerBoundary
            LocalTime actualStart = (startTime.isBefore(lowerBoundary))?lowerBoundary:startTime;

            // symetrically, truncate stop to not be after upperBounday
            LocalTime actualStop = (stopTime.isAfter(upperBoundary))?upperBoundary:stopTime;

            // now that we have the proper start and stop of the period, let's compute the price of it
            return compute(actualStart, actualStop, priceRatePerHour);
        }else{
            return 0;
        }
    }

    private static int compute(LocalTime startTime, LocalTime stopTime, int pricePerHour) {
        Duration duration = Duration.between(startTime, stopTime);
        int hours = (int) duration.toHours();
        long minutes = duration.toMinutes();
        if(minutes % 60 > 0L){
            // hour started, increasing the number
            hours++;
        }
        int result = hours * pricePerHour;
        System.out.println(String.format("%d hours at %d price/h => %d", hours, pricePerHour, result));
        return result;
    }
}

Went directly for the calculation of the final price. Updating to store total number of day hours and night hours should be much of a challenge

Result of my exemple:

checking between 2019-01-01T18:50 and 2019-01-01T20:49
2 hours at 5 price/h => 10
10
******
checking between 2019-01-01T18:50 and 2019-01-01T20:50
2 hours at 5 price/h => 10
10
******
checking between 2019-01-01T18:50 and 2019-01-01T20:51
3 hours at 5 price/h => 15
15
******
checking between 2019-01-01T18:50 and 2019-01-02T20:51
checking between 2019-01-01T18:50 and 2019-01-01T23:59:59.999999999
5 hours at 5 price/h => 25
checking between 2019-01-02T00:00 and 2019-01-02T20:51
5 hours at 5 price/h => 25
11 hours at 10 price/h => 110
3 hours at 5 price/h => 15
175
******

Might need more tests to ensure it's good in all conditions but should be a usefull starting point for you

2
Tim Hansen On

First, you need to parse the Integers differently. Your method is dangerous, e.g. loses information. Plus you need to make the code failsafe in case somebody tries to put in values that won't work. Refer to this question: How do I convert a String to an int in Java?

Apart from that, working with just minutes and hours is always difficult. I suggest using the absolute times in milliseconds which makes it far easier to do calculations. Refer to this question: Difference in hours of two Calendar objects

1
Basil Bourque On

Time Zone

Your code and the other Answers fail to account for time zone anomalies. If you are tracking actual moments, when people actually parked, as opposed to theoretical 24-hour long days, then you must account for anomalies such as Daylight Saving Time (DST). Politicians around the world have shown a penchant for redefining the time zone(s) in their jurisdiction. So days can be any length, such as 25-hours long, 23-hours, 23.5 hours, 24.25, or others.

A time zone is a history of the past, present, and future changes to the offset-from-UTC used by the people of a particular region.

The LocalDateTime class is exactly the wrong class to use for this purpose. That class intentionally has no concept of time zone or offset-from-UTC. You can use it as a building block piece in your code, but it must be assigned a ZoneId to determine an actual moment via the ZonedDateTime class.

ZoneId

Specify your time zone.

If no time zone is specified, the JVM implicitly applies its current default time zone. That default may change at any moment during runtime(!), so your results may vary. Better to specify your desired/expected time zone explicitly as an argument. If critical, confirm the zone with your user.

Specify a proper time zone name in the format of Continent/Region, such as America/Montreal, Africa/Casablanca, or Pacific/Auckland. Never use the 2-4 letter abbreviation such as EST or IST as they are not true time zones, not standardized, and not even unique(!).

ZoneId z = ZoneId.of( "America/Montreal" ) ;  

If you want to use the JVM’s current default time zone, ask for it and pass as an argument. If omitted, the code becomes ambiguous to read in that we do not know for certain if you intended to use the default or if you, like so many programmers, were unaware of the issue.

ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

Assemble date, time, & zone to determine a moment

Assemble your date and time-of-day.

LocalDate startDate = LocalDate.of( 2019 , 1 , 23 ) ;  // 23rd of January in 2019.
LocalTime startTime = LocalTime.of( 18 , 50 ) ;  // 6:50 PM.
ZonedDateTime startMoment = ZonedDateTime.of( startDate , startTime , z ) ;

LocalDate stopDate = LocalDate.of( 2019 , 1 , 23 ) ;  // 23rd of January in 2019.
LocalTime stopTime = LocalTime.of( 20 , 50 ) ;  // Two hours later, exactly — maybe! Depends on any anomalies at that time in that zone.
ZonedDateTime stopMoment = ZonedDateTime.of( stopDate , stopTime , z ) ;

➥ Note that in this example, we may have a span of time of exactly 2 hours, but maybe not. It might be 3 hours or some other length of time, depending on anomalies scheduled for that date at that time in that zone.

Elapsed time

To calculate elapsed time it terms of days (24-hour chunks of time, unrelated to the calendar), hours, minutes, and seconds, use Duration. (For year-months-days, use Period.)

Duration d = Duration.between( startMoment , stopMoment ) ;

Interrogate for entire span-of-time in terms of whole hours.

long hours = d.toHours() ;  // Entire duration as count of whole hours.

Half-Open

In the included pastebin, there is starting time 18:50 and stop time 20:49. If we input this data, the output should be 2 started day hours. Now if the minute is the same, it does not count as a started hour. But if we change the input to 20:51, then it started an another hour, so the DayHoursTotal should be equal to 3.

This approach is know as Half-Open, when the beginning is inclusive, while the ending is exclusive. This is commonly used in date-time handling. The Duration and Period classes apply this approach.

But be careful about matching the minute number alone. Your date-time objects might be holding seconds and/or a fractional second, which would throw off your algorithm. As a habit, truncate your date-time objects explicitly if there is any possibility of smaller granularity than you want. For example, ZonedDateTime.truncatedTo.

Rate changes

Obviously, rate changes complicate matters. The other Answers seem to have covered this, so I'll not repeat. But I can add a big tip: See the ThreeTen-Extra for its classes Interval and LocalDateRange that may be of help to you. They include handy comparison methods for overlaps, contains, abuts, and so on.