2008-09-26 4 views
6

Gegeben zwei Datetimes. Wie berechnet man am besten die Anzahl der Arbeitsstunden? In Anbetracht der Arbeitszeiten sind Mo 8 - 5.30 und Di-Fr 8.30 - 5.30, und dass möglicherweise jeder Tag ein Feiertag sein könnte.Berechnen der verstrichenen Arbeitsstunden zwischen 2 Datetime

Dies ist mein Aufwand, scheint scheußlich ineffizient, aber in Bezug auf die Anzahl der Iterationen und dass die IsWorkingDay-Methode die DB trifft, um zu sehen, ob diese Datetime ein Feiertag ist.

Kann jemand irgendwelche Optimierungen oder Alternativen vorschlagen.

public decimal ElapsedWorkingHours(DateTime start, DateTime finish) 
     { 

      decimal counter = 0; 

      while (start.CompareTo(finish) <= 0) 
      { 
       if (IsWorkingDay(start) && IsOfficeHours(start)) 
       { 
        start = start.AddMinutes(1); 
        counter++; 
       } 
       else 
       { 
        start = start.AddMinutes(1); 
       } 
      } 

      decimal hours; 

      if (counter != 0) 
      { 
       hours = counter/60; 
      } 

      return hours; 
     } 
+0

Wie viele Feiertage haben Sie? ; P – leppie

Antwort

1

vor allem die IsWorkingDay Methode unter Berücksichtigung trifft die DB zu sehen, ob dieser Tag ein Feiertag ist

Wenn das Problem die Anzahl der Abfragen, anstatt die Datenmenge ist, fragen Sie die Arbeits Tagesdaten aus der Datenbank für den gesamten Tagesbereich, die Sie am Anfang benötigen, statt in jeder Schleifeniteration abzufragen.

+0

Sicher bekomme ich was du sagst konzeptionell. Aber es ist das Problem, sauber zu implementieren. Und ich denke, selbst wenn Sie die Fragen missachten, ist mein Ansatz immer noch schlecht. – Dan

+0

Statt IsWorkingDay(), das einen Tag akzeptiert, implementieren Sie eine HolidayCount() -Methode, die einen Datumsbereich akzeptiert und die Anzahl der Feiertage im Bereich zurückgibt. –

+0

Wenn etwas, das die Lösung weniger effizient code weise macht, wie ich inkrementell über mehrere Tage zählen würde. – Dan

3

Bevor Sie mit der Optimierung beginnen, stellen Sie sich zwei Fragen.

a) Funktioniert es?

b) Ist es zu langsam?

Nur wenn die Antwort auf beide Fragen "Ja" ist, können Sie mit der Optimierung beginnen.

Abgesehen von diesen

  • Sie brauchen nur über Minuten und Stunden auf dem Starttag und Endtag zu kümmern. Intervenierenden Tage wird offensichtlich eine volle 9/9,5 Stunden, es sei denn, sie Feiertage oder Wochenenden sind
  • Keine Notwendigkeit, einen Tag am Wochenende zu überprüfen, um zu sehen, ob es ein Feiertag
ist

hier, wie ich tun würde es

// Normalise start and end  
while start.day is weekend or holiday, start.day++, start.time = 0.00am 
    if start.day is monday, 
     start.time = max(start.time, 8am) 
    else 
     start.time = max(start.time, 8.30am) 
while end.day is weekend or holiday, end.day--, end.time = 11.59pm 
end.time = min(end.time, 5.30pm) 

// Now we've normalised, is there any time left?  
if start > end 
    return 0 

// Calculate time in first day  
timediff = 5.30pm - start.time 
day = start.day + 1 
// Add time on all intervening days 
while(day < end.day) 
    // returns 9 or 9.30hrs or 0 as appropriate, could be optimised to grab all records 
    // from the database in 1 or 2 hits, by counting all intervening mondays, and all 
    // intervening tue-fris (non-holidays) 
    timediff += duration(day) 

// Add time on last day 
timediff += end.time - 08.30am 
if end.day is Monday then 
    timediff += end.time - 08.00am 
else 
    timediff += end.time - 08.30am 

return timediff 

Sie könnte so etwas wie SELECT COUNT (TAG) aus dem Urlaub machen, wo Urlaub zwischen @Start uND @end GROUP BY DAY

die Zahl der Feiertage am Montag, Dienstag, Mittwoch und so weiter fallen zu zählen. Wahrscheinlich eine Möglichkeit, SQL nur montags und nicht montags zu zählen, obwohl mir im Moment nichts einfällt.

+0

Ja und ja, das ist irgendwie meine Frage zu vermeiden. – Dan

+1

es hängt davon ab, was Sie tun. Wenn es * schnell genug * funktioniert, dann hat es keinen Sinn, es weiter zu optimieren, zumindest nicht aus wirtschaftlicher Sicht. –

+0

@CynicalTyler das ist, warum ich diese Frage bitte, schlagen Sie einen besseren Weg vor, sonst verschwenden Sie Ihre Zeit und meins. – Dan

1

Werfen Sie einen Blick auf die TimeSpan-Klasse. Das gibt Ihnen die Stunden zwischen 2 mal.

Ein einzelner DB-Anruf kann auch die Feiertage zwischen Ihren zwei Zeiten erhalten; etwas entlang der Linien von:

SELECT COUNT(*) FROM HOLIDAY WHERE HOLIDAY BETWEEN @Start AND @End 

Multiplizieren Sie das mit 8 und subtrahieren sie von Ihren gesamten Stunden zählen.

-Ian

EDIT: Als Antwort unten an, wenn Sie Aufenthalt sind nicht eine konstante Anzahl von Stunden.Sie können eine HolidayStart und eine HolidayEnd Zeit in Ihrer DB behalten und sie einfach vom Anruf zur DB auch zurückbringen. Zählen Sie eine Stunde, die Ihrer Methode für die Hauptroutine entspricht.

+0

1. Diese Abfrage hilft nicht bei der Implementierung 2. Nicht alle Tage sind 8 lang und Start und Ende könnte in der Mitte des Tages sein – Dan

+0

In Ians Verteidigung, in Bezug auf 2, Ihre Frage sagt "zwei Daten gegeben" nicht zwei Datumzeiten. – Powerlord

+0

@Bemrose danke für die Korrektur, iv hat es geändert. – Dan

0

Aufbauend darauf, was @OregonGhost sagte, anstatt eine IsWorkingDay() -Funktion zu verwenden, akzeptiert einen Tag und gibt einen booleschen Wert zurück, hat eine HolidayCount() -Funktion, die einen Bereich akzeptiert und gibt eine Ganzzahl mit der Anzahl der Feiertage im Bereich zurück . Der Trick hier ist, wenn Sie mit einem partiellen Datum für Ihre Grenze Anfang und Ende Tage beschäftigen, müssen Sie noch feststellen, ob diese Daten selbst Ferien sind. Aber selbst dann könnten Sie die neue Methode verwenden, um sicherzustellen, dass Sie höchstens drei Anrufe an die DB benötigt.

0

versuchen, etwas in dieser Richtung:

TimeSpan = TimeSpan Between Date1 And Date2 
cntDays = TimeSpan.Days 
cntNumberMondays = Iterate Between Date1 And Date2 Counting Mondays 
cntdays = cntdays - cntnumbermondays 
NumHolidays = DBCall To Get # Holidays BETWEEN Date1 AND Date2 
Cntdays = cntdays - numholidays 
numberhours = ((decimal)cntdays * NumberHoursInWorkingDay)+((decimal)cntNumberMondays * NumberHoursInMondayWorkDay) 
+0

Feiertage sind montags nicht berücksichtigt. –

+0

Oder Bruchteile von Tagen. Oder wenn die Daten außerhalb der Bürozeiten liegen – Dan

1

Es gibt auch die rekursive Lösung. Nicht unbedingt effizient, aber eine Menge Spaß:

public decimal ElapseddWorkingHours(DateTime start, DateTime finish) 
{ 
    if (start.Date == finish.Date) 
     return (finish - start).TotalHours; 

    if (IsWorkingDay(start.Date)) 
     return ElapsedWorkingHours(start, new DateTime(start.Year, start.Month, start.Day, 17, 30, 0)) 
      + ElapsedWorkingHours(start.Date.AddDays(1).AddHours(DateStartTime(start.Date.AddDays(1)), finish); 
    else 
     return ElapsedWorkingHours(start.Date.AddDays(1), finish); 
} 
0

Der effizienteste Weg, dies zu tun ist, um die Gesamtzeitdifferenz zu berechnen, dann ziehen Sie die Zeit, die ein Wochenende oder einen Feiertag ist. Es gibt einige Randfälle, die Sie berücksichtigen sollten, aber Sie können dies vereinfachen, indem Sie die ersten und letzten Tage des Bereichs berechnen und separat berechnen.

Die von Ian Jacobs vorgeschlagene COUNT (*) Methode scheint eine gute Möglichkeit zu sein, die Feiertage zu zählen. Was auch immer Sie verwenden, es wird nur die ganzen Tage behandelt, Sie müssen das Start- und Enddatum separat abdecken.

Das Zählen der Wochenendtage ist einfach; Wenn Sie eine Funktion Wochentag (Datum), die bis 6 für Sonntag 0 Montag gibt, sieht es wie folgt aus:

saturdays = ((finish - start) + Weekday(start) + 2)/7; 
sundays = ((finish - start) + Weekday(start) + 1)/7; 

Anmerkung: (Ende - Start) nicht zu wörtlich genommen werden, ersetzen Sie es mit etwas das berechnet die Zeitspanne in Tagen.

0

Verwenden Sie die @ Ian-Abfrage, um zwischen den Daten zu prüfen, welche Tage keine Arbeitstage sind. Dann machen Sie etwas Mathe, um herauszufinden, ob Ihre Startzeit oder Endzeit auf einen arbeitsfreien Tag fällt und den Unterschied subtrahiert.

Also wenn Start ist Samstag Mittag, und Ende ist Montag Mittag, die Abfrage sollte Ihnen 2 Tage zurückgeben, aus denen Sie 48 Stunden (2 x 24) berechnen. Wenn Ihre Abfrage zu IsWorkingDay (start) false zurückgibt, subtrahieren Sie die Zeit von Beginn bis Mitternacht von 24, was Ihnen 12 Stunden oder 36 Stunden Gesamtarbeitszeit verschafft.

Jetzt, wenn Ihre Bürozeiten für jeden Tag gleich sind, tun Sie eine ähnliche Sache. Wenn Ihre Bürozeiten etwas verstreut sind, werden Sie mehr Probleme haben.

Im Idealfall machen Sie eine einzige Abfrage in der Datenbank, die Ihnen alle Bürozeiten zwischen den beiden Zeiten (oder sogar Daten) gibt. Dann machen Sie die Mathematik lokal aus diesem Satz.

-1
Dim totalMinutes As Integer = 0 

For minute As Integer = 0 To DateDiff(DateInterval.Minute, contextInParameter1, contextInParameter2) 
    Dim d As Date = contextInParameter1.AddMinutes(minute) 
    If d.DayOfWeek <= DayOfWeek.Friday AndAlso _ 
     d.DayOfWeek >= DayOfWeek.Monday AndAlso _ 
     d.Hour >= 8 AndAlso _ 
     d.Hour <= 17 Then 
     totalMinutes += 1 
    Else 
     Dim test = "" 
    End If 
Next minute 

Dim totalHours = totalMinutes/60 

Stück Kuchen!

Prost!