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?
.