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:
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:
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...
Ç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.
OptionExplicitDim mDate AsDateFunction init(ByVal Value AsDate)As oDate
mDate = Value
Set init = MeEndFunctionPrivateSub Class_Initialize()
mDate = DateEndSub
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.
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...
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:
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...
PropertyGet Day()AsLong
Day = Day(mDate)EndProperty
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...
PropertyGet Day()AsLong
Day = VBA.Day(mDate)EndPropertyPropertyGet Month()AsLong
Month = VBA.Month(mDate)EndPropertyPropertyGet Year()AsLong
Year = VBA.Year(mDate)EndProperty
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?