4

My Java FX app handles hours worked. I have work start and end time in 2 date fields. I succeeded in calculating the differences between 2 datesTime; but now how could I check if the result is in a night or day range???? The day begin at 6 and ends at 22h. For example someone who worked between 3Am till 11Pm. Here is below how I did to have the total number of hours worked.

public void CalculNbreJourTravaille() {
    SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyy HH:mm");

    try {
        Date ddtt = format.parse(ddt.getText());
        Date dftt = format.parse(dft.getText());
        long diff = dftt.getTime() - ddtt.getTime();
        long diffhours = diff / (60*60*1000)%24;
        long diffdays = diff/(24*60*60*1000);
        long total = diffhours + (diffdays*24);
        result.setText(total + " Hours");   
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

We have workers who can work beyond 10PM, and the pay would not be the same. If they work after 10pm, they will have a special pay. We pay at the end of the work. They could would work only 10 days or more.

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
DJIBFX
  • 43
  • 6
  • You should tag this with the language you are using. I'm guessing java? – jordanm Apr 30 '18 at 05:15
  • Yes you're right. It is java. – DJIBFX Apr 30 '18 at 05:21
  • 1
    You should also ask a question. – shmosel Apr 30 '18 at 05:23
  • Sorry I am new here. I said what I am waiting as response saying I want to know if the result of 2 dates is in a range of a day or night. – DJIBFX Apr 30 '18 at 05:26
  • 1
    Try using `LocalDate`. – SedJ601 Apr 30 '18 at 05:42
  • further details Sedrick – DJIBFX Apr 30 '18 at 05:46
  • 1
    Converting a difference to a number of days by dividing by `(24*60*60*1000)` can fail when daylight savings starts or ends. – Dawood ibn Kareem Apr 30 '18 at 05:48
  • @DJIBFX - Please share more details. Better if you can share an example with the kind of scenarios you are trying to solve. – Avinash Sagar Apr 30 '18 at 05:55
  • Thanks all. Yes dear Avinash. We have workers who can work beyond 10PM and price would not be the same. If they work after 10pm they will have a special price. We pay at the end of the work. Could work only 10 days or more. – DJIBFX Apr 30 '18 at 06:03
  • 2
    Check out Java 8's new date & time API: https://www.tutorialspoint.com/java8/java8_datetime_api.htm – David Brossard Apr 30 '18 at 06:13
  • 1
    If someone works from 3 until 23, that’s 3 hours in the night, then 16 hours in the day and finally 1 hour in the night. Do you need all of this information, or do you only need to know that this person worked both in the day and in the night and a total of 20 hours? Your requirements aren’t clear, sorry. – Ole V.V. Apr 30 '18 at 07:51
  • Exactly I need. because day work has a price and same for night work. We do an additon of day work * price and day night * price. – DJIBFX Apr 30 '18 at 08:30
  • Do you need to take summer time (DST) into account, and if so, how? – Ole V.V. Apr 30 '18 at 11:23
  • Dear Ole we won't use summer time. – DJIBFX Apr 30 '18 at 15:15
  • 1
    @DJIBFX Do you mean you happen to running this code in a time zone that does not currently adopt Daylight Saving Time (DST)? Or are you truly intentionally ignoring real-world anomalies such as DST? If the first rather than the second, I would advise always coding for zone-sensitive calculations as discussed in [Answer by Ole V.V.](https://stackoverflow.com/a/50106225/642706). The reason is that you never know when your particular zone may adopt DST or may encounter other changes to your zone made so often by politicians. . Better to be safe than sorry. – Basil Bourque Apr 30 '18 at 20:32
  • 1
    FYI, the troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Apr 30 '18 at 20:33
  • Dear Basil, You are right. Just that I am in somewhere we currently don't use DST. but your are right in the futur everything it's possible. And the program has to be ready for any circonstance. – DJIBFX May 02 '18 at 05:56

5 Answers5

4

You should use the new DateTimeFormatter class to give you a LocalDateTime object, which you can pull the hour from.

DateTimeFormatter format = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
LocalDateTime localDateTimeFrom = format.parse(dateFrom.getText(), LocalDateTime::from);
LocalDateTime localDateTimeTo = format.parse(dateTo.getText(), LocalDateTime::from);

int hoursFrom = localDateTimeFrom.getHour();
int hoursTo = localDateTimeTo.getHour();

boolean workedNight = hoursFrom < 6 || hoursTo > 22;
Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110
  • 1
    It seems correct one. I will check and revert. thanks – DJIBFX Apr 30 '18 at 06:10
  • If hardcoding times 6 and 22 is OK, this works. If you want to be able to configure it to, say, 5:30 and 21:45, you will need to compare to a `LocalTime` instead. And if working from 23 to 5 in the morning is a possibility, it gets a bit more complicated still. – Ole V.V. Apr 30 '18 at 08:03
  • Yes. When work is from for instance from 02AM to 09Am. When day and night work is mixed – DJIBFX Apr 30 '18 at 08:16
  • This does not accurately detect the end of the normal work day: `22:59` will be truncated to `22` and therefore will be considered part of the normal work day... – fabian Apr 30 '18 at 09:38
  • @fabian OK, sure, but OP's difficulty was around how to find the hour values from the two text fields. I've shown him/her how to do that. What logic he/she then applies to those hour values is really something for OP to work out for himself/herself. – Dawood ibn Kareem Apr 30 '18 at 09:46
2

You can check the LocalTime part of a LocalDateTime to have a simple check using isAfter and isBefore.

I will use those values for this example.

LocalDateTime start = LocalDateTime.of(2018, Month.APRIL, 30, 23, 0);
LocalDateTime end = LocalDateTime.of(2018, Month.MAY, 1, 5, 0);

Then define the limit for the night.

LocalTime startNight = LocalTime.of(22, 0);
LocalTime endNight = LocalTime.of(6, 0);

And simply use get the LocalTime of both date and check if they are in the range. You can get the value using toLocalTime.

if(start.toLocalTime().isAfter(startNight) &&
        end.toLocalTime().isBefore(endNight)){
    System.out.println("NIGHT TIME");
} else {
    System.out.println("DAY TIME");
}

NIGHT TIME

The output is valid since we start at 23:00 and end at 05:00.


Using this allow a simpler solution if you need to define a time like LocalTime.of(5,45) for 5:45

This is an example, this might need some adaptation if it is allowed to start part 22 but keep working after 6. This is just an example on how to use those methods.

AxelH
  • 14,325
  • 2
  • 25
  • 55
2

Here’s my attempt to cover all of your requirements. I wrote the code before reading that you don’t require that summer time (DST) is taken into account, so I am using ZonedDateTime to get correct hours also across summer time transitions. For the same reason I need to iterate over each day. For each date I calculate the hours worked at night time and the hours worked at day time.

If you want to make sure that summer time is not taken into account, use LocalDateTime instead of ZonedDateTime. In this case there may also be a possible performance gain in calculating the whole work days in one lump rather than one day at a time.

The code below uses 28/03/2018 03:00 and 29/03/2018 23:30 as example start and end time. Expected total hours worked are 44.5 since one day is 24 hours and there are 20.5 hours from 03:00 to 23:30. The expected day time hours are 32 since there are 16 daytime hours each of the two days. This leaves 12.5 hours as night time. And indeed the code prints

Day 32.0 hours; night 12.5 hours

The program follows. Please fill in the correct time zone where I put America/Monterey.

static ZoneId zone = ZoneId.of("America/Monterrey");
static LocalTime dayStart = LocalTime.of(6, 0);
static LocalTime dayEnd = LocalTime.of(22, 0);
static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/uuuu H:mm");

public static void main(String[] args) {

    String workStartString = "28/03/2018 03:00";
    String workEndString = "29/03/2018 23:30";
    calculateWorkingHours(workStartString, workEndString);
}

public static void calculateWorkingHours(String workStartString, String workEndString) {
    ZonedDateTime workStart
            = LocalDateTime.parse(workStartString, formatter).atZone(zone);
    ZonedDateTime workEnd
            = LocalDateTime.parse(workEndString, formatter).atZone(zone);

    if (workEnd.isBefore(workStart)) {
        throw new IllegalArgumentException("Work end must not be before work start");
    }

    LocalDate workStartDate = workStart.toLocalDate();
    LocalDate workEndDate = workEnd.toLocalDate();

    Duration workedDaytime = Duration.ZERO;
    // first calculate work at nighttime before the start date, that is, work before 06:00
    Duration workedNighttime 
            = calculateNightTime(workStartDate.minusDays(1), workStart, workEnd);

    for (LocalDate d = workStartDate; ! d.isAfter(workEndDate); d = d.plusDays(1)) {
        workedDaytime = workedDaytime.plus(calculateDayTime(d, workStart, workEnd));
        workedNighttime = workedNighttime.plus(calculateNightTime(d, workStart, workEnd));
    }

    double dayHours = workedDaytime.toMinutes() / (double) TimeUnit.HOURS.toMinutes(1);
    double nightHours = workedNighttime.toMinutes() / (double) TimeUnit.HOURS.toMinutes(1);

    System.out.println("Day " + dayHours + " hours; night " + nightHours + " hours");

}

/**
 * Calculates amount of work in daytime on d,
 * that is between 06:00 and 22:00 on d.
 * Only time that falls with in workStart to workAnd
 * and also falls within 06:00 to 22:00 on d is included.
 * 
 * @param d The date for which to calculate day work
 * @param workStart
 * @param workEnd
 * @return Amount of daytime work on the said day
 */
private static Duration calculateDayTime(LocalDate d, ZonedDateTime workStart, ZonedDateTime workEnd) {
    ZonedDateTime dayStartToday = d.atTime(dayStart).atZone(zone);
    ZonedDateTime dayEndToday = d.atTime(dayEnd).atZone(zone);
    if (workStart.isAfter(dayEndToday) || workEnd.isBefore(dayStartToday)) {
        return Duration.ZERO;
    }

    // restrict calculation to daytime on d
    if (workStart.isBefore(dayStartToday)) {
        workStart = dayStartToday;
    }
    if (workEnd.isAfter(dayEndToday)) {
        workEnd = dayEndToday;
    }

    return Duration.between(workStart, workEnd);
}

/**
 * Calculates amount of night work in the night after d,
 * that is from 22:00 on d until 06:00 the next morning.
 * 
 * @param d The date for which to calculate night work
 * @param workStart
 * @param workEnd
 * @return Amount of nighttime work in said night
 */
private static Duration calculateNightTime(LocalDate d, ZonedDateTime workStart, ZonedDateTime workEnd) {
    assert ! workEnd.isBefore(workStart);

    ZonedDateTime nightStart = d.atTime(dayEnd).atZone(zone);
    ZonedDateTime nightEnd = d.plusDays(1).atTime(dayStart).atZone(zone);

    if (workEnd.isBefore(nightStart) || workStart.isAfter(nightEnd)) {
        return Duration.ZERO;
    }

    // restrict calculation to the night after d
    if (workStart.isBefore(nightStart)) {
        workStart = nightStart;
    }
    if (workEnd.isAfter(nightEnd)) {
        workEnd = nightEnd;
    }

    return Duration.between(workStart, workEnd);
}
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
  • Dear Ole many thanks for the time you took to help me. Yes I am in African continent and specialy East Africa. So daytime and night time doesn't change either in the summer and the winter. I am going to try your code and revert you. – DJIBFX May 02 '18 at 06:20
  • Thanks a lot Ole. It works and really pleased. You are genius and really proud to be part of stackoverflow members. Good job!!!!!!! – DJIBFX May 02 '18 at 08:21
1

This is easier, if you use the java.time API. You simply need to check, if the dates differ or if the starting time not in the range from 6:00 to 22:00:

private static final LocalTime START_TIME = LocalTime.of(6, 0); // 06:00
private static final LocalTime END_TIME = LocalTime.of(22, 0); // 22:00
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
// parse from input strings
LocalDateTime start = LocalDateTime.parse(startText, FORMATTER);
LocalDateTime end = LocalDateTime.parse(endText, FORMATTER);

boolean nightTime = 
        !start.toLocalDate().equals(end.toLocalDate())
        || start.toLocalTime().isBefore(START_TIME)
        || end.toLocalTime().isAfter(END_TIME);

// todo: change output to gui
System.out.println("night time: " + nightTime);
System.out.println("duration  : " + Duration.between(start, end).toHours());
fabian
  • 80,457
  • 12
  • 86
  • 114
0

Define two formatters. One Fromatter to get date with time from edittext. And other On to get 12AM of that day. Now we need Date Objects corresponding to 6AM and 11PM of the same day. We can get those by adding that much milliseconds to the 12AM Object. These added dates can be used for comparison.

SimpleDateFormat df_zero_hours = new SimpleDateFormat("dd/MM/yyy");
SimpleDateFormat format = new SimpleDateFormat("dd/MM/yyyy HH:mm");

Date ddtt = format.parse(ddt.getText()); //Work Start Time
Date dftt = format.parse(dft.getText()); //Work End Time


Date dateStart = df_zero_hours.parse(ddt.getText()); //12AM of the day job started
Date dayStart = new Date();
    dayStart.setTime(dateStart.getTime()+6*60*60*1000); // Get 6AM of that day
Date dayEnd = new Date();
    dayEnd.setTime(dateStart.getTime()+22*60*60*1000); //Get 10PM of that day

//        Now check the worked hours. in Whatever way you need
boolean isBefore6AM = (dayStart.getTime()-ddtt.getTime())>0;
boolean isAfter10PM = (dftt.getTime()-dayEnd.getTime())>0;
AFLAH ALI
  • 451
  • 5
  • 17
  • 1
    I don't think this will work the way you expect it to. You're comparing midnight to both 6am and 10pm on the same day. This will always give the same result. – Dawood ibn Kareem Apr 30 '18 at 06:05
  • Yes I noticed too. – DJIBFX Apr 30 '18 at 06:10
  • 1
    Thank you for this code snippet, which might provide some limited, immediate help. A proper explanation would greatly improve its long-term value by showing why this is a good solution to the problem and would make it more useful to future readers with other, similar questions. Please edit your answer to add some explanation, including the assumptions you’ve made. – rollstuhlfahrer Apr 30 '18 at 07:34
  • Sorry i was just comparing the dates. The Long values should be compared to get Proper Results. – AFLAH ALI Apr 30 '18 at 08:52
  • @Dawood ibn Kareem I have corrected now. Hope it will work now. – AFLAH ALI Apr 30 '18 at 08:55
  • FYI, the troublesome old date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Apr 30 '18 at 22:01