Apprenez à programmer en C# sur .NET
Last updated on Tuesday, January 8, 2013
  • 8 semaines
  • Facile

Ce cours est visible gratuitement en ligne.

Got it!

Travailler avec le temps

On a toujours besoin à un moment ou un autre d'utiliser des repères temporels.
C'est pourquoi je vais vous apprendre comment travailler avec le temps en C#.

Les structures DateTime et TimeSpan

DateTime

Vu que la documentation MSDN est bien faite, je me permets de faire du copier-coller :

Citation : http://msdn.microsoft.com/fr-fr/library/system.datetime(VS.80).aspx

Le type DateTime représente des dates et des heures dont la valeur est comprise entre 00:00:00 (minuit), le 1er janvier de l'année 0001 de l'ère commune et 23:59:59, le 31 décembre de l'année 9999 après J.C. (ère commune).

Les valeurs de date sont mesurées en unités de 100 nanosecondes, appelées graduations, et une date spécifique correspond au nombre de graduations écoulées à compter de 00:00 (minuit), le 1er janvier de l'année 0001 après J.C. de l'ère commune dans le calendrier GregorianCalendar. Par exemple, une valeur de graduations égale à 31241376000000000L représente la date du vendredi 1er janvier 0100 00:00:00 (minuit). Une valeur DateTime est toujours exprimée dans le contexte d'un calendrier explicite ou par défaut.

Valeurs DateTime

Dans le type DateTime, les valeurs d'heure sont souvent exprimées en heure universelle (UTC), précédemment connue sous le nom d'heure de Greenwich (GMT). Le temps universel coordonné est l'heure mesurée à la longitude zéro, le point de départ de l'UTC. L'heure d'été ne s'applique pas à l'UTC.

L'heure locale est relative à un fuseau horaire particulier. Un fuseau horaire est associé à un décalage horaire, qui est le décalage du fuseau horaire mesuré en heures par rapport au point de départ UTC. De plus, l'heure locale peut en option être affectée par l'heure d'été, qui ajoute ou soustrait une heure de la journée. Par conséquent, l'heure locale est calculée en ajoutant l'offset de fuseau horaire à l'UTC et en réglant l'heure d'été si nécessaire. Le décalage horaire au point de départ de l'UTC est nul.

L'heure UTC est appropriée pour les calculs, les comparaisons et l'enregistrement des dates et heures dans les fichiers. L'heure locale est appropriée pour l'affichage dans les interfaces utilisateur.

Si la propriété Kind d'un objet DateTime est Unspecified, rien ne spécifie si l'heure représentée est locale ou UTC. Les membres DateTime individuels adaptent l'heure non spécifiée à ce membre.

Opérations DateTime

Les opérations de conversion entre l'heure locale et l'heure UTC tiennent compte de l'heure d'été, contrairement aux opérations arithmétiques et aux comparaisons.

Les calculs et les comparaisons d'objets DateTime n'ont de signification que si les objets représentent les heures dans le même fuseau horaire. Pour cette raison, si aucun fuseau horaire n'est spécifié pour les objets, le développeur dispose probablement d'un mécanisme externe, par exemple une variable ou une stratégie explicite, qui lui permet d'établir dans quel fuseau horaire un objet DateTime a été créé.

Les opérations effectuées par les membres du type DateTime prennent en considération des éléments tels que les années bissextiles et le nombre de jours d'un mois.

DateTime

DateTime

Dans le type DateTime, les valeurs d'heure sont souvent exprimées en heure universelle (UTC), précédemment connue sous le nom d'heure de Greenwich (GMT). Le temps universel coordonné est l'heure mesurée à la longitude zéro, le point de départ de l'UTC. L'heure d'été ne s'applique pas à l'UTC.

L'heure locale est relative à un fuseau horaire particulier. Un fuseau horaire est associé à un décalage horaire, qui est le décalage du fuseau horaire mesuré en heures par rapport au point de départ UTC. De plus, l'heure locale peut en option être affectée par l'heure d'été, qui ajoute ou soustrait une heure de la journée. Par conséquent, l'heure locale est calculée en ajoutant l'offset de fuseau horaire à l'UTC et en réglant l'heure d'été si nécessaire. Le décalage horaire au point de départ de l'UTC est nul.

L'heure UTC est appropriée pour les calculs, les comparaisons et l'enregistrement des dates et heures dans les fichiers. L'heure locale est appropriée pour l'affichage dans les interfaces utilisateur.

Si la propriété Kind d'un objet DateTime est Unspecified, rien ne spécifie si l'heure représentée est locale ou UTC. Les membres DateTime individuels adaptent l'heure non spécifiée à ce membre.

Opérations DateTime

Les opérations de conversion entre l'heure locale et l'heure UTC tiennent compte de l'heure d'été, contrairement aux opérations arithmétiques et aux comparaisons.

Les calculs et les comparaisons d'objets DateTime n'ont de signification que si les objets représentent les heures dans le même fuseau horaire. Pour cette raison, si aucun fuseau horaire n'est spécifié pour les objets, le développeur dispose probablement d'un mécanisme externe, par exemple une variable ou une stratégie explicite, qui lui permet d'établir dans quel fuseau horaire un objet DateTime a été créé.

Les opérations effectuées par les membres du type DateTime prennent en considération des éléments tels que les années bissextiles et le nombre de jours d'un mois.

DateTime

Les opérations de conversion entre l'heure locale et l'heure UTC tiennent compte de l'heure d'été, contrairement aux opérations arithmétiques et aux comparaisons.

Les calculs et les comparaisons d'objets DateTime n'ont de signification que si les objets représentent les heures dans le même fuseau horaire. Pour cette raison, si aucun fuseau horaire n'est spécifié pour les objets, le développeur dispose probablement d'un mécanisme externe, par exemple une variable ou une stratégie explicite, qui lui permet d'établir dans quel fuseau horaire un objet DateTime a été créé.

Les opérations effectuées par les membres du type DateTime prennent en considération des éléments tels que les années bissextiles et le nombre de jours d'un mois.

C'est bien beau tout ça, mais vu qu'un exemple est toujours plus parlant :

Code

Description

new DateTime(2009, 8, 8)

Date d'aujourd'hui entrée "à la main" dans une méthode. new sert à appeler le constructeur.
8 août 2009 00:00:00

DateTime.Today

Date d'aujourd'hui obtenue par une propriété de DateTime.
8 août 2009 00:00:00

DateTime.Now

Date et heure d'aujourd'hui sur mon PC (heure locale) obtenues par une propriété de DateTime.
8 août 2009 22:55:37

DateTime.UtcNow

Date et heure d'aujourd'hui sur mon PC (heure universelle UTC) obtenues par une propriété de DateTime.
8 août 2009 20:55:37

DateTime.Today.AddDays(1)

Date de demain obtenue par une méthode de DateTime (il se trouve que la propriété DateTime.Today est de type DateTime).
9 août 2009 00:00:00

Maintenant, lorsqu'il s'agit d'afficher sous forme d'une chaîne de caractères un objet DateTime :

Méthode

Description

Exemple

ToLongDateString()

date (long format)

samedi 8 août 2009

ToLongTimeString()

heure (long format)

22:55:37

ToShortDateString()

date (court format)

08/08/2009

ToShortTimeString()

heure (court format)

22:55

ToString()

date + heure (court format)

08/08/2009 22:55:37

TimeSpan

Citation : http://msdn.microsoft.com/fr-fr/library/system.datetime(VS.80).aspx

DateTime et TimeSpan

Les types DateTime et TimeSpan se distinguent par le fait que DateTime représente un instant, tandis que TimeSpan représente un intervalle de temps. Cela signifie, par exemple, que vous pouvez soustraire une instance DateTime d'une autre pour obtenir l'intervalle de temps les séparant. De la même façon, vous pouvez ajouter un TimeSpan positif au DateTime en cours pour calculer une date ultérieure.

Vous pouvez ajouter ou soustraire un intervalle de temps d'un objet DateTime. Les intervalles de temps peuvent être négatifs ou positifs ; ils peuvent être exprimés en unités telles que les graduations ou les secondes ou comme un objet TimeSpan.

DateTime

TimeSpan

Les types DateTime et TimeSpan se distinguent par le fait que DateTime représente un instant, tandis que TimeSpan représente un intervalle de temps. Cela signifie, par exemple, que vous pouvez soustraire une instance DateTime d'une autre pour obtenir l'intervalle de temps les séparant. De la même façon, vous pouvez ajouter un TimeSpan positif au DateTime en cours pour calculer une date ultérieure.

Vous pouvez ajouter ou soustraire un intervalle de temps d'un objet DateTime. Les intervalles de temps peuvent être négatifs ou positifs ; ils peuvent être exprimés en unités telles que les graduations ou les secondes ou comme un objet TimeSpan.

Il n'y a pas tellement plus à dire, l'IntelliSense vous propose de toute façon ce que vous pouvez faire...

Toutefois, pour vous montrer ce que ça donne en exemple, je vais vous montrer comment déterminer le temps d'exécution de votre programme. Votre programme commence dans la méthode Main du fichier Program.cs. Modifiez-le comme ceci :

DateTime exeStart = DateTime.Now;

// Les 3 lignes suivantes sont déjà dans votre code.
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());

TimeSpan exeDuration = DateTime.Now.Subtract(exeStart);

string exeTime = string.Format(
	"Temps d'exécution : {0}h, {1}m, {2}s et {3}ms.",
	exeDuration.Hours,
	exeDuration.Minutes,
	exeDuration.Seconds,
	exeDuration.Milliseconds);

Les contrôles MonthCalendar et DateTimePicker

Vous pouvez les prendre tous les deux depuis la boîte à outils.

404 Image not found

Rendu visuel d'un MonthCalendar.
Image 020.050.20000

MonthCalendar

Voici à quoi il ressemble (cf. image 020.050.20000) :

Il permet de sélectionner une ou plusieurs dates.
Ouvrez Form1.cs en mode Design et double-cliquez sur le gestionnaire d'évènements DateChanged (dans la fenêtre Properties) pour générer le code associé.

Voici à quoi ressemble ce qui est créé automatiquement suite au double-clic (cela dépend du nom de votre MonthCalendar) :

this.monthCalendar1.DateChanged += new System.Windows.Forms.DateRangeEventHandler(this.monthCalendar1_DateChanged);

Dans Form1.cs la méthode ressemble à celle-ci :

private void monthCalendar1_DateChanged(object sender, DateRangeEventArgs e)
{
}

e.Start est un objet de type DateTime représentant le début de la sélection.
e.End est aussi un objet de type DateTime, mais représentant la fin de la sélection.

DateTimePicker

404 Image not found

Rendu visuel d'un DateTimePicker.
Image 020.050.20100

Voici l'allure qu'il peut avoir (cf. image 020.050.20100) :

Il possède une propriété Value, de type DateTime qui correspond à sa valeur actuelle.
Il possède notamment le gestionnaire d'évènements ValueChanged, qui surveille l'évènement correspondant au changement de Value.

Les classes Timer

Il existe trois contrôles Timer dans la plate-forme .NET :

  • un Timer serveur que vous pouvez ajouter à la boîte à outils (cf. MSDN) ;

  • un Timer Windows, toujours disponible dans la boîte à outils (cf. MSDN) ;

  • un Timer thread, disponible par programme (cf. MSDN) ;

Nous emploierons le Timer Windows, optimisé pour une utilisation dans les applications Windows Forms, et qui se trouve par conséquent dans l'espace de noms System.Windows.Forms.
Ce Timer est le moins précis des trois : sa précision est limitée à 55 millisecondes. Cependant, pour nous c'est suffisant.

Voici les propriétés d'un Timer Windows qui nous intéressent :

  • Interval : entier représentant la durée (exprimée en millisecondes) entre deux tics. Par défaut cette durée vaut 100ms.

  • Start : méthode qui démarre le Timer.

  • Stop : méthode qui arrête le Timer.

  • Tick : gestionnaire d'évènements qui lance un évènement à chaque tic.

Place à la démonstration. Le gestionnaire d'évènements est situé dans Form1.Designer.cs si vous l'avez fait générer automatiquement ; sinon incluez-le dans la méthode Form1 (le constructeur).

Code situé dans Form1.cs :

public partial class Form1 : Form
{
	// Déclaration des champs
	static Timer s_myTimer = new Timer();
	static int s_myCounter = 0;
	
	// Constructeur
	public Form1()
	{
		// À ne mettre ici que si ce n'est pas déjà dans Form1.Designer.cs
		s_myTimer.Tick += new EventHandler(s_myTimer_Tick);

		// 1 seconde = 1000 millisecondes
		s_myTimer.Interval = 1000;
		s_myTimer.Start();
		
		MessageBox.Show("Timer lancé.");
	}
	
	// Méthode appelée pour l'évènement
	static void s_myTimer_Tick(object sender, EventArgs e)
	{
		s_myCounter++;
		
		MessageBox.Show("s_myCounter vaut " + s_myCounter + ".");
		
		if (s_myCounter >= 5)
		{
			// Si le Timer est en marche...
			if (s_myTimer.Enabled)
			{
				s_myTimer.Stop();
				MessageBox.Show("Timer stoppé.");
			}
			else
			{
				MessageBox.Show("Timer déjà stoppé.");
			}
		}
	}
}
Rappels sur le mot-clef static

Votre fenêtre possède deux champs, un Timer nommé s_myTimer et un entier nommé s_myCounter, qui sont statiques.
Des membres non statiques sont caractéristiques d'une instance (objet particulier fait sur le modèle d'une classe). Chaque instance possède ces membres, mais leurs valeurs ne sont pas nécessairement les mêmes.
Des membres statiques sont communs à toutes les instances d'une classe.

Ainsi, dans cet exemple, plusieurs fenêtres (qui sont des instances de votre classe Form1 dérivant de Form) peuvent être créés, mais elles ont toutes le même Timer nommé s_myTimer et le même entier nommé s_myCounter.

Explications sur le code

J'ai mis s_myCounter++; avant MessageBox.Show(...); car la fenêtre créée par MessageBox a besoin d'être fermée pour que le code continue.
L'évènement, par contre, est déclenché toutes les secondes.
Si s_myCounter++; venait après, et que vous mettiez plus d'une seconde pour fermer une fenêtre, alors l'évènement serait déclenché avant que la fenêtre soit fermée et que s_myCounter soit incrémenté.
Si vous ne fermiez aucune fenêtre, vous verriez donc "s_myCounter vaut 0." s'afficher à l'infini toutes les secondes.

En mettant s_myCounter++; avant, chaque seconde on commence par incrémenter s_myCounter, donc là si vous ne faites rien, vous voyez "s_myCounter vaut 0.", puis "s_myCounter vaut 1.", ...

La propriété Enabled indique si le Timer est en marche ou non. Si Enabled vaut true, il est en marche, sinon ce n'est pas le cas.

Vous pourriez me dire que ce test est inutile, car si s_myCounter est supérieur ou égal à 5, il n'a jamais été arrêté, car sinon l'évènement n'aurait pas été déclenché. En fait, les évènements sont fourbes à cause du parallélisme implicitement créé. Sachez qu'un bon code est un code qui prévoit toutes les situations, et surtout celle qui sont les plus tordues ! Comme on dit, Never Trust User Input...

Voici un exemple farfelu où le test a du sens : je lance le programme, j'attends 10 secondes donc 10 fenêtres s'affichent. Chaque fois qu'un évènement est déclenché, une "version parallèle" du programme qui s'exécute. Chacune de ces "versions" est bloquée à MessageBox.Show(...); car elle attend que je ferme la fenêtre pop-up. Je décide de fermer une fenêtre (peu importe laquelle) ; le code de la "version" correspondante se poursuit donc. s_myCounter est bien supérieur ou égal à 5 donc on entre dans le bloc if. Le Timer est activé donc on l'arrête et on prévient l'utilisateur. Je décide de fermer une autre fenêtre. Le code de cette "version" continue lui aussi. Il entre dans le bloc if mais là le Timer est déjà bloqué, donc on prévient l'utilisateur.

On peut vouloir que sans action de l'utilisateur, le Timer s'arrête quand même au bout de 5 secondes. Dans ce cas le code devient :

// Méthode appelée pour l'évènement
static void s_myTimer_Tick(object sender, EventArgs e)
{
	s_myCounter++;
	
	if (s_myCounter >= 5)
	{
		// Si le Timer est en marche...
		if (s_myTimer.Enabled)
		{
			s_myTimer.Stop();
			MessageBox.Show("Timer stoppé.");
		}
		else
		{
			MessageBox.Show("Timer déjà stoppé.");
		}
	}
	
	MessageBox.Show("s_myCounter vaut " + s_myCounter + ".");
}

Dans le prochain TP nous utiliserons les connaissances que vous venez d'acquérir (j'ose espérer que je n'aurais pas dû dire "que vous auriez dû acquérir..." :-° ).

Allez, on continue à travailler et on s'accroche ! Vous ne croyiez quand même pas que vous pourriez vous contenter de lire en restant passifs ! :pirate:

Example of certificate of achievement
Example of certificate of achievement