IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

VBA Excel - Apprendre à créer un objet Date
Un billet blog de Pierre Fauconnier

Le , par Pierre Fauconnier

0PARTAGES

En VBA, une date n'est pas un objet, et ça ne fait pas toujours notre affaire... Et si on créait un objet Date?

Salut

- Pierre?
- Mmmmh?
- Je dois trouver la date du premier jour du trimestre de la date d'entrée d'un employé...
- Arf... Tiens, voilà un calendrier...
- Pierre, j'ai 700 employés à vérifier, là...
- ...
- Et? T'as pas de solution?
- Si... On va systématiser notre approche des dates
- Ah, et comment on fait pour "systématiser notre approche des dates"?
- Ben, on crée un objet Date, puisque ça n'existe pas en VBA.

En préambule, je précise qu'Excel permet de simplifier l'approche avec les fonctions de dates utilisables en VBA via Application.WorksheetFunction, mais j'ai choisi de faire du VBA pur pour pouvoir utiliser l'outil dans d'autres applications qu'Excel.

Trouver le premier jour du trimestre d'une date donnée

Avec une variable MyDate, on peut trouver la date du premier jour du trimestre avec le calcul suivant:
Code vba : Sélectionner tout
DateSerial(Year(MyDate), (Int((Month(MyDate) - 1) / 3) + 1) * 3 - 2, 1)

C'est chouette, mais il faut se mettre ce truc dans un OneNote, un NotePad++, un bloc-notes quelconque pour le réutiliser... Pas certain que ce soit très efficace, surtout si vous ne rangez pas bien vos notes. De plus, il faudra aussi aller chercher le bout de code, le snippet comme on dit, pour la fin du trimestre, le début de la semaine, la fin de l'année, etc, etc...

Fonction procédurale

On pourrait bien entendu se créer une fonction procédurale qui reçoit une date en argument et qui renvoie la date voulue en retour. En voilà deux, celle qui donne le début du trimestre et celle qui donne la fin:
Code vba : Sélectionner tout
1
2
3
4
5
6
7
Function getFirstDayOfQuarter(MyDate As Date) As Date 
getFirstDayOfQuarter = DateSerial(Year(MyDate), (Int((Month(MyDate) - 1) / 3) + 1) * 3 - 2, 1) 
End Function 
  
Function getLastDayOfQuarter(MyDate As Date) As Date 
getLastDayOfQuarter = DateSerial(Year(MyDate), (Int((Month(MyDate) - 1) / 3) + 1) * 3 + 1, 0) 
End Function

Et hop, on arrête de réinventer la roue. Il suffit de mettre ces fonctions dans le module Tools, voire de créer un module DateManager et le tour est joué...



Si on veut la date du 1er jour de la semaine du premier jour du trimestre, on se crée une fonction GetFirstDayOfWeek et on lui passe le résultat de la fonction GetFirstDayOfQuarter...
Code : Sélectionner tout
1
2
3
Function getFirstDayOfWeek(MyDate As Date) As Date 
  getFirstDayOfWeek = MyDate - (WeekDay(MyDate, vbMonday) - 1) 
End Function
Code vba : Sélectionner tout
? datemanager.getfirstdayofweek(datemanager.getFirstDayOfQuarter(date))


Ça devient lourd comme écriture, mais c'est faisable.

Création d'un objet Date

Plutôt que de traiter les dates en procédural, on peut les traiter en objet. VBA n'offre pas d'objet date, mais rien n'empêche de nous en créer un. C'est en fait très simple. Si vous n'êtes pas familier de la création d'une classe personnalisée, lisez mon tuto sur le sujet. On crée une classe, et l'on pourra alors créer un objet sur base de cette classe, on lui passe une date puis on peut utiliser des méthodes ou des propriétés liées à cet objet.

Création de la classe

On crée un nouveau module de classe et on lui donne un nom... Par exemple oDate.



On va avoir besoin d'une variable privée pour stocker la date qui sera gérée par l'objet. On aura également besoin d'une procédure qui passe la date à l'objet. Tant qu'à faire, on initialisera l'objet avec la date du jour. Ca ne mange pas de pain et ça peut être pratique.

Code vba : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
Option Explicit 
  
Dim mDate As Date 
  
Function init(ByVal Value As Date) As oDate 
  mDate = Value 
  Set init = Me 
End Function 
  
Private Sub Class_Initialize() 
  mDate = Date 
End Sub

A la création de l'objet, on passe par Class_Initialize qui attribue la date du jour à la variable privée mDate. La fonction Init permet de modifier la date en passant la date de son choix. On remarquera au passage que cette fonction renvoie un objet de type oDate, ce qui permettra d'en exploiter directement les propriétés et méthodes.



Propriété de l'objet qui renvoie une date

Telle quelle, la classe ne sert pas à grand-chose. On va créer une propriété qui renvoie la date passée en argument lors de l'initialisation.
Code vba : Sélectionner tout
1
2
3
Property Get ToDate() As Date 
  ToDate = mDate 
End Property

Maintenant, on peut utiliser notre classe. Comme on le voit, l'objet initialisé l'est avec la date du jour. On peut bien sûr initialiser l'objet avec une autre date



Jusqu'ici, la classe n'apporte rien d'intéressant par rapport à une simple variable de date. Mais si maintenant on ajoute nos deux propriétés permettant d'obtenir les premier et dernier jour du trimestre ainsi que la fonction vue plus haut pour obtenir le premier jour de la semaine de la date, en les réécrivant comme propriétés de notre classe oDate, ça commence à devenir intéressant, surtout si l'on décide que ces propriétés ne renvoient pas une date, mais un objet oDate. On peut alors chainer les propriétés... Au passage, on enrichit la propriété FirstDayOfWeek d'un argument optionnel pour pouvoir démarrer la semaine un autre jour que le lundi...

Code vba : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Property Get FirstDayOfQuarter() As oDate 
  Set FirstDayOfQuarter = New oDate 
  FirstDayOfQuarter.init DateSerial(Year(mDate), (Int((Month(mDate) - 1) / 3) + 1) * 3 - 2, 1) 
End Property 
  
Property Get LastDayOfQuarter() As oDate 
  Set LastDayOfQuarter = New oDate 
  LastDayOfQuarter.init DateSerial(Year(mDate), (Int((Month(mDate) - 1) / 3) + 1) * 3 + 1, 0) 
End Property 
  
Property Get FirstDayOfWeek(Optional FirstWeekDay As VbDayOfWeek = vbMonday) As oDate 
  Set FirstDayOfWeek = New oDate 
  FirstDayOfWeek.init mDate - (WeekDay(mDate, FirstWeekDay) - 1) 
End Property



Vous commencez à voir l'intérêt de travailler avec un objet oDate qui, pour certaines propriétés, renvoie un objet oDate? Ca devient amusant, non?

Création de propriétés selon vos besoins

Continuons un peu. On va ajouter:
  • une propriété qui renvoie la date selon un format passé en argument;
  • une propriété qui renvoie la date transformée en valeur numérique de type long;
  • une propriété qui renvoie un objet oDate pour le dernier jour de la semaine de la date.


Code vba : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
Property Get ToString(Pattern As String) As String 
  ToString = Format(mDate, Pattern) 
End Property 
  
Property Get ToLong() As Long 
  ToLong = mDate * 1 
End Property 
  
Property Get LastDayOfWeek(Optional FirstWeekDay As VbDayOfWeek = vbMonday) As oDate 
  Set LastDayOfWeek = New oDate 
  LastDayOfWeek.init mDate - (WeekDay(mDate, FirstWeekDay) - 1) + 6 
End Property

On peut, là aussi, chainer les propriétés:


Lorsque j'ai créé cette classe, j'ai trouvé intéressant d'y ajouter deux propriétés pour aller à la veille ou au lendemain, et qui renvoyaient évidemment un oDate. Ca permet de trouver par exemple la date du dernier jour du trimestre qui précède une date ou la date du premier jour du trimestre qui suit une date:
Code vba : Sélectionner tout
1
2
3
4
5
6
7
8
9
Property Get Inc() As oDate 
  Set Inc = New oDate 
  Inc.init mDate + 1 
End Property 
  
Property Get Dec() As oDate 
  Set Dec = New oDate 
  Dec.init mDate - 1 
End Property

Par la suite, je me suis dit qu'il était intéressant de pouvoir décaler la date de x jours, mois, années...
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function Offset(ByVal Value As Long, ByVal Unit As String) As oDate 
  Dim DateItem As Date 
  Set Offset = New oDate 
 
  Select Case UCase(Unit) 
    Case "Y" 
      Offset.init DateSerial(Year(mDate) + Value, Month(mDate), Day(mDate)) 
    Case "M" 
      DateItem = DateSerial(Year(mDate), Month(mDate) + Value, Day(mDate)) 
      If Day(DateItem) <> Day(mDate) Then DateItem = DateSerial(Year(DateItem), Month(DateItem), 0) 
      Offset.init DateItem 
    Case "D" 
      Offset.init mDate + Value 
  End Select 
End Function


Conflit de noms

Bien sûr, j'ai voulu doté ma classe des propriétés Day, Month et Year... Mais je me suis heurté à un problème. En créant la propriété Day, j'ai eu un souci...
Code vba : Sélectionner tout
1
2
3
Property Get Day() As Long 
  Day = Day(mDate) 
End Property



Forcément, le VBA cherche la fonction Day "au plus près" du code qui l'utilise, et il la trouve dans la classe elle-même, dans ce cas-ci, alors que moi, à l'intérieur de la propriété, je voudrais utiliser la fonction Day du VBA. Deux solutions s'offrent à nous: Modifier le nom de la propriété ou définir le parent de la fonction Day utilisée à l'intérieur de la propriété Day. La fonction Day est issue de la bibliothèque VBA, on va donc la préfixer pour lever l'ambiguïté:



On aura le même problème avec les propriétés Month et Year, et il faudra également modifier la propriété Offset qui utilise, elle aussi, ainsi que toutes les propriétés déjà construites qui utilisent les fonctions VBA.Year , VBA.Month et VBA.Day...

Code vba : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
Property Get Day() As Long 
  Day = VBA.Day(mDate) 
End Property 
  
Property Get Month() As Long 
  Month = VBA.Month(mDate) 
End Property 
  
Property Get Year() As Long 
  Year = VBA.Year(mDate) 
End Property


Vous pouvez ainsi développer à l'envi des propriétés pour votre objet oDate et enrichir la classe au fur et à mesure de vos besoins:


Conclusions

Il vous suffit d'importer ce module cls dans vos projets comme vous le faites pour vos modules Tools, xlTools et autres, et vous cessez ainsi de réinventer la roue à chaque développement.

Comme vous le voyez, créer une classe personnalisée peut faciliter et systématiser votre écriture du code. A vous d'enrichir petit à petit la classe dont vous trouverez le fichier ci-dessous, ou de créer vos classes pour gérer des durées, etc...

Alors, ça vous donne des idées pour créer vos propres classes personnalisées?

.

Une erreur dans cette actualité ? Signalez-nous-la !