2010-09-30 4 views
20

Je dois déterminer la durée entre deux DateTimes en minutes.C# - Durée entre deux DateTimes en minutes

Cependant, il y a une légère torsion:

  • ne comprennent pas le week-end
  • compter seulement quelques minutes qui sont 07:00-19:00. par exemple: [09/30/2010 6:39:00 PM] - [09/30/2010 7:39:00 PM] = 21 Minutes

Je suis juste un moment difficile à venir avec une manière décente de le faire et apprécierait si quelqu'un peut suggérer quelque chose.

Merci.


Edit:

J'ai fini par aller avec la solution de DTB. Un seul cas particulier doit être pris en compte: si l'heure de fin est après 19h00, comptez les minutes entre 7h00 et l'heure de fin réelle.

Voici comment je l'ai modifié:

var minutes = from day in start.DaysInRangeUntil(end) 
       where !day.IsWeekendDay() 
       let st = Helpers.Max(day.AddHours(7), start) 
       let en = (day.DayOfYear == end.DayOfYear ? 
          end : 
          Helpers.Min(day.AddHours(19), end) 
          ) 
       select (en - st).TotalMinutes; 

Encore une fois, merci pour l'aide.

+2

@ 0xA3, pas un dup de cette question, car cette question ne répond pas aux jours et heures exclus demandés ici. –

+2

@Kirk - c'est vrai. Le nombre infini de façons d'utiliser TimeSpan avec des règles d'exclusion personnalisées permet de conserver les questions SO pendant encore 10 ans. –

+0

Eh bien, les gens devraient montrer leur fonctionnement, comme ils devaient le faire à l'école. :) –

Répondre

23

Vous pouvez, bien sûr, utiliser LINQ:

DateTime a = new DateTime(2010, 10, 30, 21, 58, 29); 
DateTime b = a + new TimeSpan(12, 5, 54, 24, 623); 

var minutes = from day in a.DaysInRangeUntil(b) 
       where !day.IsWeekendDay() 
       let start = Max(day.AddHours(7), a) 
       let end = Min(day.AddHours(19), b) 
       select (end - start).TotalMinutes; 

var result = minutes.Sum(); 

// result == 6292.89 

(Note: Vous avez probablement besoin de vérifier un grand nombre de cas d'angle que je complètement ignoré.)

Méthodes d'aide:

static IEnumerable<DateTime> DaysInRangeUntil(this DateTime start, DateTime end) 
{ 
    return Enumerable.Range(0, 1 + (int)(end.Date - start.Date).TotalDays) 
        .Select(dt => start.Date.AddDays(dt)); 
} 

static bool IsWeekendDay(this DateTime dt) 
{ 
    return dt.DayOfWeek == DayOfWeek.Saturday 
     || dt.DayOfWeek == DayOfWeek.Sunday; 
} 

static DateTime Max(DateTime a, DateTime b) 
{ 
    return new DateTime(Math.Max(a.Ticks, b.Ticks)); 
} 

static DateTime Min(DateTime a, DateTime b) 
{ 
    return new DateTime(Math.Min(a.Ticks, b.Ticks)); 
} 
+2

+1, je grimpe encore un peu quand je regarde ce code mais j'applaudis l'utilisation créative de LINQ. – JaredPar

+0

Puis-je suggérer une extension 'IsWeekend'? –

+0

Tout d'abord, je voudrais dire que c'est en effet une façon très compacte de gérer ce problème. LINQ est assez lisse. Mon problème est que vous itérez tous les jours entre le début et la fin. Si c'était une période de 5 ans, vous feriez beaucoup de calculs inutiles. Ne vous méprenez pas, les ordinateurs d'aujourd'hui sont super puissants et pourraient gérer quelque chose comme ça facilement. Mais il est facile de calculer le nombre total d'heures sur une période de 7 jours => 5 * 12 = 60. Calculer la partie restante est délicat, mais ne nécessitera jamais d'itération sur plus de 7 jours. – dana

5

Prenez votre heure de départ, obtenez le nombre de minutes jusqu'à la fin de cette journée (c'est-à-dire les 19h).

Ensuite, à partir de 7h le jour suivant, comptez le nombre de jours jusqu'au dernier jour (à l'exclusion de toute heure de fin de journée).

Calculez combien de week-ends (le cas échéant) se sont écoulés. (Pour chaque week-end, réduisez le nombre de jours de 2). Faites quelques calculs simples à partir de là pour obtenir le nombre total de minutes pour le nombre de jours.

Ajouter le temps supplémentaire sur le dernier jour et les jours de départ supplémentaires.

+0

Je suis d'accord avec celui-ci - ce n'est pas un simple calcul en une seule étape, mais c'est propre, et le code devrait être assez lisible. –

+0

Je voudrais fouetter un peu de code, mais a) Cela ruine le plaisir pour l'OP, et b) Mon VS est sur le fritz. – Psytronic

4

Essayez la fonction DiffRange suivante.

public static DateTime DayStart(DateTime date) 
{ 
    return date.Date.AddHours(7); 
} 

public static DateTime DayEnd(DateTime date) 
{ 
    return date.Date.AddHours(19); 
} 

public static TimeSpan DiffSingleDay(DateTime start, DateTime end) 
{ 
    if (start.Date != end.Date) { 
     throw new ArgumentException(); 
    } 

    if (start.DayOfWeek == DayOfWeek.Saturday || start.DayOfWeek == DayOfWeek.Sunday) 
    { 
     return TimeSpan.Zero; 
    } 

    start = start >= DayStart(start) ? start : DayStart(start); 
    end = end <= DayEnd(end) ? end : DayEnd(end); 
    return end - start; 
} 

public static TimeSpan DiffRange(DateTime start, DateTime end) 
{ 
    if (start.Date == end.Date) 
    { 
     return DiffSingleDay(start, end); 
    } 

    var firstDay = DiffSingleDay(start, DayEnd(start)); 
    var lastDay = DiffSingleDay(DayStart(end), end); 

    var middle = TimeSpan.Zero; 
    var current = start.AddDays(1); 
    while (current.Date != end.Date) 
    { 
     middle = middle + DiffSingleDay(current.Date, DayEnd(current.Date)); 
     current = current.AddDays(1); 
    } 

    return firstDay + lastDay + middle; 
} 
+0

Cela produit un résultat très différent de celui de ma solution. Pouvez-vous repérer ce qui ne va pas avec mon code? – dtb

+0

@dtb, ressemble à mon problème. J'ai ajouté 14 au lieu de 19. Corrige – JaredPar

+0

Oh cool. Votre version corrigée renvoie maintenant le même résultat pour mon exemple. – dtb

1

Je suis sûr qu'il y a quelque chose qui me manque.

TimeSpan CalcBusinessTime(DateTime a, DateTime b) 
    { 
    if (a > b) 
    { 
     DateTime tmp = a; 
     a = b; 
     b = tmp; 
    } 

    if (a.TimeOfDay < new TimeSpan(7, 0, 0)) 
     a = new DateTime(a.Year, a.Month, a.Day, 7, 0, 0); 
    if (b.TimeOfDay > new TimeSpan(19, 0, 0)) 
     b = new DateTime(b.Year, b.Month, b.Day, 19, 0, 0); 

    TimeSpan sum = new TimeSpan(); 
    TimeSpan fullDay = new TimeSpan(12, 0, 0); 
    while (a < b) 
    { 
     if (a.DayOfWeek != DayOfWeek.Saturday && a.DayOfWeek != DayOfWeek.Sunday) 
     { 
      sum += (b - a < fullDay) ? b - a : fullDay; 
     } 
     a = a.AddDays(1); 
    } 

    return sum; 
    } 
+0

@dtb, Merci, espérons-le sans bug maintenant. Peut-être que je vais faire quelques tests –

1

C'était une question assez difficile. Pour une base, dans une approche simple, je l'ai écrit le code ci-dessous:

DateTime start = new DateTime(2010, 01, 01, 21, 00, 00); 
DateTime end = new DateTime(2010, 10, 01, 14, 00, 00); 

// Shift start date's hour to 7 and same for end date 
// These will be added after doing calculation: 
double startAdjustmentMinutes = (start - start.Date.AddHours(7)).TotalMinutes; 
double endAdjustmentMinutes = (end - end.Date.AddHours(7)).TotalMinutes; 

// We can do some basic 
// mathematical calculation to find weekdays count: 
// divide by 7 multiply by 5 gives complete weeks weekdays 
// and adding remainder gives the all weekdays: 
int weekdaysCount = (((int)((end.Date - start.Date).Days/7) * 5) 
      + ((end.Date - start.Date).Days % 7)); 
// so we can multiply it by minutes between 7am to 7 pm 
int minutes = weekdaysCount * (12 * 60); 

// after adding adjustment we have the result: 
int result = minutes + startAdjustmentMinutes + endAdjustmentMinutes; 

Je sais que cela semble pas beau programme, mais je ne sais pas s'il est bon de itérer jours et heures entre le début et la fin .

1
static int WorkPeriodMinuteDifference(DateTime start, DateTime end) 
{ 
    //easier to only have to work in one direction. 
    if(start > end) 
     return WorkPeriodMinuteDifference(end, start); 
    //if weekend, move to start of next Monday. 
    while((int)start.DayOfWeek % 6 == 0) 
     start = start.Add(new TimeSpan(1, 0, 0, 0)).Date; 
    while((int)end.DayOfWeek % 6 == 0) 
     end = end.Add(new TimeSpan(1, 0, 0, 0)).Date; 
    //Move up to 07:00 or down to 19:00 
    if(start.TimeOfDay.Hours < 7) 
     start = new DateTime(start.Year, start.Month, start.Day, 7, 0, 0); 
    else if(start.TimeOfDay.Hours > 19) 
     start = new DateTime(start.Year, start.Month, start.Day, 19, 0, 0); 
    if(end.TimeOfDay.Hours < 7) 
     end = new DateTime(end.Year, end.Month, end.Day, 7, 0, 0); 
    else if(end.TimeOfDay.Hours > 19) 
     end = new DateTime(end.Year, end.Month, end.Day, 19, 0, 0); 

    TimeSpan difference = end - start; 

    int weeks = difference.Days/7; 
    int weekDays = difference.Days % 7; 
    if(end.DayOfWeek < start.DayOfWeek) 
     weekDays -= 2; 

    return (weeks * 5 * 12 * 60) + (weekDays * 12 * 60) + difference.Hours * 60 + difference.Minutes 
} 
1

Ma mise en œuvre :) L'idée est de calculer rapidement les semaines au total, et de marcher le jour de la semaine restante par jour ...

public TimeSpan Compute(DateTime start, DateTime end) 
{ 
    // constant start/end times per day 
    TimeSpan sevenAM = TimeSpan.FromHours(7); 
    TimeSpan sevenPM = TimeSpan.FromHours(19); 

    if(start >= end) 
    { 
     throw new Exception("invalid date range"); 
    } 

    // total # of weeks 
    int completeWeeks = ((int)(end - start).TotalDays)/7; 

    // starting total 
    TimeSpan total = TimeSpan.FromHours(completeWeeks * 12 * 5); 

    // adjust the start date to be exactly "completeWeeks" past its original start 
    start = start.AddDays(completeWeeks * 7); 

    // walk days from the adjusted start to end (at most 7), accumulating time as we can... 
    for(
     // start at midnight 
     DateTime dt = start.Date; 

     // continue while there is time left 
     dt < end; 

     // increment 1 day at a time 
     dt = dt.AddDays(1) 
    ) 
    { 
     // ignore weekend 
     if((dt.DayOfWeek == DayOfWeek.Saturday) || 
      (dt.DayOfWeek == DayOfWeek.Sunday)) 
     { 
      continue; 
     } 

     // get the start/end time for each day... 
     // typically 7am/7pm unless we are at the start/end date 
     TimeSpan dtStartTime = ((dt == start.Date) && (start.TimeOfDay > sevenAM)) ? 
      start.TimeOfDay : sevenAM; 
     TimeSpan dtEndTime = ((dt == end.Date) && (end.TimeOfDay < sevenPM)) ? 
      end.TimeOfDay : sevenPM; 

     if(dtStartTime < dtEndTime) 
     { 
      total = total.Add(dtEndTime - dtStartTime); 
     } 
    } 

    return total; 
} 
0

Je ne vais pas écrire un code, mais ayant un DateTime vous pouvez dire le jour de la semaine, ainsi vous savez combien de week-ends sont dans votre gamme, ainsi vous pouvez dire combien de minutes sont dans un week-end. Donc, ce ne serait pas si difficile ... bien sûr, il doit y avoir une solution optimale d'une ligne ... mais je pense que vous pouvez vous déplacer avec celui-ci.

J'ai oublié de mentionner que vous connaissez également les minutes entre 19h00 et 7h00, il vous suffit de soustraire la bonne quantité de minutes au décalage horaire que vous obtenez.

1

Utilisez TimeSpan.TotalMinutes, soustrayez les jours non ouvrables, soustrayez les heures superflues.

Questions connexes