Comment tronquer les millisecondes hors d'un .NET DateTime

334

J'essaie de comparer un horodatage d'une demande entrante à une valeur stockée de base de données. SQL Server conserve bien sûr une certaine précision de millisecondes sur l'heure, et lorsqu'il est lu dans un .NET DateTime, il inclut ces millisecondes. La demande entrante au système, cependant, n'offre pas cette précision, donc je dois simplement laisser tomber les millisecondes.

J'ai l'impression de manquer quelque chose d'évident, mais je n'ai pas trouvé de façon élégante de le faire (C #).

Jeff Putz
la source
(3e essai ...) Étant donné que 20% des réponses ( 1 , 2 , 3 ) décrivent comment omettre ou supprimer le composant millisecondes de la stringreprésentation formatée de a DateTime, une modification est peut-être nécessaire pour indiquer clairement que "tronquer" / millisecondes « baisse » signifie « produire une DateTimevaleur où toutes les composantes de date / heure sont les mêmes , sauf TimeOfDay.TotalMillisecondsest 0. » Les gens ne lisent pas, bien sûr, mais juste pour éliminer toute ambiguïté.
BACON

Réponses:

557

Ce qui suit fonctionnera pour un DateTime qui a des millisecondes fractionnaires et conserve également la propriété Kind (Local, Utc ou Undefined).

DateTime dateTime = ... anything ...
dateTime = new DateTime(
    dateTime.Ticks - (dateTime.Ticks % TimeSpan.TicksPerSecond), 
    dateTime.Kind
    );

ou l'équivalent et plus court:

dateTime = dateTime.AddTicks( - (dateTime.Ticks % TimeSpan.TicksPerSecond));

Cela pourrait être généralisé dans une méthode d'extension:

public static DateTime Truncate(this DateTime dateTime, TimeSpan timeSpan)
{
    if (timeSpan == TimeSpan.Zero) return dateTime; // Or could throw an ArgumentException
    if (dateTime == DateTime.MinValue || dateTime == DateTime.MaxValue) return dateTime; // do not modify "guard" values
    return dateTime.AddTicks(-(dateTime.Ticks % timeSpan.Ticks));
}

qui est utilisé comme suit:

dateTime = dateTime.Truncate(TimeSpan.FromMilliseconds(1)); // Truncate to whole ms
dateTime = dateTime.Truncate(TimeSpan.FromSeconds(1)); // Truncate to whole second
dateTime = dateTime.Truncate(TimeSpan.FromMinutes(1)); // Truncate to whole minute
...
Joe
la source
Bien que je vous donne ceci parce que vous êtes techniquement correct, cette quantité de résolution n'est pas nécessaire pour les personnes qui lisent des données de SQL Server pour les comparer à certaines données distribuées (une demande Web, dans mon cas).
Jeff Putz
1
Agréable. Il est clair que quelqu'un doit donner à la classe DateTime des méthodes d'extension à arrondir au plus proche pour que ce type de bon codage soit réutilisé.
chris.w.mclean
C'est très peu probable, mais cette approche ne se casse-t-elle pas lorsque ticks = 0?
adotout
@adotout, la méthode Truncate ci-dessus lèvera une exception DivideByZeroException si le paramètre timeSpan est nul, est-ce ce que vous entendez par "approche s'interrompt lorsque ticks = 0"? Il serait préférable de lever une ArgumentException lorsque timeSpan est nul.
Joe
145
var date = DateTime.Now;

date = new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
benPearce
la source
34
Clair et simple, n'oubliez pas d'ajouter un ", date.Kind" à la fin du constructeur pour vous assurer de ne pas perdre une information importante.
JMcDaniel
9
Soyez prudent avec cette solution dans le code sensible aux performances. Mon application passait 12% du temps CPU dans System.DateTime.GetDatePart .
Colonel Panic
3
C'est simple, mais plus lent que la question marquée comme meilleure réponse. Non pas que cela puisse être un goulot d'étranglement, mais il est environ 7 à 8 fois plus lent.
Jonas
Les instructions "beaucoup plus lentes" ne sont pas exactement correctes, la différence est comprise entre 50% et environ 100% selon le temps d'exécution; 4.7.2 net: 0,35 µs vs 0,62 µs et core 3.1: 0,18 µs vs 0,12 µs , soit des micro-secondes (10 ^ -6 secondes)
juwens
62

Voici une méthode d'extension basée sur une réponse précédente qui vous permettra de tronquer à n'importe quelle résolution ...

Usage:

DateTime myDateSansMilliseconds = myDate.Truncate(TimeSpan.TicksPerSecond);
DateTime myDateSansSeconds = myDate.Truncate(TimeSpan.TicksPerMinute)

Classe:

public static class DateTimeUtils
{
    /// <summary>
    /// <para>Truncates a DateTime to a specified resolution.</para>
    /// <para>A convenient source for resolution is TimeSpan.TicksPerXXXX constants.</para>
    /// </summary>
    /// <param name="date">The DateTime object to truncate</param>
    /// <param name="resolution">e.g. to round to nearest second, TimeSpan.TicksPerSecond</param>
    /// <returns>Truncated DateTime</returns>
    public static DateTime Truncate(this DateTime date, long resolution)
    {
        return new DateTime(date.Ticks - (date.Ticks % resolution), date.Kind);
    }
}
Sky Sanders
la source
1
Il s'agit d'une solution vraiment flexible et réutilisable, concise et expressive sans être trop verbeuse. Mon vote comme meilleure solution.
Jaans
2
Vous n'avez pas réellement besoin des parenthèses autour des opérandes%.
ErikE
8
.. mais les parens ajoutent de la clarté, à mon avis.
orion elenzil
28
DateTime d = DateTime.Now;
d = d.AddMilliseconds(-d.Millisecond);
chris.w.mclean
la source
70
-1: ne fonctionnera que si la valeur DateTime n'inclut pas les fractions de milliseconde.
Joe
7
L'utilisation de cette méthode a provoqué l'échec de certains de mes tests unitaires: Prévue: 2010-05-05 15: 55: 49.000 Mais était: 2010-05-05 15: 55: 49.000. Je suppose qu'en raison de ce que Joe a mentionné à propos des fractions de milliseconde.
Seth Reno
6
Ne fonctionne pas pour la sérialisation, par exemple 2010-12-08T11: 20: 03.000099 + 15: 00 est la sortie, ne coupe pas complètement les millisecondes.
joedotnot
5
La Millisecondpropriété donne un entier compris entre 0 et 999 (inclus). Donc, si l'heure de la journée avant l'opération était, disons, 23:48:49.1234567alors cet entier sera 123, et l'heure de la journée après l'opération 23:48:49.0004567. Il n'a donc pas été tronqué à un nombre entier de secondes.
Jeppe Stig Nielsen
11

Parfois, vous souhaitez tronquer quelque chose en fonction du calendrier, comme l'année ou le mois. Voici une méthode d'extension qui vous permet de choisir n'importe quelle résolution.

public enum DateTimeResolution
{
    Year, Month, Day, Hour, Minute, Second, Millisecond, Tick
}

public static DateTime Truncate(this DateTime self, DateTimeResolution resolution = DateTimeResolution.Second)
{
    switch (resolution)
    {
        case DateTimeResolution.Year:
            return new DateTime(self.Year, 1, 1, 0, 0, 0, 0, self.Kind);
        case DateTimeResolution.Month:
            return new DateTime(self.Year, self.Month, 1, 0, 0, 0, self.Kind);
        case DateTimeResolution.Day:
            return new DateTime(self.Year, self.Month, self.Day, 0, 0, 0, self.Kind);
        case DateTimeResolution.Hour:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerHour));
        case DateTimeResolution.Minute:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMinute));
        case DateTimeResolution.Second:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerSecond));
        case DateTimeResolution.Millisecond:
            return self.AddTicks(-(self.Ticks % TimeSpan.TicksPerMillisecond));
        case DateTimeResolution.Tick:
            return self.AddTicks(0);
        default:
            throw new ArgumentException("unrecognized resolution", "resolution");
    }
}
KingPong
la source
9

Au lieu de laisser tomber les millisecondes puis de comparer, pourquoi ne pas comparer la différence?

DateTime x; DateTime y;
bool areEqual = (x-y).TotalSeconds == 0;

ou

TimeSpan precision = TimeSpan.FromSeconds(1);
bool areEqual = (x-y).Duration() < precision;
Bob
la source
3
la première option ne fonctionne pas, car TotalSeconds est un double; il renvoie également les millisecondes.
Jowen
1
Comparer la différence ne donne pas le même résultat que tronquer puis comparer. Par exemple, 5,900 et 6,100 sont à moins d'une seconde d'intervalle, donc compareraient à égalité avec votre méthode. Mais les valeurs tronquées 5 et 6 sont différentes. Ce qui est approprié dépend de vos besoins.
Joe
7

Moins évident mais plus de 2 fois plus rapide:

// 10000000 runs

DateTime d = DateTime.Now;

// 484,375ms
d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

// 1296,875ms
d = d.AddMilliseconds(-d.Millisecond);
Diadistis
la source
3
Notez que la deuxième option d.AddMilliseconds(-d.Millisecond),, ne déplace pas nécessairement le DateTime exactement à la seconde complète précédente. d.Ticks % TimeSpan.TicksPerMillisecondles tiques (entre 0 et 9 999) au-delà de votre seconde resteront.
Technetium
5

Pour arrondir à la seconde:

dateTime.AddTicks(-dateTime.Ticks % TimeSpan.TicksPerSecond)

Remplacez par TicksPerMinutepour arrondir à la minute près.


Si votre code est sensible aux performances, soyez prudent

new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second)

Mon application passait 12% du temps CPU dans System.DateTime.GetDatePart .

Colonel Panic
la source
3

Un moyen de lecture facile est ...

//Remove milliseconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm:ss"), "yyyy-MM-dd HH:mm:ss", null);

Et plus...

//Remove seconds
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH:mm"), "yyyy-MM-dd HH:mm", null);

//Remove minutes
DateTime date = DateTime.Now;
date = DateTime.ParseExact(date.ToString("yyyy-MM-dd HH"), "yyyy-MM-dd HH", null);

//and go on...
Sergio Cabral
la source
4
La conversion en chaînes et l'analyse est une idée horrible en termes de performances.
Jeff Putz
2
@JeffPutz true, mais ce n'est que simple. Convient pour un test automatisé où une valeur insérée et extraite d'une base de données perd les tiques (ma situation exacte). Cependant, cette réponse pourrait être encore plus simple qu'elle ne l'est, car elle var now = DateTime.Parse(DateTime.Now.ToString())fonctionne très bien.
Grimm The Opiner
1
@GrimmTheOpiner - "... fonctionne très bien", surtout, mais pas garanti. Ce qu'il fait est: "Arrondit un DateTime à n'importe quelle précision 'Long time' est configuré comme dans les préférences du Panneau de configuration de l'utilisateur actuel". Ce qui est généralement, mais pas nécessairement, en secondes.
Joe
1
comme sa simplicité, les performances ne sont pas un problème pour les tests automatisés.
liang
1

Concernant la réponse de Diadistis. Cela a fonctionné pour moi, sauf que j'ai dû utiliser Floor pour supprimer la partie fractionnaire de la division avant la multiplication. Alors,

d = new DateTime((d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

devient

d = new DateTime(Math.Floor(d.Ticks / TimeSpan.TicksPerSecond) * TimeSpan.TicksPerSecond);

Je m'attendais à ce que la division de deux valeurs Long aboutisse à un Long, supprimant ainsi la partie décimale, mais elle la résout comme un Double, laissant la même valeur exacte après la multiplication.

Eppsy


la source
1

2 Méthodes d'extension pour les solutions mentionnées ci-dessus

    public static bool LiesAfterIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate > compareDate;
    }


    public static bool LiesAfterOrEqualsIgnoringMilliseconds(this DateTime theDate, DateTime compareDate, DateTimeKind kind)
    {
        DateTime thisDate = new DateTime(theDate.Year, theDate.Month, theDate.Day, theDate.Hour, theDate.Minute, theDate.Second, kind);
        compareDate = new DateTime(compareDate.Year, compareDate.Month, compareDate.Day, compareDate.Hour, compareDate.Minute, compareDate.Second, kind);

        return thisDate >= compareDate;
    }

usage:

bool liesAfter = myObject.DateProperty.LiesAfterOrEqualsIgnoringMilliseconds(startDateTime, DateTimeKind.Utc);
HerbalMart
la source
1

Pas la solution la plus rapide mais simple et facile à comprendre:

DateTime d = DateTime.Now;
d = d.Date.AddHours(d.Hour).AddMinutes(d.Minute).AddSeconds(d.Second)
AlliterativeAlice
la source
0
DateID.Text = DateTime.Today.ToShortDateString();

Use ToShortDateString() //Date 2-02-2016
Use ToShortDateString() // Time 

Et par l'utilisation de

ToLongDateString() // its show 19 February 2016.

: P

Dhawal Shukal
la source
-1. Je peux voir comment la question a pu être mal interprétée comme demandant de produire un stringau lieu d'un DateTime, mais cela omet complètement les composants temporels de la sortie . (Cela rend également l'accès à la Todaypropriété inutile.)
BACON
0

Nouvelle méthode

String Date = DateTime.Today.ToString("dd-MMM-yyyy"); 

// définir le paramètre de passe de chaîne jj-mmm-aaaa return 24-feb-2016

Ou affiché sur la zone de texte

txtDate.Text = DateTime.Today.ToString("dd-MMM-yyyy");

// mettre sur PageonLoad

Dhawal Shukal
la source
-1. Je peux voir comment la question a pu être mal interprétée comme demandant de produire un stringau lieu d'un DateTime, mais cela omet complètement les composants temporels de la sortie . (Cela rend également l'accès à la Todaypropriété inutile.)
BACON
0

Dans mon cas, je visais à sauver TimeSpan de l'outil datetimePicker sans enregistrer les secondes et les millisecondes, et voici la solution.

Convertissez d'abord le datetimePicker.value au format de votre choix, le mien est "HH: mm", puis reconvertissez-le en TimeSpan.

var datetime = datetimepicker1.Value.ToString("HH:mm");
TimeSpan timeSpan = Convert.ToDateTime(datetime).TimeOfDay;
Thinker Bell
la source
Une meilleure façon (intention plus claire, évite le formatage et l'analyse de a string) de le faire serait DateTime datetime = datetimepicker1.Value; TimeSpan timeSpan = new TimeSpan(datetime.Hour, datetime.Minute, 0); Ou vous pouvez utiliser une variante de la méthode d'extension de Joe qui fonctionne sur des TimeSpanvaleurs et utilise TimeSpan timeSpan = datetime.TimeOfDay.Truncate(TimeSpan.FromSeconds(1));pour tronquer les secondes.
BACON
0

Ceci est ma version des méthodes d'extension publiées ici et dans des questions similaires. Cela valide la valeur des graduations d'une manière facile à lire et préserve le DateTimeKind de l'instance DateTime d'origine. (Cela a des effets secondaires subtils mais pertinents lors du stockage dans une base de données comme MongoDB.)

Si le véritable objectif est de tronquer un DateTime à une valeur spécifiée (c'est-à-dire Heures / Minutes / Secondes / MS), je recommande plutôt d'implémenter cette méthode d'extension dans votre code. Il garantit que vous ne pouvez tronquer qu'avec une précision valide et il conserve les métadonnées DateTimeKind importantes de votre instance d'origine:

public static DateTime Truncate(this DateTime dateTime, long ticks)
{
    bool isValid = ticks == TimeSpan.TicksPerDay 
        || ticks == TimeSpan.TicksPerHour 
        || ticks == TimeSpan.TicksPerMinute 
        || ticks == TimeSpan.TicksPerSecond 
        || ticks == TimeSpan.TicksPerMillisecond;

    // /programming/21704604/have-datetime-now-return-to-the-nearest-second
    return isValid 
        ? DateTime.SpecifyKind(
            new DateTime(
                dateTime.Ticks - (dateTime.Ticks % ticks)
            ),
            dateTime.Kind
        )
        : throw new ArgumentException("Invalid ticks value given. Only TimeSpan tick values are allowed.");
}

Ensuite, vous pouvez utiliser la méthode comme ceci:

DateTime dateTime = DateTime.UtcNow.Truncate(TimeSpan.TicksPerMillisecond);

dateTime.Kind => DateTimeKind.Utc
Kyle L.
la source
-1

Je sais que la réponse est assez tardive, mais la meilleure façon de se débarrasser des millisecondes est

var currentDateTime = DateTime.Now.ToString("s");

Essayez d'imprimer la valeur de la variable, elle affichera la date et l'heure, sans millisecondes.

Nisarg Shah
la source
1
Ce n'est pas l'idéal. Vous avez alors une chaîne et non un DateTime.
Jeff Putz