Corrigé sujet 3 / Épreuve pratique NSI 2026 : dates, tuples et assertions en Python
Le sujet 3 de l'épreuve pratique NSI 2026 modélise le cycle menstruel avec des tuples de dates. Années bissextiles, assertion, écriture de tests, et un format iCalendar à réparer : la correction complète, question par question, avec les fichiers.
Le sujet 3 de l'épreuve pratique NSI 2026, aussi appelée ECE de NSI, te fait construire une petite application de suivi du cycle menstruel. Tu y manipules des dates sous forme de tuples d'entiers, tu écris une assertion, tu complètes une fonction de test, et tu répares une chaîne de caractères au format iCalendar qu'un agenda en ligne refuse. C'est un sujet zéro, donc un des plus travaillés en classe, et tu disposes d'une heure.
On te demande une fonction est_bissextile qui reçoit une année sous forme d'entier et renvoie un booléen, en appliquant la règle donnée dans l'énoncé.
Le truc, c'est que l'énoncé te donne la règle "en français" comme on l'expliquerait à un humain...
Une année est bissextile si elle est divisible par 4 à l'exception des années divisibles par 100 sauf si ce sont des multiples de 400.
Mais toi, tu dois "l'expliquer" à une... machine ! Et ça change tout. Pourquoi ? Parce qu'en informatique on a une structure de type "si... alors... sinon..." et pas "ça... à l'exception de ça... sauf si ça...".
Comme on ne peut pas rédiger en mode "exception", on va renverser la phrase pour la dire en mode "si... alors..." :
Si une année est un multiple de 400, alors elle est bissextile. Sinon, si elle est un multiple de 100 alors elle n'est pas bissextile. Sinon, si elle est un multiple de 4 alors elle est bissextile, sinon elle n'est pas bissextile.
J'avoue que la formulation est plus lourde comme ça, mais là : on a une structure que l'on peut directement traduire en Python, pour la machine !
La lecture de la condition suit exactement la phrase de l'énoncé : divisible par 4, et en plus, soit pas divisible par 100, soit divisible par 400. Les parenthèses ne sont pas décoratives : en Python, and est prioritaire sur or, donc sans elles la condition change de sens et 2000 serait mal classée.
Bien sûr, on teste son code avec les exemples donnés dans l'énoncé :
Mal traduire la phrase en français de l'énoncé dans une forme directement utilisable par la machine.
Renvoyer "oui" ou 1 ou true au lieu d'un booléen. L'énoncé dit « renvoie un booléen » : c'est une consigne, pas une indication de style.
Ne pas tester son code ! L'énoncé te donne des exemples, profites-en !
Question 2 / Phase du cycle
Ici, tu écris determiner_phase, qui reçoit un jour du cycle compris entre 1 et 28 et renvoie le numéro de la phase correspondante. L'énoncé demande en plus de garantir par une assertion que l'argument est bien dans cet intervalle.
Les bornes sont dans le tableau de l'énoncé :
Phases d'un cycle menstruel
Phase
Numéro
Jours du cycle
Description
Règles
1
1 à 5
Écoulement menstruel
Phase folliculaire
2
6 à 13
Développement d'un follicule dans l'ovaire
Ovulation
3
14
Libération de l'ovocyte 1
Phase lutéale
4
15 à 28
Développement de la muqueuse utérine
L'assertion se place en toute première ligne, avant le moindre calcul : son rôle est d'arrêter la fonction si l'hypothèse de travail est fausse. Une assertion écrite après le calcul ne protège plus rien.
La fonction que tu vas écrire va comporter des tests if. Le plus simple est d'enchaîner les conditions dans l'ordre croissant et dans ce cas, tu n'as besoin que de la borne haute de chaque phase : si le jour n'est pas inférieur ou égal à 5, c'est qu'il est supérieur ou égal à 6, le elif suivant n'a donc pas à le revérifier.
Python
defdeterminer_phase(jour):
assert1 <= jour <= 28, "le jour du cycle doit être compris entre 1 et 28"if jour <= 5:
return1elif jour <= 13:
return2elif jour == 14:
return3else:
return4
Le message après la virgule dans l'assertion est facultatif mais il montre à l'examinateur que tu sais à quoi sert une assertion. La double comparaison 1 <= jour <= 28 est valide en Python et se lit comme en mathématiques.
Et comme toujours... on teste son code ! Et pour cela on peut utiliser une boucle for, comme ça on est sûr de passer en revue tous les cas :
Python
for i inrange(1, 29):
print("Jour",i,"... phase:",determiner_phase(i))
Et observer ce qui se passe aux bornes des intervalles (les symboles -/- indiquent que des lignes qui sont bien présentes à l'affichage ont été omises dans ce bloc de code) :
Code
Jour 1 ... phase: 1
-/-
Jour 5 ... phase: 1
Jour 6 ... phase: 2
-/-
Jour 13 ... phase: 2
Jour 14 ... phase: 3
Jour 15 ... phase: 4
-/-
Jour 28 ... phase: 4
Les pièges classiques
Arriver à la question 2 et oublier le début de l'énoncé, notamment le tableau qui te sert à écrire la fonction determiner_phase.
Oublier l'assertion. Elle est explicitement demandée dans l'énoncé, c'est une partie de la question, pas un bonus.
Utiliser un if qui renvoie un message d'erreur à la place de l'assertion. Ce n'est pas la même chose : assert interrompt l'exécution, un print ou un return la poursuivent avec une valeur invalide.
Se tromper d'une unité sur les bornes, typiquement classer le jour 14 en phase 2 ou le jour 15 en phase 3. Vérifie les six valeurs frontières : 1, 5, 6, 13, 14, 15.
Ne pas tester son code !
Question 3 / Compléter les tests
La fonction ajouter_jours t'est fournie : elle prend une date sous forme de tuple et un nombre de jours, et renvoie la date obtenue. Tu dois compléter test_ajouter_jours avec au moins trois tests supplémentaires, chacun accompagné d'une justification.
Le mot important de l'énoncé est « pertinents ». Qu'est-ce qui pourrait bien être « pertinent » lorsqu'on ajoute un certain nombre de jours à une date ?
Si on examine le code de la fonction ajouter_jours on peut en avoir une petite idée. On voit dans le code qu'il y a une histoire de mois et d'années.
En effet, si tu ajoutes 2 jours au 29 juin, tu vas changer de mois et atterrir au 1er juillet. Et si tu ajoutes 2 jours au 30 décembre 2025, tu dois obtenir le 1er janvier 2026.
On peut donc dresser ce tableau de tests. Je t'encourage, en situation d'examen, à te l'écrire au brouillon avant de coder, cela peut t'aider à bien mettre à plat tes idées et à calculer chaque résultat attendu à la main avant de lancer le test. Si tu écris les tests sans avoir vérifié ce qu'ils sont censés produire, tu ne testes plus rien.
#
Test
Date départ
nb jours
Date arrivée
À faire ?
1
Test simple
7 septembre 2025
3
10 septembre 2025
non : il est déjà donné dans l'énoncé
2
Changement de mois (30 jours)
20 septembre 2025
13
3 octobre 2025
obligatoire
3
Changement de mois (31 jours)
29 mai 2026
4
2 juin 2026
facultatif : mécanique similaire au test #2
4
Changement de mois (28 jours)
27 février 2026
5
4 mars 2026
obligatoire
5
Changement de mois (29 jours)
27 février 2024
5
3 mars 2024
obligatoire
6
Changement d'année
30 décembre 2025
2
1er janvier 2026
obligatoire
Si l'on devait n'en choisir que 3, je te suggérerais les tests numéros 2, 4 et 6. Mais je te recommande quand même fortement le 5, pour tester si cela fonctionne correctement avec les années bissextiles.
Dans le code ci-dessous je fais les 5 nouveaux :
Python
deftest_ajouter_jours():
# 1. Test simple - déjà donné dans l'énoncéassert ajouter_jours((7, 9, 2025), 3) == (10, 9, 2025)
# 2. Changement de mois : mois de 30 jours (septembre)assert ajouter_jours((20, 9, 2025), 13) == (3, 10, 2025)
# 3. Changement de mois : mois de 31 jours (mai)assert ajouter_jours((29, 5, 2026), 4) == (2, 6, 2026)
# 4. Changement de mois : février à 28 jours en 2026assert ajouter_jours((27, 2, 2026), 5) == (4, 3, 2026)
# 5. Changement de mois : février à 29 jours en 2024assert ajouter_jours((27, 2, 2024), 5) == (3, 3, 2024)
# 6. Changement d'annéeassert ajouter_jours((30, 12, 2025), 2) == (1, 1, 2026)
Les pièges classiques
Écrire les trois tests dans le même mois. Ils sont justes, mais ils ne vérifient qu'un seul chemin d'exécution : c'est le sens du mot « pertinents ».
Sauter les justifications. L'énoncé demande une brève explication par test ajouté ; un commentaire d'une ligne au-dessus de chaque assert suffit.
Tester un nombre de jours négatif. La boucle while ne s'exécute alors jamais et la fonction peut renvoyer un jour nul ou négatif. C'est un vrai défaut, mais il sort du domaine d'usage prévu : le jour de l'épreuve, ce test échoue et te fait perdre du temps sur une question qui ne le demande pas.
Question 4 / Un calendrier au format invalide
Dernière question, la seule où tu dois diagnostiquer avant de corriger. La fonction calendrier_cycles produit une chaîne au format iCalendar, et test_calendrier_cycles la fait relire par la bibliothèque ics, qui la refuse.
Commence par afficher ce que la fonction renvoie réellement, pour la date du 12 mars 2026 par exemple. Tu obtiens des lignes de la forme DTSTART:2026312, puis DTSTART:202649. Compare maintenant avec la règle de l'énoncé : la valeur de DTSTART s'écrit AAAAMMJJ et fait toujours huit caractères, les nombres inférieurs à 10 étant complétés par un zéro. Ici, la première valeur en fait sept et la deuxième six. Le 9 avril 2026 devrait s'écrire 20260409, pas 202649.
La cause est la ligne qui construit la chaîne : str(annee) + str(mois) + str(jour) colle les trois nombres sans jamais les compléter. La démarche de résolution consiste donc à formater chaque composante sur sa largeur fixe, deux chiffres pour le mois et pour le jour. Pour cela, on va tester le mois et s'il est inférieur ou égal à 9 on ajoute un zéro à la chaîne de caractères produite. Et pareil pour le jour. Bien sûr, cela va nous obliger à définir de nouvelles variables :
Python
if mois <= 9:
mois_str = "0" + str(mois)
else:
mois_str = str(mois)
if jour <= 9:
jour_str = "0" + str(jour)
else:
jour_str = str(jour)
date = str(annee)+mois_str+jour_str
Avec cette version, le calendrier produit pour le 12 mars 2026 contient bien 20260312, 20260409 et 20260507, et la bibliothèque le relit sans erreur.
Les pièges classiques
Chercher une erreur dans le code sans avoir affiché à l'écran le résultat de la fonction calendrier_cycles.
Se précipiter sur une erreur que l'on a cru voir dans le résultat de la fonction calendrier_cycles et ne pas passer en revue toutes les règles du format iCalendar que l'énoncé explique.
Modifier test_calendrier_cycles au lieu de calendrier_cycles. Le test n'est pas en cause, il fait son travail en signalant le problème.
Ne compléter que le mois, ou ne compléter que le jour. Les deux composantes sont concernées, et une seule des deux corrigée laisse le format faux un jour sur deux.
Récapitulatif des notions
représentation d'une date par un tuple d'entiers
opérateur modulo et tests de divisibilité
expression booléenne, priorité de and sur or
assertion comme garantie d'une hypothèse en entrée de fonction
écriture de tests pertinents, un par chemin d'exécution
formatage de chaînes de caractères sur une largeur fixe
Si tu prépares l'épreuve pratique et que certains de ces réflexes te manquent encore, un accompagnement en NSI permet de les installer avant le jour J.