Jour de la semaine en fonction de YY/MM/DD

Bonjour,

Suite aux explications données par @bricoleau sur sa fonction :

uint8_t _RTCcalculerJoursem(uint8_t annee, uint8_t mois, uint8_t jour)
{
  //Formule maison, économe en calculs fin d'épargner le petit processeur 8 bits
  uint8_t somme = annee + (annee >> 2) + jour;
  switch (mois)
  {
	case  2 :
	case  3 :
	case 11 : somme++;    break;
	case  6 : somme += 2; break;
	case  9 :
	case 12 : somme += 3; break;
	case  4 :
	case  7 : somme += 4; break;
    case  1 :
	case 10 : somme += 5; break;
    case  5 : somme += 6;
  }
  if (mois > 2 || (annee & 3)) somme++;
  return somme % 7;
}

Histoire de faire un peu de code j'ai voulu créer une toute petite librairie pour pouvoir calculer à partir de cet algorithme de calcul (celui de bricoleau) le jour de la semaine sur plusieurs siècles et en passant par le moniteur série. Au final j'en ai créer deux :

pour les deux, l'exemple et le header sont identiques :
1/ exemple :

#include "Dow.h"

void setup() {
  Serial.begin(115200);
}

void loop() {
  uint8_t quelleDate[4];
  Serial.println(F("siècle : 4 (1700 à 1799) - 2 (1800 à 1899) - 0 (1900 à 1999) - 6 (2000 à 2099)"));
  Serial.println(F("(4 couvre également de 2100 à 2199 - 0 couvre également de 1582 à 1599"));
  Serial.println(F("2 couvre également de 2200 à 2299 - 6 couvre également de 1600 à 1699)"));
  Serial.println(F("Saisir le siècle :"));
  quelleDate[0] = entree();
  Serial.println(quelleDate[0]);
  Serial.println(F("Saisir le jour :"));
  quelleDate[1] = entree();
  Serial.println(quelleDate[1]);
  Serial.println(F("Saisir le mois :"));
  quelleDate[2] = entree();
  Serial.println(quelleDate[2]);
  Serial.println(F("Saisir l'année :"));
  quelleDate[3] = entree();
  Serial.println(quelleDate[3]);
  Serial.print (quelleDate[1]);  Serial.print (F("/"));
  Serial.print (quelleDate[2]); Serial.print (F("/"));
  if (quelleDate[0] == 4)  Serial.print(F("17"));
  else if (quelleDate[0] == 2)  Serial.print(F("18"));
  else if (quelleDate[0] == 0 ) Serial.print(F("19"));
  else if (quelleDate[0] == 6 ) Serial.print(F("20"));
  (quelleDate[3] < 10) ? Serial.print(F("0")) : Serial.print(F(""));
  Serial.print (quelleDate[3]); Serial.print (F(" = "));
  Dow quelJour (quelleDate[0], quelleDate[1], quelleDate[2], quelleDate[3]);
  quelJour.affiche();
}

uint8_t entree()
{
  char caractere;
  while (!Serial.available());
  uint8_t chiffre = Serial.parseInt();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

2/ header :

#ifndef Dow_h
#define Dow_h
#include <stdint.h>
#include <arduino.h> 

class Dow {
  public:
    // Constructeur
    Dow(uint8_t siecle, uint8_t d, uint8_t m, uint8_t y);
    void affiche(void);
    
    // Destructeur
    ~Dow();

  private:
    uint8_t _siecle;
    uint8_t _d;
    uint8_t _m;
    uint8_t _y;
};

#endif

3/ 1ère version du CPP :

#include "Dow.h"
const char leJour [7][9] PROGMEM = {"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"};

uint8_t dowPlus[12];
bool bissextile (uint8_t s, uint8_t annee) {
  if ((s == 4) && ((annee & 3) == 0) && (annee != 0)) return true; // 1700 à 1799
  else if ((s == 2) && ((annee & 3) == 0) && (annee != 0)) return true; // 1800 à 1899
  else if ((s == 0) && ((annee & 3) == 0) && (annee != 0)) return true; // 1900 à 1999
  else if ((s == 6) && ((annee & 3) == 0))  return true; // 2000 à 2099
  else return  false;
}

void calcul( uint8_t s) {
  dowPlus[0] = (0 + s) % 7;
  dowPlus[1] = (31 + s) % 7;
  dowPlus[2] = (59 + s) % 7;
  dowPlus[3] = (90 + s) % 7;
  dowPlus[4] = (120 + s) % 7;
  dowPlus[5] = (151 + s) % 7;
  dowPlus[6] = (181 + s) % 7;
  dowPlus[7] = (212 + s) % 7;
  dowPlus[8] = (243 + s) % 7;
  dowPlus[9] = (273 + s) % 7;
  dowPlus[10] = (304 + s) % 7;
  dowPlus[11] = (334 + s) % 7;
}

//commun
uint8_t calcDow( uint8_t siecle, uint8_t jour, uint8_t  mois, uint8_t annee) {
  uint8_t dow = annee + (annee >> 2) + jour;
  switch (mois) {
    case  1 : dow += dowPlus[0]; break;
    case  2 : dow += dowPlus[1]; break;
    case  3 : dow += dowPlus[2]; break;
    case  4 : dow += dowPlus[3]; break;
    case  5 : dow += dowPlus[4]; break;
    case  6 : dow += dowPlus[5]; break;
    case   7 : dow += dowPlus[6]; break;
    case  8 : dow += dowPlus[7]; break;
    case  9 : dow += dowPlus[8]; break;
    case  10 : dow += dowPlus[9]; break;
    case 11 : dow += dowPlus[10]; break;
    case 12 : dow += dowPlus[11]; break;
  }
  if ((mois < 3) && (bissextile (siecle, annee)))  dow--; // annee bissextile
  return dow % 7;
}

// Initialisation classe Dow
Dow::Dow(uint8_t siecle, uint8_t d, uint8_t m, uint8_t y)
  : _siecle(siecle),
    _d(d),
    _m(m),
    _y(y)
{}

void Dow::affiche() {
  calcul( _siecle);
  Serial.println((__FlashStringHelper*) leJour[calcDow(_siecle, _d, _m,  _y)]);
  Serial.print('\n');
}

// Destruction
Dow::~Dow() {}

4/ 2ème version du CPP :

#include "Dow.h"
const char leJour [7][9] PROGMEM = {"dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"};
const  uint16_t  base[12] PROGMEM = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
uint16_t dowPlus[12];
uint16_t *pn;

void copie(uint16_t tableauOriginal[], uint16_t tableauCopie[], uint8_t tailleTableau)
{
  for (uint8_t y = 0 ; y < tailleTableau ; y++)
    *(tableauCopie + y) = pgm_read_word_near(tableauOriginal + y);
}

bool bissextile (uint8_t s, uint8_t annee) {
  if ((s == 4) && ((annee & 3) == 0) && (annee != 0)) return true; // 1700 à 1799
  else if ((s == 2) && ((annee & 3) == 0) && (annee != 0)) return true; // 1800 à 1899
  else if ((s == 0) && ((annee & 3) == 0) && (annee != 0)) return true; // 1900 à 1999
  else if ((s == 6) && ((annee & 3) == 0))  return true; // 2000 à 2099
  else return  false;
}

void calcul( uint8_t s) {
  pn = dowPlus;
  for (uint8_t z = 0; z < 12; z++) {
    *pn =  (*pn + s) % 7;
    pn++;
  }
}

//commun
uint8_t calcDow( uint8_t siecle, uint8_t jour, uint8_t  mois, uint8_t annee) {
  pn = dowPlus;
  uint8_t dow = annee + (annee >> 2) + jour;
  for ( uint8_t x = 1 ; x < 13 ; x++) {
    if (mois == x) dow += *pn;
    pn++;
  }

  if ((mois < 3) && (bissextile (siecle, annee)))  dow--; // annee bissextile
  return dow % 7;
}

// Initialisation classe Dow
Dow::Dow(uint8_t siecle, uint8_t d, uint8_t m, uint8_t y)
  : _siecle(siecle),
    _d(d),
    _m(m),
    _y(y)
{}

void Dow::affiche() {
  copie (base, dowPlus, 12);
  calcul( _siecle);
  Serial.println((__FlashStringHelper*) leJour[calcDow(_siecle, _d, _m,  _y)]);
  Serial.print('\n');
}

// Destruction
Dow::~Dow() {}

La deuxième version est "optimisée" (je mets des guillemets car c'est moi qui le pense) mais utilise des tableaux sur 2 octets.
Conclusion :

  • Ce fut un bon entrainement en ce qui me concerne pour coder et pour comprendre ;
  • le code de Sakamoto et la méthode de Zeller sont bien plus efficaces que mes petites librairies mais avec l'inconvénient de travailler sur deux octets si on utilise un AVR 8 bits (tout comme ma deuxième version) ;
  • le code de @bricoleau est particulièrement efficace pour sa librairie et sur AVR, il a l'avantage de ne porter que sur un siècle et ça suffit dans le cadre de son utilisation ...

Voilà ça n'intéressera certainement pas grand monde mais c'est une synthèse pour moi et qui sait même si une seule personne est intéressée par mon travail ça suffira :wink:

Bonne journée.

1 Like

merci pour le partage

Chouette partage

L'étape d'après est de calculer efficacement à quelle date tombe le dernier dimanche de mars, ainsi que le dernier dimanche d'octobre, après quoi la gestion des changements d'heure est très simple.
:crazy_face:

Bonjour

Peut-être hors sujet,
mais juste pour info, parce que on ne pense pas toujours à tous les cas possibles.

Bonjour @J-M-L , @bricoleau et @amic,

J'ai bien conscience que le partage concerne uniquement ceux qui comme moi ne s'étaient pas posé la question du fonctionnement du calendrier Grégorien introduit en 1582 par le pape Grégoire XIII. Je ne faisais que l'utiliser et j'avais oublié ce que j'avais du apprendre à l'école il y a très longtemps :wink:

Ce qui est un partage pour certain est une évidence pour d'autres, j'en suis conscient et il est important de savoir à quel niveau on se situe, à quel niveau je me situe.

Pourquoi pas mais ce sera pour le mois de septembre. Je pars en vacances avec un livre sur le C++. J'ai encore beaucoup à apprendre ... mais c'est sûr je vais me pencher sur la fameuse fonction "_RTCete".

@amic
Oui ce n'est pas simple. Enfin, il s'agit également de se poser des questions et de chercher les réponses. Pour ma part il me manque beaucoup de réponses :wink: mais il y a trop de questions :kissing_closed_eyes:

Bonne journée.

Attention, si on se pose trop de question en voulant être sûr de tout maîtriser avant de créer, on risque de ne plus rien créer.

Enfin c'est mon idée, accepter de laisser une part (raisonnable ... ou pas?) d'inconnues dans la direction que l'on prend pour ne pas rester sur place.

Bonnes vacances.

Hello Philippe,
Merci de partager.
Je te suis avec intérêt.:+1:
Bonnes vacances, studieuses ou pas :grin:

1 Like

Bonjour @jef59

Tu sais à mon âge je ne pars pas dans la bonne direction :smiling_face: Ma place sera au cimetière :joy:
D'ailleurs on prend tous la même direction et c'est d'ailleurs il me semble la seule égalité qui existe entre les hommes mais elle n'est pas parfaite non plus ; j'espère juste que la dernière étape de ma vie se passera sans souffrances :wink:

En attendant profitons des plaisirs de la vie ...
Bonnes vacances à toi aussi si je peux m'exprimer ainsi sachant que nous sommes tous les deux à la retraite et en vacances perpétuelles :wink:

Bonjour Dominique,
Merci pour ta bienveillance :wink:
Je n'oublierai jamais que le premier code, la première direction c'est toi qui me l'a donnée (code de la calculatrice).

Bonne vacances à toi aussi si ce n'est pas déjà fait :wink:

Très sincèrement.
Philippe.

Bonjour à toutes et tous,

Un grand merci @philippe86220 et @bricoleau pour cette petite routine compacte et efficace.
J'aurais juste une petite remarque concernant les mois : la routine prend les mois de 1 à 12, alors que les mois des routines C (membre tm_mon de struct tm) vont de 0 à 11...
Pour limiter la casse en cas de confusion, je suggèrerai de modifier la routine comme suit:

uint8_t _RTCcalculerJoursem(uint8_t annee, uint8_t mois, uint8_t jour)
{
  //Formule maison, économe en calculs fin d'épargner le petit processeur 8 bits
  uint8_t somme = annee + (annee >> 2) + jour;
  switch (mois)
  {
	case  6 : somme += 2; break;
	case  9 :
	case 12 : somme += 3; break;
	case  4 :
	case  7 : somme += 4; break;
    case  1 :
	case 10 : somme += 5; break;
    case  5 : somme += 6; break;
	default : somme++;    break;
  }
  if (mois > 2 || (annee & 3)) somme++;
  return somme % 7;
}

Si on passe "0", le calcul sera faux, mais au moins ça ne partira pas dans le décors.

A+ et bonnes vacances à ceux qui partent !

MicroQuettas

PS : j'ai une routine pour le calcul des bornes heure d'été /heure d'hiver, mais d'une part ce n'est pas complètement simple, d'autre part je n'ai pas noté ma source...

Bonjour @MicroQuettas
Concernant mes librairies :
Tu peux gérer le jour et le mois au niveau du déboggage , vite fait ça peut donner ça au niveau des mois et jours uniquement mais on peut contrôler d'autres formats d'entrées :

#include "Dow.h"

void setup() {
  Serial.begin(115200);
}

void loop() {
  uint8_t quelleDate[4];
  Serial.println(F("siècle : 4 (1700 à 1799) - 2 (1800 à 1899) - 0 (1900 à 1999) - 6 (2000 à 2099)"));
  Serial.println(F("(4 couvre également de 2100 à 2199 - 0 couvre également de 1582 à 1599"));
  Serial.println(F("2 couvre également de 2200 à 2299 - 6 couvre également de 1600 à 1699)"));
  Serial.println(F("Saisir le siècle :"));
  quelleDate[0] = entree();
  Serial.println(quelleDate[0]);
  Serial.println(F("Saisir le jour :"));
  quelleDate[1] = entree();
  Serial.println(quelleDate[1]);
  Serial.println(F("Saisir le mois :"));
  quelleDate[2] = entree();
  Serial.println(quelleDate[2]);
  Serial.println(F("Saisir l'année :"));
  quelleDate[3] = entree();
  Serial.println(quelleDate[3]);
  Serial.print (quelleDate[1]);  Serial.print (F("/"));
  Serial.print (quelleDate[2]); Serial.print (F("/"));
  if (quelleDate[0] == 4)  Serial.print(F("17"));
  else if (quelleDate[0] == 2)  Serial.print(F("18"));
  else if (quelleDate[0] == 0 ) Serial.print(F("19"));
  else if (quelleDate[0] == 6 ) Serial.print(F("20"));
  (quelleDate[3] < 10) ? Serial.print(F("0")) : Serial.print(F(""));
  Serial.print (quelleDate[3]); Serial.print (F(" = "));
  if (quelleDate[1] != 0 && quelleDate[2] != 0) {
    Dow quelJour (quelleDate[0], quelleDate[1], quelleDate[2], quelleDate[3]);
    quelJour.affiche();
  }
  else Serial.println("erreur de saisie !");
  Serial.print('\n');
}

uint8_t entree()
{
  char caractere;
  while (!Serial.available());
  uint8_t chiffre = Serial.parseInt();
  while (Serial.readBytes(&caractere, 1) != 0) {
    if (caractere == '\r') {
      break;
    }
  }
  return chiffre;
}

Pour les valeurs de 0 à 11 tu les passes en paramètre avec +1.

Concernant la routine de bricoleau, tu dois la considérer par rapport à sa librairie et le problème que tu évoques n’existe pas :wink:

Bonne journée
PS : Tout est possible en programmation, c'est ce qui en fait le charme ...

Je ne suis pas le concepteur de la routine, rendons à César ce qui lui appartient. Je l'ai juste comprise ...

Merci.

Tu sais, quand je pars en vélo me balader, souvent je ne sais pas où je vais partir, et c'est comme cela que je découvre de nouveau chemin (heureusement j'ai le GPS pour revenir.

Bon, ça m'a valu quelques épiques ballades quand je vivais à Lyon et que je partais à "l'aventure" vers quelques quartiers improbables où au détour d'une ruelle, perdu dans une impasse, tu dérange quelques jeunes commerçants.

Maintenant je dérange plutôt les lapins, cervidés, ragondins, rapaces splendides (et malheureusement rarement des renards par ici).
Mais rien de bien grave.

C’est sympa de partir à l’aventure :wink:
Bonne journée

Bonjour,

Prenons le postulat de @bricoleau :

Analysons le code :

void setup() {
  Serial.begin(115200);
  Serial.print(" dernier dimanche MARS 2022 : "); Serial.println(dernierDimancheM(22));
  Serial.print(" dernier dimanche MARS 2000 : "); Serial.println(dernierDimancheM(0));
  Serial.print(" dernier dimanche OCTOBRE 2000 : "); Serial.println(dernierDimancheO(0));

}

void loop() {
  // put your main code here, to run repeatedly:

}
uint8_t dernierDimancheM(uint8_t y) {
  uint8_t dernierDimancheMars = 31 - (( 5 + y  + (y >> 2)) % 7);
  return dernierDimancheMars;

}

uint8_t dernierDimancheO(uint8_t y) {
  uint8_t dernierDimancheMars = 31 - (( 2 + y  + (y >> 2)) % 7);
  return dernierDimancheMars;

}

C'est celui de @bricoleau bien sûr et ici je me suis attaché à déterminer le dernier dimanche de mars et d'octobre pour les années 2000 ainsi que le dernier dimanche pour mars 2022. Analysons le code pour mars :
31 - (( 5 + y + (y >> 2)) % 7);

  • 31 constitue le nombre de jours de mars ;
  • 5 c'est le décalage qui existe entre le dernier jour de mars 2000 et le dernier dimanche de mars 2000 : 31 - 26. Et oui on part de l'année 2000 ;
  • y c'est le nombre d'années mais aussi le nombre de jours de décalage pour arriver à l'année recherchée. Rappelez vous :

Le " et 1 jour" implique qu'entre deux années consécutives les semaines sont décalées d'un jour
Ainsi :

  • aujourd'hui 19 juillet 2024 est un vendredi
  • le 19 juillet 2025 sera un samedi
  • le 19 juillet 2026 sera un dimanche
  • le 19 juillet 2027 sera un lundi
  • (y >> 2) qui équivaut à (y /4) (ici il s'agit de la division entière) en plus rapide, constitue le nombre de jours supplémentaires qu'il faut ajouter à cause des année bissextiles (du 1er janvier 2000 au 31 décembre 2099 tous les 4 ans nous avons une année bissextile). Rappelez-vous :

Et il faut tenir compte des années bissextiles en décalant d'un jour supplémentaire si un 29 février est venu se glisser dans la période comptabilisée.
Ainsi, l'an dernier, le 19 juillet 2023 n'était pas un jeudi mais un mercredi puisque c'était il y a 366 jours.

Donc en résumé, calculons le dernier dimanche de mars 2024 :
31 jours de mars 2024 moins :
1/ le point de départ du dernier dimanche de mars 2000 : 5 jours ;
2/ plus le nombre de jours de décalage (consécutif au nombre d'années écoulées ) : 24 ;
3/ plus le nombre de jours à ajouter suite aux années bissextiles écoulées depuis mars 2000 : 6 (2004, 2008, 2012, 2016, 2020, 2024)

  • le tout modulo 7 (1/, 2/, 3/)
    Ce qui donne 31 - ((5 + 24 + 6) %7) = 31 - 0 = 31. 31 mars 2024 est le jour du dernier dimanche de mars 2024 ...

Pour 2022 : 31 - ((5 + 22 + 5)%7) = 31 - (32%7) = 31 - 4 = 27.

Enfin pour le dernier dimanche d'octobre c'est la même chose sauf que l'on part du dernier dimanche d'octobre 2000 qui est le 29 octobre 2000 => 31 - 29 = 2

soit :
31 - (( 2 + y + (y >> 2)) % 7);

Bonne journée.

Le mot de la fin pour clôturer ce fil de discussion :

Au final comprendre un algorithme c'est pas trop compliqué :wink:
Par contre le concevoir est bien plus difficile ...

Bravo @bricoleau
Bonne journée et bonne vacances

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.