{"id":2228547,"url":"http://patchwork.ozlabs.org/api/1.2/patches/2228547/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/patch/CAF8dVMVGR-jqHMvX3--mXDXSCJ6+FFvc-R=zbdyM34Vh13co7Q@mail.gmail.com/","project":{"id":17,"url":"http://patchwork.ozlabs.org/api/1.2/projects/17/?format=json","name":"GNU Compiler Collection","link_name":"gcc","list_id":"gcc-patches.gcc.gnu.org","list_email":"gcc-patches@gcc.gnu.org","web_url":null,"scm_url":null,"webscm_url":null,"list_archive_url":"","list_archive_url_format":"","commit_url_format":""},"msgid":"<CAF8dVMVGR-jqHMvX3--mXDXSCJ6+FFvc-R=zbdyM34Vh13co7Q@mail.gmail.com>","list_archive_url":null,"date":"2026-04-26T23:55:56","name":"[v2,4/5] libstdc++: Cascade wall-time saves in lazy expansion seeding [PR124853]","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"5e9e86e0360fc8e8d3e5d51ba4847f03425ce6eb","submitter":{"id":93119,"url":"http://patchwork.ozlabs.org/api/1.2/people/93119/?format=json","name":"Álvaro Begué","email":"alvaro.begue@gmail.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/gcc/patch/CAF8dVMVGR-jqHMvX3--mXDXSCJ6+FFvc-R=zbdyM34Vh13co7Q@mail.gmail.com/mbox/","series":[{"id":501555,"url":"http://patchwork.ozlabs.org/api/1.2/series/501555/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/list/?series=501555","date":"2026-04-26T23:42:54","name":"libstdc++: chrono tzdb correctness fixes","version":2,"mbox":"http://patchwork.ozlabs.org/series/501555/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2228547/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2228547/checks/","tags":{},"related":[],"headers":{"Return-Path":"<gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org>","X-Original-To":["incoming@patchwork.ozlabs.org","gcc-patches@gcc.gnu.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","gcc-patches@gcc.gnu.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=Ll3tKg1k;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org\n (client-ip=38.145.34.32; helo=vm01.sourceware.org;\n envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=Ll3tKg1k","sourceware.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com","sourceware.org; spf=pass smtp.mailfrom=gmail.com","server2.sourceware.org;\n arc=pass smtp.remote-ip=209.85.217.52"],"Received":["from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4g3kC71PTZz1yJX\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 27 Apr 2026 09:57:23 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 5B7D84BB1C31\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 26 Apr 2026 23:57:21 +0000 (GMT)","from mail-vs1-f52.google.com (mail-vs1-f52.google.com\n [209.85.217.52])\n by sourceware.org (Postfix) with ESMTPS id 685094BB24D2\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 23:56:33 +0000 (GMT)","by mail-vs1-f52.google.com with SMTP id\n ada2fe7eead31-60fecdd1efaso2936843137.3\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 16:56:33 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 5B7D84BB1C31","OpenDKIM Filter v2.11.0 sourceware.org 685094BB24D2"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 685094BB24D2","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 685094BB24D2","ARC-Seal":["i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1777247793; cv=pass;\n b=WlKG5QFHlfG2Ja2e+rSOCp19h29rw5yYgnOT9POUtTTUov0k/+Br2iw4KG32zNs5ej7AvaEmobWDDGYRyTdRrba+6wqRdp75Br2tPYLdA91wWsWI/Ht4pDeQaPxK9eXdEX1WAKE+1DL/rsVPuD4EL7dVtMfYKbX8Skk8AKLJTHg=","i=1; a=rsa-sha256; t=1777247793; cv=none;\n d=google.com; s=arc-20240605;\n b=OClzcAWeRN5viG/UU5HV8UmBqETqGpSAd4Dv9f83UYDxj7kcIJ/bxNUJtEdqrrXCjK\n zXSlXA+CsfWrMO5qNvwAF3tbfPlMHJmQwWOhgvxlgECVJ7ca6LKhcT0Ykg4Us78ibpMo\n jS66/g39fw8QYsX3A9tQZ3rtFkktzG5nRW8DkPZIzi82yBqPimMNqymtsw9hJPUfVu4W\n M5OyuK6IV7BmH6M4yrVjTlWws+08cUAShQVdQJ27r/LO7xkVVL+uHbklWQgQJuPVauQW\n qDG46gVIT2K04F9QPb6zlyxkTqPqbslWwhg/ej5ad2w1sqL2FYZ5dgMZmqBSUlml/rkg\n vsTQ=="],"ARC-Message-Signature":["i=2; a=rsa-sha256; d=sourceware.org; s=key;\n t=1777247793; c=relaxed/simple;\n bh=0vb3H8cY0ylFpDw6sPwkswayq+1MmuTcIhoyJUhcOxw=;\n h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To;\n b=ujEFhOL0RTU3WW355R+3of5cxWxQ6XhRWI93s0T59P6L0qb3tnt487iZ2qlPmsD9ATBRiMbAHt4g2dSCVUCVA3mGzWRRPBjjML8/1SPCK55VLzHHPM5uc+uRiIHFXjMaXha+U3pOBjMCZBmLJpf3BfD6lpIGArQPjt33P6nclLs=","i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com;\n s=arc-20240605;\n h=cc:to:subject:message-id:date:from:mime-version:dkim-signature;\n bh=Fvl7A+VhnmigpH0zBHPyBEwEGoQ4y4XtiRwQSbJP2pQ=;\n fh=sWbBxiX87E8F1zPxXVV3X4tQqxV9sZLoQvmjvveliwE=;\n b=KIBQHfGXuBkXlLqu5WJTD5Vtbua0chWwH3Rt05+Z2Nc99t5zFBOXKFkE7t+Wona30h\n wlwVLk+c5jQenpJIOmygiHhUGn8cNwXTDbBdv9UQOJsEuL5FKKirNjf5xqDK/WsOqKPG\n xL1rcwxFSuxPcsgSAHjKgX9ogLNPVy2YD4AZD0ykxw2Phb4vycFycvJ6gmlV52WTT8ha\n T6PlFtamZniLYy42gIx3YeJFhbc9kbG9fg1X2mYRL7ycs8bFhiKTxQxE4yPERKP2YRWU\n +aPUmA20cntwEHgF63Gojcw5W48UToqwIg8dbKEl5kiSsoh85cibsvuwbX/pSXmV5vB0\n A5vg==; darn=gcc.gnu.org"],"ARC-Authentication-Results":["i=2; server2.sourceware.org","i=1; mx.google.com; arc=none"],"DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1777247793; x=1777852593; darn=gcc.gnu.org;\n h=cc:to:subject:message-id:date:from:mime-version:from:to:cc:subject\n :date:message-id:reply-to;\n bh=Fvl7A+VhnmigpH0zBHPyBEwEGoQ4y4XtiRwQSbJP2pQ=;\n b=Ll3tKg1kbPXFAJZ+pcr+FohwFLaUeDLLD63I2cuQtjmWy9eF5Hm3lB6BwKztT1+WJ5\n JVpkSXOPr1NMQ7ZutTcr16/xTlOUIXZq1AurFTQwJ8L1aVAP3lm2DtFjjV3x1yINdRxE\n dYY7wQRWCnVAccpd2TmkkvPLOUWZte2/XQiAetO9Cb2fITdKzzaREMIEqUJaS6NmrGDp\n aTipFhFi7KIOo+PpGlS4Ok6ZkfD1YlJ/sNJ4/Gpg02safq0zgrFFxhgNVmMr+FMSEnVR\n uWaEVzFiGfcBfbCFkpxwbtGG+KY70/oYOAdLnbYN1YdC7UZtQkOx78clYsuvb+IJy00J\n /nOg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777247793; x=1777852593;\n h=cc:to:subject:message-id:date:from:mime-version:x-gm-gg\n :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n bh=Fvl7A+VhnmigpH0zBHPyBEwEGoQ4y4XtiRwQSbJP2pQ=;\n b=BFVZe4wT+qvAMbCDm6lwyAww5apc5Y9UztCLTa5A0dPtY0WkDnZpQ9l+c2y/iysyqS\n TCp53tqSeXQqX2q0RLAcfGKQflTWApgMLbZnTrujP/9+kYKK2z0AlAonMrh9Y9p/EC56\n tmPcDZfm8YQKvJYGLkENP3deqtwOl0dXVUmWcNX8Z7mkb4A+8Xe3hhPbOu/YBrdhKNSD\n bbmiyh0wpw77Y5duauH5229OdbmOQIaHl6BQVPOQTogUtAzSJfYg4CBn1VlXf18QKDE6\n PHYoogGzQUDUpJt2puClFWtnz/o7KUreLRynj8mJ6AlpjFMkywC7BCkXvm6ghuUYYQx9\n YWKA==","X-Gm-Message-State":"AOJu0YwHzM4xXpWYIcHrgeUi1zXA8jn6Ksvqn2d/E8uPVZJYrQ8YcIAu\n qNRV+VFNRfaONuI1T6w25ttc+zvhbSTWtBlzuGAhiGs7VMhVSze89xeiBOGcko21NtEv+gNW/XV\n rhQ9zpFedcoMB8Gi8zPduAUR3pKgIS/ALjQqTPWg=","X-Gm-Gg":"AeBDiev1iVfh5mMZF+nLiNyF+bwXLipHvg6dbvBJW8BnzPaNu6e/sVAzacrV7cNiOhJ\n R3rSwDzoudtGpYNtr3L4wBPq+ZeyzOsp1l1f/D2ED7UqO76lkHkTWLrytZaTplhGaU5CNfiwUup\n 2vo2AtEWfcjHBb9GI6Ru3FBkXGRQ6yYVWl+Idb8YkDfRsMC65z1Zt4l3iAjrPDv68MrMN5Rph9V\n hASgr39dn3TJFKbrJzAngjJyeAlNgHflLP0yR2tlV9kznLl+biyDks1ENRWVH1+e/nH1C2dQAHS\n 05kYODN+8nwrpbnGKRtu0XjCncmVhQQZDQq4n24OVwiMcIloNa6Y1JRfk+9ErUt7HNw78COV9QG\n 5zIEVgO6diacZHG3ijFp9QMixZVjUzrCVg/Cm5z0zuIr04Go8qofGuAUIYXpA0pMEkBAcvnFw/e\n 3Iu8Mw","X-Received":"by 2002:a05:6102:41ab:b0:611:7c7b:4d40 with SMTP id\n ada2fe7eead31-616f91e60e1mr19220247137.30.1777247792503; Sun, 26 Apr 2026\n 16:56:32 -0700 (PDT)","MIME-Version":"1.0","From":"=?utf-8?b?w4FsdmFybyBCZWd1w6k=?= <alvaro.begue@gmail.com>","Date":"Sun, 26 Apr 2026 19:55:56 -0400","X-Gm-Features":"AVHnY4KLPCMOzV1WhQrBQKZ5yFTGoKWg338yt6Z4Q9Xrnp3e0gAsAO4IeIWTsS0","Message-ID":"\n <CAF8dVMVGR-jqHMvX3--mXDXSCJ6+FFvc-R=zbdyM34Vh13co7Q@mail.gmail.com>","Subject":"[PATCH v2 4/5] libstdc++: Cascade wall-time saves in lazy expansion\n seeding [PR124853]","To":"gcc-patches@gcc.gnu.org","Cc":"jwakely@redhat.com, libstdc++@gcc.gnu.org, tkaminsk@redhat.com,\n\t=?utf-8?b?w4FsdmFybyBCZWd1w6k=?= <alvaro.begue@gmail.com>","Content-Type":"multipart/alternative; boundary=\"00000000000059a157065065bf7e\"","X-BeenThere":"gcc-patches@gcc.gnu.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Gcc-patches mailing list <gcc-patches.gcc.gnu.org>","List-Unsubscribe":"<https://gcc.gnu.org/mailman/options/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe>","List-Archive":"<https://gcc.gnu.org/pipermail/gcc-patches/>","List-Post":"<mailto:gcc-patches@gcc.gnu.org>","List-Help":"<mailto:gcc-patches-request@gcc.gnu.org?subject=help>","List-Subscribe":"<https://gcc.gnu.org/mailman/listinfo/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe>","Errors-To":"gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org"},"content":"When _M_get_sys_info seeds a Zone line by looking up the active rule\njust before info.begin, the previous code interpreted each rule in\nisolation against ri.offset() (the line's standard offset alone),\nignoring the running save accumulated by earlier rules in the same\nyear.  For most zones this gives the right answer because the search\nonly matters when no rule has fired yet, but for zones whose rule\nset has wall-time rules whose effective firing time depends on a\nprior rule's save it produces wrong answers.\n\nCanonical case: Europe/Paris around 1945.  France's rules\n\n  R Fr 1945 o - Apr 2  2 2 M\n  R Fr 1945 o - Sep 16 3 0 -\n\nboth use plain wall time.  In Paris's stdoff=1 frame, the September\nrule's at_time of 03:00 wall translates to UT Sep 16 02:00 if no\nprior save is applied, but to UT Sep 16 00:00 once the running save\nof 2h from the April rule is taken into account.  When seeding a\nsys_info whose info.begin falls between those two values, the simple\nsearch picks the April rule (save=2 → CEMT, total offset 3h) when\nthe correct answer is the September rule (save=0 → CET, total offset\n1h).  The harness reports this as a sustained CEMT stretch where\nzic and libc agree on CET.\n\nReplace the per-rule isolated lookup with a chronological cascade\nwalker (the same shape as find_pre_until_rule from the previous\nPR 116110 commit, but with a fixed boundary `t = info.begin + 1s`\ninstead of iterative-shrink semantics).  All (rule, year) pairs are\ncollected over [min(rule.from), year(t)+1], sorted by an approximate\nfire time, and walked in order while a running save value is\nmaintained.  Each Wall-time rule's actual fire time is computed\nrelative to the cascaded save state, and rules whose actual fire\ntime is < t apply their save and become candidates for the active\nrule.  The +1 year extension catches rules whose wall at_time falls\nin early January or late December but whose UT firing crosses a\nyear boundary due to a large stdoff or save (Pacific/Auckland's\n1946 Jan 1 rule, in stdoff=12h, fires at 1945-12-31 11:30 UT).\n\nThe fallback \"earliest STD rule\" logic is preserved for the case\nwhere no rule has fired yet, but is moved to its own branch for\nclarity.\n\nlibstdc++-v3/ChangeLog:\n\nPR libstdc++/124853\n* src/c++20/tzdb.cc (time_zone::_M_get_sys_info):\nReplace the per-rule isolated active-rule search with a\nchronological cascade walker that maintains a running save\nand interprets Wall-time rules' at_time relative to it.\nExtend the calendar window by one year on each side to catch\nrules whose UT firing crosses a year boundary.  Move the\n\"earliest STD rule\" fallback to its own branch.\n* testsuite/std/time/time_zone/wall_cascade.cc: New test.\n\nSigned-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n---\n libstdc++-v3/src/c++20/tzdb.cc                | 142 +++++++++++-------\n .../std/time/time_zone/wall_cascade.cc        |  70 +++++++++\n 2 files changed, 158 insertions(+), 54 deletions(-)\n create mode 100644\nlibstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc","diff":"diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc\nindex 648b9f85a..4de77e6b1 100644\n--- a/libstdc++-v3/src/c++20/tzdb.cc\n+++ b/libstdc++-v3/src/c++20/tzdb.cc\n@@ -695,19 +695,81 @@ namespace std::chrono\n #endif\n     };\n\n-    // Find the Rule whose save value is in force at the wall-time UNTIL\n-    // of a Zone line, given that `wall_minus_stdoff` is the line's UNTIL\n-    // with STDOFF subtracted and `stdoff` is the line's standard offset.\n+    // Find the Rule whose effect was last in force at time `t`, given\n+    // that `stdoff` is the standard offset of the enclosing zone line.\n+    // Returns nullptr if no rule fired strictly before t.\n     //\n     // Walks (rule, year) pairs chronologically, maintaining a running\n     // save value used to interpret subsequent Wall-indicator rules.\n-    // The boundary `wall_minus_stdoff - running_save` shrinks as save\n-    // accumulates, so a rule firing AT the boundary is treated as\n-    // belonging to the next zone line.\n     //\n     // The calendar window extends by one year on each side to catch\n     // rules whose wall at_time crosses a year boundary in UT due to a\n     // large stdoff or save.\n+    template<typename _RuleRange>\n+      const Rule*\n+      find_active_rule(const _RuleRange& rules, sys_seconds t, seconds\nstdoff)\n+      {\n+ if (rules.empty())\n+  return nullptr;\n+\n+ const year last_year\n+  = year_month_day{chrono::floor<days>(t)}.year() + years(1);\n+ year first_year = year::max();\n+ for (const auto& r : rules)\n+  if (r.from < first_year)\n+    first_year = r.from;\n+ if (first_year > last_year)\n+  return nullptr;\n+\n+ struct Pending\n+ {\n+  const Rule* rule;\n+  year y;\n+  sys_seconds approx_when;\n+ };\n+ vector<Pending> pending;\n+ pending.reserve(64);\n+ for (year y = first_year; y <= last_year; ++y)\n+  for (const auto& r : rules)\n+    {\n+      if (y < r.from || y > r.to)\n+ continue;\n+      seconds approx_off{};\n+      if (r.when.indicator == at_time::Wall\n+    || r.when.indicator == at_time::Standard)\n+ approx_off = stdoff;\n+      pending.push_back({&r, y, r.start_time(y, approx_off)});\n+    }\n+ std::sort(pending.begin(), pending.end(),\n+  [](const Pending& a, const Pending& b) {\n+    return a.approx_when < b.approx_when;\n+  });\n+\n+ seconds running_save{};\n+ const Rule* last_fired = nullptr;\n+ for (const auto& p : pending)\n+  {\n+    seconds offset{};\n+    if (p.rule->when.indicator == at_time::Wall)\n+      offset = stdoff + running_save;\n+    else if (p.rule->when.indicator == at_time::Standard)\n+      offset = stdoff;\n+    sys_seconds fire = p.rule->start_time(p.y, offset);\n+    if (fire >= t)\n+      continue;\n+    last_fired = p.rule;\n+    running_save = p.rule->save;\n+  }\n+ return last_fired;\n+      }\n+\n+    // Find the Rule whose save value is in force at the wall-time UNTIL\n+    // of a Zone line, given that `wall_minus_stdoff` is the line's UNTIL\n+    // with STDOFF subtracted and `stdoff` is the line's standard offset.\n+    //\n+    // Like find_active_rule but `wall_minus_stdoff - running_save`\n+    // shrinks as save accumulates, so a rule firing AT the boundary is\n+    // treated as belonging to the next zone line.\n     template<typename _RuleRange>\n       const Rule*\n       find_pre_until_rule(const _RuleRange& rules,\n@@ -960,19 +1022,28 @@ namespace std::chrono\n  // info.begin + 1s makes the strict `rule_start < t` search\n  // inclusive of a rule that fires at exactly info.begin.\n  sys_seconds t = info.begin + seconds(1);\n- const year_month_day date(chrono::floor<days>(t));\n\n  // Try to find a Rule active before this time, to get initial\n- // SAVE and LETTERS values. There may not be a Rule for the period\n- // before the first DST transition, so find the earliest DST->STD\n- // transition and use the LETTERS from that.\n- const Rule* active_rule = nullptr;\n- sys_seconds active_rule_start = sys_seconds::min();\n- const Rule* first_std = nullptr;\n- for (const auto& rule : rules)\n+ // SAVE and LETTERS values.  See find_active_rule for the search\n+ // semantics.\n+ const Rule* active_rule = find_active_rule(rules, t, ri.offset());\n+\n+ if (active_rule)\n+  {\n+    info.offset = ri.offset() + active_rule->save;\n+    info.save = chrono::duration_cast<minutes>(active_rule->save);\n+    letters = active_rule->letters;\n+  }\n+ else\n   {\n-    if (rule.save == minutes(0))\n+    // No rule applies before info.begin; fall back to the LETTERS\n+    // of the earliest STD rule, since the period before the first\n+    // DST transition is conventionally standard time.\n+    const Rule* first_std = nullptr;\n+    for (const auto& rule : rules)\n       {\n+ if (rule.save != minutes(0))\n+  continue;\n  if (!first_std)\n   first_std = &rule;\n  else if (rule.from < first_std->from)\n@@ -984,46 +1055,9 @@ namespace std::chrono\n       first_std = &rule;\n   }\n       }\n-\n-    year y = date.year();\n-\n-    if (y > rule.to) // rule no longer applies at time t\n-      continue;\n-    if (y < rule.from) // rule doesn't apply yet at time t\n-      continue;\n-\n-    sys_seconds rule_start;\n-\n-    seconds offset{}; // appropriate for at_time::Universal\n-    if (rule.when.indicator == at_time::Wall)\n-      offset = info.offset;\n-    else if (rule.when.indicator == at_time::Standard)\n-      offset = ri.offset();\n-\n-    // Time the rule takes effect this year:\n-    rule_start = rule.start_time(y, offset);\n-\n-    if (rule_start >= t && rule.from < y)\n-      {\n- // Try this rule in the previous year.\n- rule_start = rule.start_time(--y, offset);\n-      }\n-\n-    if (active_rule_start < rule_start && rule_start < t)\n-      {\n- active_rule_start = rule_start;\n- active_rule = &rule;\n-      }\n-  }\n-\n- if (active_rule)\n-  {\n-    info.offset = ri.offset() + active_rule->save;\n-    info.save = chrono::duration_cast<minutes>(active_rule->save);\n-    letters = active_rule->letters;\n+    if (first_std)\n+      letters = first_std->letters;\n   }\n- else if (first_std)\n-  letters = first_std->letters;\n       }\n\n     const Rule* curr_rule = nullptr;\ndiff --git a/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc\nb/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc\nnew file mode 100644\nindex 000000000..46e4e5346\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc\n@@ -0,0 +1,70 @@\n+// { dg-do run { target c++20 } }\n+// { dg-require-effective-target tzdb }\n+// { dg-require-effective-target cxx11_abi }\n+// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n+\n+// Wall-time rules in the same rule set whose effective firing time\n+// depends on a prior rule's save (Europe/Paris 1945):\n+//   1945 Apr 2  02:00 wall  save=2  M\n+//   1945 Sep 16 03:00 wall  save=0  -\n+// In the (stdoff=1, save=2) frame the September rule fires at\n+// Sep 16 00:00 UT, not Sep 16 02:00 UT.\n+\n+#include <chrono>\n+#include <fstream>\n+#include <testsuite_hooks.h>\n+\n+static bool override_used = false;\n+\n+namespace __gnu_cxx\n+{\n+  const char* zoneinfo_dir_override() {\n+    override_used = true;\n+    return \"./\";\n+  }\n+}\n+\n+int\n+main()\n+{\n+  using namespace std::chrono;\n+\n+  // Line 1 ends at \"1945 Sep 16 1u\" (Universal time, no save shenanigans),\n+  // so info.begin for line 2 is exactly 1945-09-16 01:00 UT.\n+  //\n+  // Two-line zone whose second line begins at 1945 Sep 16 01:00 UT,\n+  // between the cascaded firing time (Sep 16 00:00 UT) and the\n+  // non-cascaded firing time (Sep 16 02:00 UT) of the September rule.\n+  // The seeding must pick the September rule (save=0, CET) at info.begin.\n+  std::ofstream(\"tzdata.zi\") << R\"(# version test_wall_cascade\n+R Fr 1945 o - Apr 2  2 2 M\n+R Fr 1945 o - Sep 16 3 0 -\n+Z Test/Paris 0  -  X     1945 Sep 16 1u\n+             1  Fr CE%sT\n+)\";\n+\n+  const auto& db = reload_tzdb();\n+  VERIFY( override_used );\n+  VERIFY( db.version == \"test_wall_cascade\" );\n+\n+  auto* tz = locate_zone(\"Test/Paris\");\n+\n+  // Line 2 begins at exactly 1945-09-16 01:00 UT.  Sample one second\n+  // after the boundary, well inside line 2's first sys_info.\n+  auto info = tz->get_info(sys_seconds{\n+      sys_days(1945y/September/16) + 1h + 1s});\n+  VERIFY( info.offset == 1h );\n+  VERIFY( info.save == 0min );\n+  VERIFY( info.abbrev == \"CET\" );\n+\n+  // The boundary instant itself is in the new line.\n+  auto at_boundary\n+    = tz->get_info(sys_seconds{sys_days(1945y/September/16) + 1h});\n+  VERIFY( at_boundary.offset == 1h );\n+  VERIFY( at_boundary.save == 0min );\n+\n+  // Sample later still in line 2 (winter): unchanged.\n+  auto winter = tz->get_info(sys_days(1945y/December/1));\n+  VERIFY( winter.offset == 1h );\n+  VERIFY( winter.save == 0min );\n+}\n","prefixes":["v2","4/5"]}