Skip to content

Correction des timezones des RDV dans l’API#6207

Draft
AntoineGirard wants to merge 1 commit intoproductionfrom
agd/fix-rdv-timezone-api
Draft

Correction des timezones des RDV dans l’API#6207
AntoineGirard wants to merge 1 commit intoproductionfrom
agd/fix-rdv-timezone-api

Conversation

@AntoineGirard
Copy link
Member

Closes #6202

Contexte

Le contexte complet est dans l’issue ci-dessus ☝🏻

Solution

@francois-ferrandis, je sais que cette PR va nous ramener à notre fameuse discussion sur comment on stocke les fuseaux horaires en base. Je sais ce que tu penses, et je te comprends, mais j’ai l’impression qu’ici, on peut fournir une solution simple à nos agents guadeloupéens.
Qu’en penses-tu ?

Copy link
Collaborator

@francois-ferrandis francois-ferrandis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merci Antoine d'avoir anticipé que le sujet allait me tenir à cœur. ❤️

Cette première proposition, si j'ai bien tout compris, ne corrige pas le problème. J'ai expliqué en commentaire ma compréhension du sujet et proposé des tests et une implémentation.

J'ai aussi quelques suggestions moins importantes (preloading et RdvPlanBlueprint).

Comment on lines +107 to +125

describe "utilise la bonne timezone" do
context "lorsque la timezone de l'organisation n'est pas définie" do
it "utilise la timezone de l'instance" do
get "/api/v1/rdvs", headers: headers, params: { user_id: user.id, agent_id: agent.id }, as: :json
expect(parsed_response_body["rdvs"].first["starts_at"]).to eq rdv_with_user_and_agent.starts_at.to_s
expect(parsed_response_body["rdvs"].first["ends_at"]).to eq rdv_with_user_and_agent.ends_at.to_s
end
end

context "lorsque la timezone de l'organisation est définie" do
let(:organisation) { create(:organisation, time_zone: "America/Guadeloupe") }

it "utilise la timezone de l'organisation" do
get "/api/v1/rdvs", headers: headers, params: { user_id: user.id, agent_id: agent.id }, as: :json
expect(parsed_response_body["rdvs"].first["ends_at"]).to eq rdv_with_user_and_agent.ends_at.in_time_zone("America/Guadeloupe").to_s
end
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ces tests n'explicitent pas le comportement attendu, mais vérifient uniquement que l'on utilise in_time_zone.

Est-on d'accord que le comportement attendu est celui-ci ?

    describe "utilise la bonne timezone" do
      context "lorsque la timezone de l'organisation n'est pas définie" do
        it "utilise la timezone de l'instance" do
          rdv = create(:rdv, starts_at: ActiveSupport::TimeZone["Paris"].parse("2026-02-28 14:30"), organisation:, agents: [agent])
          get "/api/v1/rdvs", headers: headers, params: { agent_id: agent.id }, as: :json
          api_rdv = parsed_response_body["rdvs"].find { _1["id"].to_i == rdv.id }
          expect(api_rdv["starts_at"]).to eq "2026-02-28 14:30:00 +0100"
          expect(api_rdv["ends_at"]).to eq "2026-02-28 15:15:00 +0100"
        end
      end

      context "lorsque la timezone de l'organisation est définie" do
        let(:organisation) { create(:organisation, time_zone: "America/Guadeloupe") }

        it "utilise la timezone de l'organisation" do
          rdv = create(:rdv, starts_at: ActiveSupport::TimeZone["Paris"].parse("2026-02-28 14:30"), organisation:, agents: [agent])
          get "/api/v1/rdvs", headers: headers, params: { agent_id: agent.id }, as: :json
          api_rdv = parsed_response_body["rdvs"].find { _1["id"].to_i == rdv.id }
          expect(api_rdv["starts_at"]).to eq "2026-02-28 14:30:00 -0400"
          expect(api_rdv["ends_at"]).to eq "2026-02-28 15:15:00 -0400"
        end
      end
    end


field :starts_at do |rdv, _options|
if rdv.motif.organisation.time_zone
rdv.starts_at.in_time_zone(rdv.motif.organisation.time_zone)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cette implémentation continue de retourner à peu près la même valeur fausse, simplement sous une autre forme.

ActiveSupport::TimeWithZone#in_time_zone a pour effet de convertir un instant d'une timezone à une autre. Cela signifie que si un agent guadeloupéen prend un RDV à 14h30 aujourd'hui, nous stockons 2026-02-28 14:30 CET +01:00 en base, c'est à dire que nous stockons en réalité 2026-02-28 13:30 UTC.

Avec ce nouveau code, nous allons avoir cet effet :

Time.zone.parse("2026-02-28 14:30").in_time_zone("America/Guadeloupe").to_s
=> "2026-02-28 09:30:00 -0400"

Cette valeur correspond au même instant erroné que celui que l'API renvoie actuellement, juste formaté pour exprimer qu'il a lieu en -0400. Et donc le serveur de la coop va l'interpréter exactement de la même façon qu'actuellement, c'est à dire à 9h30 du matin en Guadeloupe.

Ce que nous voulons faire plutôt, c'est savoir que nous stockons en base une heure naïve, et nous le faisons en utilisant l'heure locale de Paris. Pour convertir cette heure naïve en heure locale guadeloupéenne, il faut l’interpréter naïvement dans la timezone guadeloupéenne :

ActiveSupport::TimeZone["America/Guadeloupe"].parse("2026-02-28 14:30").to_s
=> "2026-02-28 14:30:00 -0400"

Nous avons bien une représentation de l'instant auquel le RDV a lieu : aujourd'hui à 14h30 en Guadeloupe.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu as raison, je suis allé beaucoup trop vite sur le sujet, et j’ai pondu ce truc erroné.
J’étais biaisé parce que dans les ICS on avait « juste » changé la tzid, mais effectivement ici, il faudrait faire ce que tu proposes juste au-dessus idéalement.
Désolé d’avoir envoyé ce truc beaucoup trop vite
Je vais corriger quand j’aurai un peu de temps

end

field :starts_at do |rdv, _options|
if rdv.motif.organisation.time_zone
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question de perf : est-ce que le motif et l'orga est préloadé ? D'ailleurs on peut direct choper l'orga car les Rdv on un organisation_id.

@@ -1,7 +1,7 @@
class RdvBlueprint < Blueprinter::Base
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il faudra aussi modifier RdvPlanBlueprint qui retourne rdv.starts_at. Peut-être que ça incite à mettre ces logiques de gestion de timezone dans un module séparé qui explique le problème.

@github-project-automation github-project-automation bot moved this from 🔖 Ready to 👀 Needs review in RDV Service Public Feb 28, 2026
@AntoineGirard AntoineGirard marked this pull request as draft February 28, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: 👀 Needs review

Development

Successfully merging this pull request may close these issues.

corriger les timezones publiées via notre API

2 participants