Berechnen einer Dauer - Nur Arbeitstage

alan79

Mitglied
Hallo Forum

Ich muss in Java Durations errechnen basierend auf einem definierten Kalender.
In diesem Kalender sind die Arbeitsstunden definiert z.B.
Montag 08:00 - 12:00
Montag 13 :00 - 17:30
Dienstag 08:00 - 12:00
Dienstag 13 :00 - 17:30
Mittwoch 08:00 - 12:00
Mittwoch 13 :00 - 17:30
Donnerstag 08:00 - 12:00
Donnerstag 13 :00 - 17:30
Freitag 08:00 - 16:00
Samstag - Kein Arbeitstag
Sonntag - Kein Arbeitstag

Nun hab ich einen Startzeitpunkt und Endzeitpunkt z.B.

Start - 01.05.2009 13:00
Ende - 12.06.2009 17:00
Wie lange ist die dauer in Minuten basierend auf dem definierten Kalender d.h. es werden nur die definierten Arbeitsstunden berechnet. Zusätzlich gibt es noch definierte Feiertage welche ebenso nicht mitgerechnet werden dürfen.

Ich hab mir gedacht, dass ich womöglich nicht der erste bin der mit dieser Aufgabe konfrontiert ist. Gibt es irgend ein Framework in Java welches einen dabei unterstützt?

Vielen Dank für einen Tipp.
Grüsse
Alan
 
Hallo Zeja

Vielen Dank für deine Antwort. Ich hab mir mal den user guide angeschaut.
Bin aber noch einwenig unschlüssig ob und wie ich damit das Ganze abdecken kann.

Hast Du mir womöglich einen Tipp dazu? Hast du womöglich Beispiel Code wo Du schon mal was ähnliches gemacht hast?

Vielen Dank für Deine Hilfe.
Grüsse
Alan
 
Hallo Alan ich würde dir auch eher zu Joda Time raten, obwohl ich mir nicht sicher bin, ob dieser alle deine Wünsche erfüllen kann.
Allerdings mus man auch darauf achten, dass es Open Source ist und den Lizensbestimmungen von Apache unterliegt.

Aber sonst kannst du es vielleicht auch etwa so machen:
Obwohl ich es mir schwierig vorstelle, weil du einmal Zeitangaben beinhaltest (Start und Ende) und einmal "ganze Tage" (Feiertage).

1.
Du baust dir ein SimpleDateFormat mit dem zu deine Zeitangaben in ein Date umwandeln kannst .

Java:
SimpleDateFormat myDateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");

2.
Du baust dir verschiedene Date, z.B. Beginn, Ende, Feiertage

Java:
Date myDate = myDateFormat.parse("01.05.2009 13:00");

3.
Du legst dir einen neuen GregorianCalendar an und setzt die Zeit auf die Startzeit.

Java:
GregorianCalendar myCalendar = new GregorianCalendar();
myCalendar.setTime(myDate);

4.
Du zählst in einer Schleife den Tag immer um 1 hoch und überprüfst dort, welcher Wochentag der momentane Tag ist (vergleich mit Feiertagen) und erhöhst dementsprechend die Arbeitszeit. Die Schleife hat die Endzeit als Abbruchbedingung.
Außer dem ersten und dem letzten Tag sind das ja immer kontsante Werte. Die Arbeitszeit vom ersten und letzten Tag müsstest du halt seperat überprüfen und berechnen.

Java:
while(/*TODO: Abbruchbedingung*/)
...
// erhöhe Tag um 1
myCalendar.add(Calendar.DAY_OF_WEEK,1);
...
//TODO: Auf Feiertage vergleichen
...
// ***Montag ***
if(myCalendar.get(Calendar.DAY_OF_WEEK)==2)  {
// TODO: Arbeitszeit aufsummieren
}
...

Für die Vergleiche würde ich denke ich mal den Millisekunden Bereich wählen.

Java:
// ...vom momentan Tag
myCalendar.getTimeInMillis();
// ...von Date
myDate.getTime();
 
Zuletzt bearbeitet:
2 Dinge: 1. JodaTime is the way to go... das native DateTime API is borked bis zum geht nicht mehr. Inkonstistent, schlecht zu programmiern usw. usw.

2. Das ist doch eher eine algorithmische Frage. es sollte beim Aufrechnen doch einfach helfen Zeiteinträge der wohldefinierten Tage, die nicht zu beachten sind herauszufiltern. Alles was Samstag, Sonntag oder Feiertag ist überspringst du einfach. IMHO bietet sich da eine FilterCallback an, was entscheidet, welche zeiteinträge zu berücksichtigen sind. Die werden dann beim Aufrechnen gerufen.

Gruß
Ollie
 
Vielen Dank für eure Antworten.

Ich denke ich habe nun eine gute Lösung gefunden, welche auf Joda Time basiert.
Zusätzlich hab ich noch eine Library gefunden (ObjectLabs Kit) welche auf JodaTime basiert. Diese bietet unterstützung zu Business Days und Holidays.
Diese Library findet man hier:
http://objectlabkit.sourceforge.net/

Falls es jemand interessiert.. hier ist der Beispiel Code:

Application.java:
Code:
package com.antavis.calcDuration;

import org.joda.time.LocalDate;
import org.joda.time.LocalTime;

public class Application {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Calculator calculator = new Calculator();
		LocalDate start = new LocalDate("2009-07-25");
		LocalDate end = new LocalDate("2009-08-04");
		
		int result = calculator.calculate(start,new LocalTime(11,0), end,new LocalTime(17,0));
		System.out.println("Result in seconds: "+result);
		System.out.println("Result in hours: "+result/3600);
		System.out.println("Result in days: "+result/3600/24);

	}

}

BusinessDay.java
Code:
package com.antavis.calcDuration;


import org.joda.time.LocalTime;

public class BusinessDay {
	
	private LocalTime startMorning;
	private LocalTime endMorning;
	private LocalTime startNoon;
	private LocalTime endNoon;
	private Boolean fullDay=false;
	
	public int getSecondsOfDay(){
		int morning=0;
		int noon=0;
		int total=0;
		if (fullDay==true){
			total=86400;
		}
		if (fullDay==false){
			morning=endMorning.getMillisOfDay() - startMorning.getMillisOfDay();
			noon=endNoon.getMillisOfDay() - startNoon.getMillisOfDay();
			total = (morning + noon)/1000;
			
		}
		return total;
	}
	
	public int getSecondsOfDay(LocalTime from, LocalTime to){
		int morning=0;
		int noon=0;
		int total=0;
		if (to.isBefore(from)){
			return 0;
		}
		if (fullDay==true){
			total=to.getMillisOfDay() - from.getMillisOfDay();
			total = total /1000;
		}
		if (fullDay==false){
			if (from.isAfter(endNoon)){
				return 0;
			}
			if (from.isBefore(startMorning)&& to.isBefore(startMorning)){
				return 0;
			}
			if (from.isBefore(startMorning)){
				from=startMorning;
			}
			if (from.isAfter(endMorning)&& (from.isBefore(startNoon)||from.isEqual(startNoon))){
				from=startNoon;
			}
			if (to.isAfter(endMorning)&& (to.isBefore(startNoon)||to.isEqual(startNoon))){
				to=endMorning;
			}
			if (to.isAfter(endNoon)){
				to=endNoon;
			}
			if (to.isBefore(from)){
				return 0;
			}
			
			// In between morning
			if (from.isBefore(endMorning) && (to.isBefore(endMorning)||to.isEqual(endMorning))){
				total=(to.getMillisOfDay() - from.getMillisOfDay())/1000;
				
			}
			// In between Noon
			
			if ((from.isAfter(startNoon)||from.isEqual(startNoon)) && (to.isBefore(endNoon)||to.isEqual(endNoon))){
				total=(to.getMillisOfDay() - from.getMillisOfDay())/1000;
				
			}
			// Spread over day
			if (from.isBefore(endMorning) && to.isAfter(startNoon)){
				morning=endMorning.getMillisOfDay()-from.getMillisOfDay();
				noon=to.getMillisOfDay() - startNoon.getMillisOfDay();
				total=(morning+noon)/1000;
				
			}
			
			
			
		}
		return total;
	}
	
	public LocalTime getEndNoon() {
		return endNoon;
	}

	public void setEndNoon(LocalTime endNoon) {
		this.endNoon = endNoon;
	}

	public LocalTime getStartMorning() {
		return startMorning;
	}
	public void setStartMorning(LocalTime startMorning) {
		this.startMorning = startMorning;
	}
	public LocalTime getEndMorning() {
		return endMorning;
	}
	public void setEndMorning(LocalTime endMorning) {
		this.endMorning = endMorning;
	}
	public LocalTime getStartNoon() {
		return startNoon;
	}
	public void setStartNoon(LocalTime startNoon) {
		this.startNoon = startNoon;
	}
	
	public Boolean getFullDay() {
		return fullDay;
	}
	public void setFullDay(Boolean fullDay) {
		this.fullDay = fullDay;
	}
	
}

Calculator.java
Code:
package com.antavis.calcDuration;

import java.util.HashSet;
import java.util.Set;

import net.objectlab.kit.datecalc.common.DateCalculator;
import net.objectlab.kit.datecalc.common.DefaultHolidayCalendar;
import net.objectlab.kit.datecalc.common.HolidayCalendar;
import net.objectlab.kit.datecalc.common.HolidayHandlerType;
import net.objectlab.kit.datecalc.joda.LocalDateKitCalculatorsFactory;

import org.joda.time.LocalDate;
import org.joda.time.LocalTime;

public class Calculator {
	
	final Set<LocalDate> holidays = new HashSet<LocalDate>();
	final HolidayCalendar<LocalDate> calendar;
	private BusinessDay mon = new BusinessDay();
	private BusinessDay tue = new BusinessDay();
	private BusinessDay wed = new BusinessDay();
	private BusinessDay thu = new BusinessDay();
	private BusinessDay fri = new BusinessDay();
	
	
	public Calculator(){
		holidays.add(new LocalDate("2009-05-01"));
		holidays.add(new LocalDate("2009-08-01"));
	
		holidays.add(new LocalDate("2009-12-25"));
		holidays.add(new LocalDate("2009-12-26"));
		holidays.add(new LocalDate("2010-01-01"));
		//... keep adding all holidays for the calendar duration

		// create the HolidayCalendar
		calendar = new DefaultHolidayCalendar<LocalDate>(holidays, new LocalDate("2009-01-01"), new LocalDate("2025-12-31"));
		  
		// register the holiday calendar 
		LocalDateKitCalculatorsFactory.getDefaultInstance().registerHolidays("CH", calendar);
		
		//Define Monday
		
		mon.setFullDay(true);
		
		tue.setStartMorning(new LocalTime(8,30));
		tue.setEndMorning(new LocalTime(12,30));
		tue.setStartNoon(new LocalTime(13,0));
		tue.setEndNoon(new LocalTime(17,30));
		wed.setFullDay(true);
		thu.setFullDay(true);
		fri.setFullDay(true);
		
		

	

	}
	
	
	// Returns duration in seconds
	public Integer calculate(LocalDate start, LocalTime startTime, LocalDate end, LocalTime endTime){
		
		DateCalculator<LocalDate> cal = LocalDateKitCalculatorsFactory.getDefaultInstance().getDateCalculator("CH", HolidayHandlerType.FORWARD);
		//Seconds
		int total=0;
		cal.setStartDate(start); // this also sets the current business date.
		
		while (cal.getCurrentBusinessDate().isBefore(end)||cal.getCurrentBusinessDate().isEqual(end)){
			
			int subTotal=0;
			LocalDate current = cal.getCurrentBusinessDate();
			if (current.isEqual(start)){
				if (current.getDayOfWeek()==1){
					subTotal=mon.getSecondsOfDay(startTime,new LocalTime(24,0));
				}
				if (current.getDayOfWeek()==2){
					subTotal=tue.getSecondsOfDay(startTime,new LocalTime(24,0));
				}
				if (current.getDayOfWeek()==3){
					subTotal=wed.getSecondsOfDay(startTime,new LocalTime(24,0));
				}
				if (current.getDayOfWeek()==4){
					subTotal=thu.getSecondsOfDay(startTime,new LocalTime(24,0));
				}
				if (current.getDayOfWeek()==5){
					subTotal=fri.getSecondsOfDay(startTime,new LocalTime(24,0));
				}
			}
			
			if (current.isEqual(end)){
				if (current.getDayOfWeek()==1){
					subTotal=mon.getSecondsOfDay(new LocalTime(0,0),endTime);
				}
				if (current.getDayOfWeek()==2){
					subTotal=tue.getSecondsOfDay(new LocalTime(0,0),endTime);
				}
				if (current.getDayOfWeek()==3){
					subTotal=wed.getSecondsOfDay(new LocalTime(0,0),endTime);
				}
				if (current.getDayOfWeek()==4){
					subTotal=thu.getSecondsOfDay(new LocalTime(0,0),endTime);
				}
				if (current.getDayOfWeek()==5){
					subTotal=fri.getSecondsOfDay(new LocalTime(0,0),endTime);
				}
			}
			if (!current.isEqual(start) && !current.isEqual(end)) {
				if (current.getDayOfWeek()==1){
					subTotal=mon.getSecondsOfDay();
				}
				if (current.getDayOfWeek()==2){
					subTotal=tue.getSecondsOfDay();
				}
				if (current.getDayOfWeek()==3){
					subTotal=wed.getSecondsOfDay();
				}
				if (current.getDayOfWeek()==4){
					subTotal=thu.getSecondsOfDay();
				}
				if (current.getDayOfWeek()==5){
					subTotal=fri.getSecondsOfDay();
				}
			}
			
			
			System.out.println("Processing day: "+current.toString()+", represented by int: "+current.getDayOfWeek()+", total seconds: "+subTotal);
			total=total+subTotal;
			cal.moveByDays(1);
		}
	
		
		return total;
		
	}

}

PS: Die Berechnungen sind noch nicht intensiv getestet. Ich übernehme also kein Haftung für falsche Ergebnisse! ;-) Aber auf den ersten Blick schauts gut aus.

Grüsse
Alan
 
Danke für das super Beispiel. Genau so etwas habe ich gesucht, ok ohne Mittagspause, aber die kann man ja auf 0 setzen :)

Ich habe das Beispiel nur etwas abgeändert so dass man die Feiertage nicht direkt eintragen muss, sondern sich die von dem Jollyday Framework geben lassen kann.

Calculator.java
Code:
...
public Calculator(){
        
     // Urlaubstage für Sachsen holen
        HolidayManager manager = HolidayManager
                .getInstance(de.jollyday.HolidayCalendar.GERMANY); 
        Set<Holiday> holidays2 = manager.getHolidays(2013, "sn");
        for (Holiday h : holidays2)
            holidays.add(new LocalDate(h.getDate()));


 
        // create the HolidayCalendar
        calendar = new DefaultHolidayCalendar<LocalDate>(holidays, new LocalDate("2013-01-01"), new LocalDate("2013-12-31"));
          
        // register the holiday calendar 
        LocalDateKitCalculatorsFactory.getDefaultInstance().registerHolidays("DE", calendar);

...
 
Bei der Berechnung von Dauer mit Java Date/Time muss man tierisch aufpassen, dass keine Zeitzonen-Korrekturen vorgenommen werden, ansonsten stimmt die Dauer nicht!
 
Zuletzt bearbeitet:
Ich habe in der Berechnung einen Fehler gefunden. Wenn das Start und Enddatum am gleichen Tag ist, so wird oben die Startzeit auf die Anfangszeit des Werktages gelegt.

Bei Calculator.java habe ich daher folgendes geändert:
Code:
"if (current.isEqual(end)){" --> "if (current.isEqual(end)&&start.isBefore(end)){"
und einen weiteren Block darunter gesetzt wo geprüft wird:
Code:
"if (current.isEqual(end)&&start.isEqual(end)){"
und darin die Zeilen:
Code:
"subTotal=XXX.getSecondsOfDay(new LocalTime(0,0),endTime);"
in
Code:
"subTotal=XXX.getSecondsOfDay(start,endTime);"
abgeändert.

Außerdem akzeptiert er bei mir nicht
Code:
(new LocalTime(24,0)
und musste dafür
Code:
(new LocalTime(23,59,59)
eingeben.
 

Neue Beiträge

Zurück