2016-11-07 2 views
1

J'essaye d'écrire une fonction qui (1) fonctionnera sur une machine dans n'importe quel fuseau horaire, (2) calculera le nombre de heures entre deux dates, et (3) prendre en compte les paramètres DST pour le fuseau horaire donné ...Déterminer le nombre d'heures entre deux DateTimes dans un fuseau horaire spécifique, en prenant DST en compte

Cela semble si facile, mais je n'arrive pas à le comprendre. Je génère des horaires de travail dans d'autres fuseaux horaires. Donc, hier (6 novembre 2016) à New York, par exemple, je devrais générer 25 heures de programmes. Aujourd'hui (7 novembre 2016), je n'aurais besoin que de générer des horaires pour 24 heures. Mars prochain, il y aura une journée qui nécessite 23 heures.

Je regardais ceci étant ma signature, mais je pense que je pourrais avoir besoin d'un argument string TimeZoneId.

public double GetScheduledHours(DateTime begin, DateTime end) {} 

j'ai commencé ici:

// incorrectly outputs 24 
Console.WriteLine((new DateTime(2016, 11, 7) - new DateTime(2016, 11, 6)).TotalHours); 

J'ai essayé à côté, avec un succès de courte durée:

// correctly outputs 25 on a machine whose local time zone is "Eastern Standard Time" 
// incorrectly outputs 24 on dotnetfiddle whose local time zone is "Coordinated Universal Time" 
var ticks = (new DateTime(2016, 11, 7).ToUniversalTime().Ticks - new DateTime(2016, 11, 6).ToUniversalTime().Ticks; 
Console.WriteLine(ticks/10000/1000/60/60 + " hours"); 

J'ai essayé d'utiliser DateTimeOffset en vain et trouvé un comportement bizarre aussi bien ...

// on a machine whose local time zone is Easter Standard Time, 
// if you store the DateTime inside of a DateTimeOffset, simple subtraction works fine 
var pacificTz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); 

DateTime pst6thAsDateTime = pst6th.DateTime; 
DateTime pst7thAsDateTime = pst7th.DateTime; 

DateTimeOffset pst6thAsDateTimeOffset = pst6th.DateTime; 
DateTimeOffset pst7thAsDateTimeOffset = pst7th.DateTime; 

// incorrectly outputs 24 
Console.WriteLine((pst7thAsDateTime - pst6thAsDateTime).TotalHours); 

// correctly outputs 25 
// also incorrectly outputs 24 on a machine whose local time zone is not "Eastern Standard Time" 
Console.WriteLine((pst7thAsDateTimeOffset - pst6thAsDateTimeOffset).TotalHours); 

Un violon avec quelques codes ... si vous exécutez ce code sur une machine dont le fuseau horaire local est "Eastern Standard Time", seulement 2 échouent.

https://dotnetfiddle.net/djZ7ME

+0

Vous devez également garder à l'esprit à New York change l'heure d'été au 6 novembre, mais en Europe au 30 octobre. –

Répondre

2

Si vous voulez juste utiliser le fuseau horaire local de la machine, alors la réponse est très simple:

var start = new DateTimeOffset(new DateTime(2016, 11, 6)); 
var end = new DateTimeOffset(new DateTime(2016, 11, 7)); 
var hours = (end - start).TotalHours; // 25 

Toutefois, si le fuseau horaire qui vous intéresse est pas un local, les choses deviennent un peu plus compliquées. Jon Skeet's answer here montre la solution pour trouver minuit dans un fuseau horaire spécifique qui devrait aider.

var pacificTz = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time"); 
var startLocal = new DateTime(2016, 11, 6); 
var startOffset = new DateTimeOffset(startLocal, pacificTz.GetUtcOffset(startLocal)); 
var endLocal = new DateTime(2016, 11, 7); 
var endOffset = new DateTimeOffset(endLocal, pacificTz.GetUtcOffset(endLocal)); 
var hours = (endOffset - startOffset).TotalHours; // 25 

Dans ce cas, comme le fait remarquer la réponse, vous devriez prendre soin de créer des heures qui n'existent pas!

-1

vous pouvez utiliser IsDaylightSavingTime et utiliser opération XOR pour ajouter ou soustraire une heure. Dans mon test, j'utilise le fuseau horaire allemand pour tester. Peut-être que vous devez adapter les testdates. J'utilise fluentassertions pour affirmer.

[TestFixture] 
public class DateTimeScheduleTests 
{ 
    public double GetScheduledHours(DateTime begin, DateTime end) 
    { 
     var beginIsDaylight = begin.IsDaylightSavingTime(); 
     var endIsDayLight = end.IsDaylightSavingTime(); 

     var diff = 0; 
     if (beginIsDaylight^endIsDayLight) 
      diff = beginIsDaylight ? -1 : 1; 

     return (begin - end).TotalHours + diff; 
    } 

    [Test] 
    public void TestDate() 
    { 
     var begin = new DateTime(2016, 10, 30); 
     var end = new DateTime(2016, 10, 29); 

     GetScheduledHours(begin, end).Should().Be(24); 

     begin = new DateTime(2016, 10, 31); 
     end = new DateTime(2016, 10, 30); 

     GetScheduledHours(begin, end).Should().Be(25); 

     begin = new DateTime(2017, 3, 26); 
     end = new DateTime(2017, 3, 25); 

     GetScheduledHours(begin, end).Should().Be(24); 

     begin = new DateTime(2017, 3, 27); 
     end = new DateTime(2017, 3, 26); 

     GetScheduledHours(begin, end).Should().Be(23); 
    } 
} 
+0

'DateTime.IsDaylightSavingTime' vous indiquera seulement si la valeur fournie est en heure d'été * pour le fuseau horaire local actuel * - et non pour un fuseau horaire donné. –

+0

Mais si j'ai bien compris la question de @Langdon, il est seulement intéressé par le fuseau horaire local actuel '(1) fonctionne sur une machine dans n'importe quel fuseau horaire,' '(3) prend les paramètres DST pour le fuseau horaire donné en compte ' –

+0

Droite,' "pour le fuseau horaire donné" '- indépendamment de celui défini sur la machine. Le titre dit aussi «dans un fuseau horaire spécifique». –