Cover Letter Detail
Show a cover letter.
GET /api/1.2/covers/2228543/?format=api
{ "id": 2228543, "url": "http://patchwork.ozlabs.org/api/1.2/covers/2228543/?format=api", "web_url": "http://patchwork.ozlabs.org/project/gcc/cover/CAF8dVMXKp=CN6mH8CGrSnhLc8k3vTGmuu2K2ae4xyxDu6eekxg@mail.gmail.com/", "project": { "id": 17, "url": "http://patchwork.ozlabs.org/api/1.2/projects/17/?format=api", "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": "<CAF8dVMXKp=CN6mH8CGrSnhLc8k3vTGmuu2K2ae4xyxDu6eekxg@mail.gmail.com>", "list_archive_url": null, "date": "2026-04-26T23:42:54", "name": "[v2,0/5] libstdc++: chrono tzdb correctness fixes", "submitter": { "id": 93119, "url": "http://patchwork.ozlabs.org/api/1.2/people/93119/?format=api", "name": "Álvaro Begué", "email": "alvaro.begue@gmail.com" }, "mbox": "http://patchwork.ozlabs.org/project/gcc/cover/CAF8dVMXKp=CN6mH8CGrSnhLc8k3vTGmuu2K2ae4xyxDu6eekxg@mail.gmail.com/mbox/", "series": [ { "id": 501555, "url": "http://patchwork.ozlabs.org/api/1.2/series/501555/?format=api", "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/covers/2228543/comments/", "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=r3KpmuzO;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org\n (client-ip=2620:52:6:3111::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=r3KpmuzO", "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.43" ], "Received": [ "from vm01.sourceware.org (vm01.sourceware.org\n [IPv6:2620:52:6:3111::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 4g3jwd1K8Bz1yJX\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 27 Apr 2026 09:44:47 +1000 (AEST)", "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id C0A494BABF16\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 26 Apr 2026 23:44:45 +0000 (GMT)", "from mail-vs1-f43.google.com (mail-vs1-f43.google.com\n [209.85.217.43])\n by sourceware.org (Postfix) with ESMTPS id D06EE4BABF11\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 23:43:31 +0000 (GMT)", "by mail-vs1-f43.google.com with SMTP id\n ada2fe7eead31-6221c7251e1so897154137.3\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 16:43:31 -0700 (PDT)" ], "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 sourceware.org C0A494BABF16", "OpenDKIM Filter v2.11.0 sourceware.org D06EE4BABF11" ], "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org D06EE4BABF11", "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org D06EE4BABF11", "ARC-Seal": [ "i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1777247012; cv=pass;\n b=LJ3CAza8erP5bdmF98daaAmktOz5ObFRc0Nrq0k7v6HjVV6KwLXQ/nvV5OtMZ1bftyvNBCWmCDGHralGQ8a1I3gQoNRlC6KsVJydXphZdaae2oRs7RNlu+FNhnD/KO6WHZxVY4UKNfrMNLo2rCgKfIAxSxv6+Py7EfWkDtxlBoc=", "i=1; a=rsa-sha256; t=1777247011; cv=none;\n d=google.com; s=arc-20240605;\n b=AYRzPyt54Q3D8tTmW1N3omRVYYWTvqGdYZywt7FB2w8pfQMcN5IuM5VVNk6EWso5at\n 8ov6BP2nwRRJK9TMlGIlBrcMmcIO7hnbK1YAczMS/vrSMLBgDihGY2QurNg2bcMWUhWD\n LGA9rjV0+O/1F3gtHoOd6LtvroHugHQW/z442Rixa12vsmg/mETtPwiF10pf2bt0aD3v\n t0B4LZbCM1DqXXv3WmEfUAMBlhOCNYseq14h5L1TMP+pKFKGsuYMxpj0y0f7tYFV4SSV\n s8mH4TArAgJDUKinR6vbCNwBtPtvkqwasXrmc+PmzaOxcI9wVC03HXFKdNqFQqWE5IiR\n YQfw==" ], "ARC-Message-Signature": [ "i=2; a=rsa-sha256; d=sourceware.org; s=key;\n t=1777247012; c=relaxed/simple;\n bh=/HCOf+odwyeGW8unPp2IHp+TVBeuZVVIlFOjPCf4eJ0=;\n h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To;\n b=GBx8LS4/6/XZYC4RteJCasZecrn/UYMyQTH7i1Nzv7G75VwWEGrB5hKvxT5HBL497i90y1uyiw3+O0Flb3FWY5po6oihpEsTGWC46xzjX+6wKhBjl0ki3uix6FSWxoiXreXtr0UyAVbBTqos9cl5VcpNpK2jjcaFdCYV7mIPgu8=", "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=LFqYLKxlN4GbXuMcwyCpqWTav17zCOl7Nfn1kVct+e0=;\n fh=sWbBxiX87E8F1zPxXVV3X4tQqxV9sZLoQvmjvveliwE=;\n b=RXdGVBjLMYS7mJIBpgNtkAJSz+/krwrodFXLr/QRLAF3DMUDYOJdvRRcr7qRunuFM2\n 3v/qSFey0DZa8LljS1sqi5jS2lK1IaXZrjrjVmr+UJNpm0d5OMv+MSyv+mWzBY2V/8Zd\n JmQSLziOvHmJ9k9rN/Wn92Lygq96bPwi/oBY56wUvmLNCFr1PB5y2IGWVYFub7uiK2o/\n Chq9n+LCyNCjWCzORcp44TJ9WZFuZOptUxnFFM0vef+41C7CUsHWU7MIftQUWAil6I07\n L/533bkhs7s+gaRd+3qRMSZjYmL1NDEbyWlp6Az5KxGSJEEFFbHglObU+dRBTmLiGXOs\n Pc/g==; 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=1777247011; x=1777851811; 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=LFqYLKxlN4GbXuMcwyCpqWTav17zCOl7Nfn1kVct+e0=;\n b=r3KpmuzOXdCKEYyRBZ3S+pgdBm/r6SRjAuITUUGIuSKmgwddI3XMofFHlOxOtdRiTU\n D39i2n4QRSas+YzpzHpp60mOd5OJEIdc2yvCUpnusNdAicz3IXq98erevtRJR3f+h/uS\n bOMZUmFlr2RpKRz6q6V6H2qTPzjFrzzU2bPYe4jobFvsK19AlwCOWROvnVTH+9VT4Pc7\n 91nPjuc/xqx7I/15zpq2+sEDCU7w1YzwF+dgMe4BbqsJ8n4fJovXtr1gzLk8FSVevXnX\n gG5kEuHzcctJNg8p+2AXh2Va9Tg18PfqDg8IsIjCsNbfvZ3Blpo16FFNfV/WN1Sk3sBe\n 8/5A==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777247011; x=1777851811;\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=LFqYLKxlN4GbXuMcwyCpqWTav17zCOl7Nfn1kVct+e0=;\n b=eCl6Pcw4+ZnNMpDRA8kNv91ISBf+LLry2lhQt73h3Ej1qpkLpCoRgd3o92MIaxVc6I\n C9QMvtYRptfU/RS9ucIZEdgKJW97BLoOB1122lwnDpZUxnzFAz9DrcWQaVyx2NyU/Uke\n rW3hq0VJ5+Wh8M0+8ZNdcv7XqHdS1cCyI58SLXkLJQAjp6cEuPvlTAnVJubK64LZ7BL6\n 1eweRn17mHntQ73Obe94hyGyaXnTWqPVBcyeLyeAr7pdv8nlVJtDAthg1KdEOQd6Xiwh\n 3ofi+luxCZguTHvVnfuKMtOZrGINSQ2FcESTAzCABFww34OdKNZDUFLNrCbZVI9OOSWp\n rMeg==", "X-Gm-Message-State": "AOJu0YwR/1Rh6RmDWcbNMwcTkA1BBmPIUPVIHpcVVcNBuEGIAkqf3Pz3\n DabbMnNwCjHEwMW67m8BPNTjIzkBjoC06VoAb9+F5dtIzW05j022ShwxMzYlCQjxPLirPl/fL/d\n u5yiwl9rBR6EkIg5xke3PScHw40KnavQJ1rlxKCY=", "X-Gm-Gg": "AeBDieuUIcP4jAjc1pxaWsGpZ7nTxfpbDB0Yoft/cv9fBYC9F3TtC0LgooXpKWAMerg\n 4cqZy2WsaSq7zbTgUaalFXKSVF6fJeSC//qbbFN+5RKp7F5/m5uG1EYtvj8tyktw8DVTcVB0iXd\n Ztzd6cdgZCimynFLMX1kgrBSowIsSPASYruj/guwpKweVh6TilsKSxu5gOa7panl1YwYfTAuB5I\n LZNWXxuxL7Tw5kK/cslrDhnMDY+b9aqQuXxXrpo4Co+6+gjBmWNQuXHCbh6elqaNJ5lnxWkI9Ex\n ts3qwvcnHxNEe4pSgdCL6NZxWpPK6e+vK/g0MyvvS04eNDfAgQZO80C2yVL4qxr8kmSKma6V4Cc\n 2MObko3avJ279i1E0sw9+uXNRPZVBfzAiLOSKXDCQ+1MRd4oTzl1VpnaQauuoip0nc2rKzw==", "X-Received": "by 2002:a05:6102:512c:b0:608:6c7b:4554 with SMTP id\n ada2fe7eead31-616f68d370dmr18281045137.16.1777247010219; Sun, 26 Apr 2026\n 16:43:30 -0700 (PDT)", "MIME-Version": "1.0", "From": "=?utf-8?b?w4FsdmFybyBCZWd1w6k=?= <alvaro.begue@gmail.com>", "Date": "Sun, 26 Apr 2026 19:42:54 -0400", "X-Gm-Features": "AVHnY4JjzQX4EvMS7Zrm7VVB46BbCKnMGBzDKeOXwlG9TvXEkS3utIX4eCnxNuk", "Message-ID": "\n <CAF8dVMXKp=CN6mH8CGrSnhLc8k3vTGmuu2K2ae4xyxDu6eekxg@mail.gmail.com>", "Subject": "[PATCH v2 0/5] libstdc++: chrono tzdb correctness fixes", "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=\"000000000000b8ebef0650659093\"", "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": "This is v2 of the chrono tzdb correctness fix series. The actual fixes\nare unchanged; v2 addresses Tomasz Kamiński's review comments, which\nwere all stylistic.\n\nChanges since v1 (per Tomasz's feedback):\n\n * Trim verbose \"Regression test:\" / past-bug-history prose from the\n test files. Test comments now describe the behavior under test\n rather than the historical bug.\n * Trim redundant cross-reference comments and shorten the surviving\n ones. \"// PR 116110\" breadcrumbs in code comments are gone -- the\n PR number is captured in the commit message and the ChangeLog entry.\n * Patch 2: rename parse_on_day_body -> parse_day_spec; use C++20\n designated initializers and if-with-initializer for `on_day` and\n `abbrev_month`; restructure the optional DAY/TIME parse so TIME is\n only attempted when DAY parsing succeeds.\n\nEach commit now carries a Signed-off-by trailer.\n\nA range-diff of v1 vs v2 is included at the end of this cover letter.\n\nTest plan (rerun against current master HEAD):\n\n * libstdc++ stage1 build of GCC 16.0.1 trunk (b99e67e8a) succeeds.\n * All 7 chrono tests pass (Wakely's existing 116110 + 124513 plus the\n 5 new ones added by this series).\n * Brute-force comparison harness (628310 samples × 447 zones) reports\n zero mismatches against libc localtime_r.\n * Abbreviation-only sweep (188493 samples) reports zero offset and\n zero abbrev diffs.\n\nThe series builds on Jonathan Wakely's recent PR116110 / PR124513\nwork (commits 663e5ade1, cddf4111c, fbc5d2b1a). Patch 3 in particular\nresolves the \"FIXME: PR116110\" left in operator>>(istream&, ZoneInfo&)\nfor the named-rule wall-UNTIL case.\n\n 1. Fix numeric save offset on Zone lines [PR124851].\n\n ZoneInfo::m_offset had inconsistent semantics: the parser path\n stored stdoff alone, but the two sys_info-taking constructors\n stored the total (stdoff + save). Normalize m_offset to stdoff\n alone everywhere; to() adds save back when reconstructing.\n\n 2. Support ON-format DAY in Zone UNTIL field [PR124852].\n\n The UNTIL parser only accepted a plain integer as the DAY,\n silently misparsing tzdata.zi entries like Europe/Simferopol's\n \"1997 Mar lastSu 1u\". Reuse the on_day machinery and the\n parse_day_spec helper.\n\n 3. Resolve named-rule UNTIL save adjustment [PR116110].\n\n The remaining FIXME in operator>>(istream&, ZoneInfo&) for\n wall-time UNTILs on named-rule zone lines. At parse time the\n active rule cannot be evaluated, so the parser leaves the SAVE\n adjustment pending and a fixup pass in reload_tzdb walks every\n pending ZoneInfo and applies the adjustment using a new\n find_pre_until_rule helper with iterative-boundary cascade\n semantics. Removes the +11h workaround from test_apia in\n 116110.cc.\n\n 4. Cascade wall-time saves in lazy expansion seeding [PR124853].\n\n Replace the per-rule isolated active-rule lookup in\n _M_get_sys_info with a chronological cascade walker that\n maintains a running save and interprets each Wall-time rule's\n at_time relative to the cascaded state (matching zic.c's\n outzone()).\n\n 5. Implement zic writezone merge optimization [PR124854].\n\n Two related fixes: (a) always seed info.offset/save from\n find_active_rule (not just when letters is empty), so partial-\n expansion re-entry sees the right state; (b) add the writezone\n merge for backward jumps at zone-line boundaries.\n\nÁlvaro Begué (5):\n libstdc++: Fix numeric save offset on Zone lines [PR124851]\n libstdc++: Support ON-format DAY in Zone UNTIL field [PR124852]\n libstdc++: Resolve named-rule UNTIL save adjustment [PR116110]\n libstdc++: Cascade wall-time saves in lazy expansion seeding\n [PR124853]\n libstdc++: Implement zic writezone merge optimization [PR124854]\n\n libstdc++-v3/src/c++20/tzdb.cc | 378 ++++++++++++++----\n .../testsuite/std/time/time_zone/116110.cc | 5 +-\n .../std/time/time_zone/numeric_save.cc | 58 +++\n .../std/time/time_zone/pr116110_named.cc | 74 ++++\n .../std/time/time_zone/until_day_on.cc | 168 ++++++++\n .../std/time/time_zone/wall_cascade.cc | 70 ++++\n .../std/time/time_zone/zone_merge.cc | 84 ++++\n 7 files changed, 748 insertions(+), 89 deletions(-)\n create mode 100644\nlibstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc\n create mode 100644\nlibstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc\n create mode 100644\nlibstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc\n create mode 100644\nlibstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc\n create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc\n\nRange-diff against v1:\n1: 7e77ac729 ! 1: 2136a9e20 libstdc++: Fix numeric save offset on Zone\nlines [PR 124851]\n @@\n ## Metadata ##\n -Author: Alvaro Begue <alvaro.begue@gmail.com>\n +Author: Álvaro Begué <alvaro.begue@gmail.com>\n\n ## Commit message ##\n - libstdc++: Fix numeric save offset on Zone lines [PR 124851]\n + libstdc++: Fix numeric save offset on Zone lines [PR124851]\n\n When a Zone line specifies a numeric value as its RULES field (the\n constant DST save value for that zone line, e.g. Africa/Gaborone's\n @@ Commit message\n populating sys_info::offset.\n * testsuite/std/time/time_zone/numeric_save.cc: New test.\n\n + Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n +\n ## libstdc++-v3/src/c++20/tzdb.cc ##\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n }\n\n - // STDOFF: Seconds from UTC during standard time.\n -+ // STDOFF: Seconds from UTC during standard time. Always the\n -+ // standard offset only; the saved value (if any) is in m_save\nand\n -+ // is added back when reconstructing a sys_info via to().\n ++ // STDOFF: Seconds from UTC during standard time (without any\nsave).\n seconds\n offset() const noexcept { return m_offset; }\n\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n\n info.end = until();\n - info.offset = offset();\n -+ // m_offset is the standard offset only; add the saved value to\n -+ // reconstruct the total offset. See ZoneInfo's m_offset comment.\n + info.offset = offset() + seconds(m_save);\n info.save = minutes(m_save);\n info.abbrev = format();\n @@ libstdc++-v3/testsuite/std/time/time_zone/numeric_save.cc (new)\n +// { dg-require-effective-target cxx11_abi }\n +// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n +\n -+// Regression test: when a Zone line specifies a numeric value as its\n -+// RULES field, that value is the constant DST save value for that\nzone\n -+// line. ZoneInfo::to() previously set sys_info::offset to the zone\n -+// line's STDOFF only, ignoring the parsed save. Per\n[time.zone.info.sys]\n -+// sys_info::offset is the *total* UTC offset (stdoff + save), so any\n -+// zone line with a non-zero numeric save reported the wrong offset.\n -+//\n -+// Mirrors Africa/Gaborone's tzdata, which uses\n -+// 2 - CAT 1943 S 19 2\n -+// 2 1 CAST 1944 Mar 19 2 <-- numeric \"1\" RULES, save = +1h\n -+// 2 - CAT\n -+// The middle line is what triggers the bug.\n ++// When a Zone line specifies a numeric value as its RULES field, that\n ++// value is the constant DST save value for that zone line. Per\n ++// [time.zone.info.sys] sys_info::offset is the total UTC offset\n ++// (stdoff + save).\n +\n +#include <chrono>\n +#include <fstream>\n2: 57b11cf6e ! 2: 60c6f5eef libstdc++: Support ON-format DAY in Zone\nUNTIL field [PR 124852]\n @@\n ## Metadata ##\n -Author: Alvaro Begue <alvaro.begue@gmail.com>\n +Author: Álvaro Begué <alvaro.begue@gmail.com>\n\n ## Commit message ##\n - libstdc++: Support ON-format DAY in Zone UNTIL field [PR 124852]\n + libstdc++: Support ON-format DAY in Zone UNTIL field [PR124852]\n\n The Zone-line UNTIL parser only accepted a plain day-of-month\ninteger\n for the DAY field, while the tzdata.zi grammar accepts the same\nON-style\n @@ Commit message\n left d == 1 when the day token wasn't a digit, then went on to\nparse the\n remainder as the TIME field.\n\n - Fix by reusing the existing parse_on_day_body() helper that already\n + Factor out the day-component parser from operator>>(istream&,\non_day&)\n + as parse_day_spec(), and reuse it for the UNTIL DAY field.\n parse_day_spec\n handles all three on_day forms (DayOfMonth, LastWeekday, LessEq /\n - GreaterEq) for Rule lines. The MONTH-only and YEAR-only short\nforms are\n - still accepted because the DAY/TIME fields are optional and\ndefault to\n - day 1, time 00:00. The on_day struct's pin() method handles the\n - year/month-relative resolution.\n + GreaterEq). The MONTH-only and YEAR-only short forms are still\naccepted\n + because the DAY/TIME fields are optional and default to day 1,\ntime 00:00.\n + The on_day struct's pin() method handles the year/month-relative\n + resolution.\n\n The DAY field is unambiguously distinguishable from a TIME field\nthat\n could otherwise follow the MONTH directly: per zic's grammar, MONTH\n - must be followed by DAY before any TIME is allowed. So we always\n + must be followed by DAY before any TIME is allowed. So we always\n attempt to parse a DAY if any non-whitespace remains after the\nMONTH.\n\n libstdc++-v3/ChangeLog:\n\n PR libstdc++/124852\n - * src/c++20/tzdb.cc (parse_on_day_body): Factor out the\nday-\n - component parser from operator>>(istream&, on_day&) so it\ncan\n - be reused.\n - (operator>>(istream&, on_day&)): Use the new helper.\n + * src/c++20/tzdb.cc (parse_day_spec): New function,\nfactored\n + out of operator>>(istream&, on_day&).\n + (operator>>(istream&, on_day&)): Use parse_day_spec.\n (operator>>(istream&, ZoneInfo&)): Replace the integer DAY\n - parser with parse_on_day_body for the UNTIL field.\n + parser with parse_day_spec for the UNTIL field.\n * testsuite/std/time/time_zone/until_day_on.cc: New test.\n\n + Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n +\n ## libstdc++-v3/src/c++20/tzdb.cc ##\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n }\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n + // Read the day-component of an on_day expression (everything\nafter the\n + // month). Three forms are accepted: a plain day-of-month number,\n + // \"lastXxx\" where Xxx is a weekday name (LastWeekday), or\n\"Xxx<=N\" or\n -+ // \"Xxx>=N\" (LessEq / GreaterEq). The caller is responsible for\nsetting\n -+ // `on.month` before calling. On failure the function sets\nfailbit and\n -+ // leaves `on` unchanged.\n ++ // \"Xxx>=N\" (LessEq / GreaterEq). On failure the function sets\nfailbit\n ++ // and leaves `on` unchanged.\n + istream&\n -+ parse_on_day_body(istream& in, on_day& on)\n ++ parse_day_spec(istream& in, on_day& on)\n {\n - on_day on{};\n - abbrev_month m{};\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n + abbrev_month m{};\n + in >> m;\n + on.month = static_cast<unsigned>(m.m);\n -+ if (parse_on_day_body(in, on))\n ++ if (parse_day_spec(in, on))\n + to = on;\n + return in;\n + }\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n {\n int sign = 1;\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n + in.exceptions(ios::goodbit); // Don't throw ios::failure if\nYEAR absent.\n if (int y = int(year::max()); in >> y)\n {\n - abbrev_month m{January};\n +- abbrev_month m{January};\n - int d = 1;\n -+ on_day on{};\n -+ on.kind = on_day::DayOfMonth;\n -+ on.month = 1; // default January\n -+ on.day_of_month = 1; // default day-of-month 1\n ++ on_day on{.kind = on_day::DayOfMonth, .month = 1, .day_of_month =\n1};\n at_time t{};\n - // XXX DAY should support ON format, e.g. lastSun or Sun>=8\n - in >> m >> d >> t;\n - inf.m_until = sys_days(year(y)/m.m/day(d)) + seconds(t.time);\n -+ if (in >> m)\n ++ if (abbrev_month m{January}; in >> m)\n + {\n + on.month = static_cast<unsigned>(m.m);\n -+ // The DAY field is optional. Per the tzdata.zi grammar,\n -+ // a MONTH followed by anything more is always followed by\n -+ // a DAY (possibly followed by a TIME); MONTH directly\n -+ // followed by TIME is not a valid form. So if there's\n -+ // any non-whitespace before end of line, parse a DAY.\n + if (!ws(in).eof())\n -+ parse_on_day_body(in, on);\n ++ if (parse_day_spec(in, on))\n ++ in >> t;\n + }\n -+ in >> t;\n + year_month_day ymd = on.pin(year(y));\n + inf.m_until = sys_days(ymd) + seconds(t.time);\n if (t.indicator != at_time::Universal)\n @@ libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc (new)\n +// { dg-require-effective-target cxx11_abi }\n +// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n +\n -+// Regression test: the DAY portion of a Zone line's UNTIL field\naccepts\n -+// not only a numeric day-of-month but also \"lastXxx\" (last weekday in\n -+// the month) and \"Xxx<=N\" / \"Xxx>=N\" forms, just like the ON field of\n -+// a Rule line. Previously the UNTIL parser used `int d; in >> d;`\nwhich\n -+// silently failed on the non-numeric forms and defaulted d to 1,\nplacing\n -+// any zone-line transition with such an UNTIL on the wrong calendar\nday.\n ++// The DAY portion of a Zone line's UNTIL field accepts not only a\n ++// numeric day-of-month but also \"lastXxx\" (last weekday in the month)\n ++// and \"Xxx<=N\" / \"Xxx>=N\" forms, just like the ON field of a Rule\nline.\n +//\n +// Real-world example: Europe/Simferopol has\n +// 3 - MSK 1997 Mar lastSu 1u\n -+// which must place the boundary on 1997-03-30 (the last Sunday of\nMarch),\n -+// not on 1997-03-01.\n ++// which places the boundary on 1997-03-30 (the last Sunday of March).\n +\n +#include <chrono>\n +#include <fstream>\n @@ libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc (new)\n + auto at = tz->get_info(boundary);\n + VERIFY( at.abbrev == \"X\" );\n +\n -+ // Critical regression check: a sample 15 days BEFORE the boundary\nmust\n -+ // still be in the MSK line. The unfixed parser placed the\nboundary on\n -+ // March 1 because \"lastSu\" defaulted to day 1, and a March-15 query\n -+ // landed in the X line instead.\n ++ // Check that the lastSu day is parsed correctly, and not defaulted\n ++ // to the 1st: a March 15 query must still be in the MSK line.\n + auto mid_march = tz->get_info(sys_days{1997y/March/15});\n + VERIFY( mid_march.abbrev == \"MSK\" );\n + VERIFY( mid_march.offset == 3h );\n @@ libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc (new)\n + auto at = tz->get_info(boundary);\n + VERIFY( at.abbrev == \"B\" );\n +\n -+ // A June-1 query must still be in the A line (the unfixed parser\n -+ // placed the boundary on June 1).\n ++ // Check that Sun>=8 is parsed correctly, and not defaulted to the\n1st.\n + auto early = tz->get_info(sys_days{1990y/June/1});\n + VERIFY( early.abbrev == \"A\" );\n +}\n @@ libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc (new)\n +{\n + using namespace std::chrono;\n +\n -+ // Sanity check: a UNTIL with only a year (no MONTH, no DAY, no\nTIME)\n -+ // must continue to default to January 1 00:00.\n ++ // MONTH, DAY and TIME default to January 1st 00:00 if not\nspecified.\n + std::ofstream(\"tzdata.zi\") << R\"(# version test_year_only\n +Z Test/YearOnly 0 - A 1990\n + 0 - B\n @@ libstdc++-v3/testsuite/std/time/time_zone/until_day_on.cc (new)\n +{\n + using namespace std::chrono;\n +\n -+ // Sanity check: UNTIL with only YEAR and MONTH (no DAY, no TIME)\n -+ // must default DAY to 1 and TIME to 00:00.\n ++ // DAY and TIME default to the 1st 00:00 if not specified.\n + std::ofstream(\"tzdata.zi\") << R\"(# version test_year_month_only\n +Z Test/YearMonth 0 - A 1990 Jul\n + 0 - B\n3: 38e5ea518 ! 3: 5b20eac08 libstdc++: Resolve named-rule UNTIL save\nadjustment [PR116110]\n @@\n ## Metadata ##\n -Author: Alvaro Begue <alvaro.begue@gmail.com>\n +Author: Álvaro Begué <alvaro.begue@gmail.com>\n\n ## Commit message ##\n libstdc++: Resolve named-rule UNTIL save adjustment [PR116110]\n @@ Commit message\n (operator>>(istream&, ZoneInfo&)): Set m_until_save_pending\n when the wall UNTIL on a named-rule line cannot have its\nsave\n subtracted at parse time. Replaces the FIXME.\n - (time_zone::_Impl::_M_get_sys_info): Change the seeding\nactive-\n + (time_zone::_M_get_sys_info): Change the seeding active-\n rule lookup to use t = info.begin + 1s, so a rule firing at\n exactly info.begin is included.\n (reload_tzdb): After sorting node->rules, run a fixup pass\nover\n @@ Commit message\n canonical +10h boundary is now produced.\n * testsuite/std/time/time_zone/pr116110_named.cc: New test.\n\n + Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n +\n ## libstdc++-v3/src/c++20/tzdb.cc ##\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n sys_seconds\n until() const noexcept { return m_until; }\n\n -+ // PR 116110: When the parser sees a wall-time UNTIL on a\nnamed-Rule\n -+ // line, the SAVE component of the conversion can't be applied\nyet\n -+ // because the active Rule depends on the cumulative state of\nthe\n -+ // (not-yet-loaded) rule set. The parser leaves m_until in the\n -+ // \"STDOFF subtracted but SAVE not yet subtracted\" intermediate\n -+ // state and sets this bit; reload_tzdb runs a fixup pass after\nall\n -+ // Rule records are loaded to subtract the SAVE.\n ++ // True if this is a named-rule zone line whose wall-time UNTIL\nstill\n ++ // needs its SAVE adjustment applied. See reload_tzdb for the\nfixup.\n + bool\n + until_save_pending() const noexcept { return\nm_until_save_pending; }\n +\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n + void\n + clear_until_save_pending() noexcept { m_until_save_pending = 0;\n}\n +\n -+ // Subtract `s` from m_until. Used by the PR 116110 fixup pass.\n + void\n + adjust_until(seconds s) noexcept { m_until -= s; }\n +\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n - uint_least16_t m_pos : 15 = 0; // offset of format() in m_buf\n + uint_least16_t m_pos : 14 = 0; // offset of format() in m_buf\n uint_least16_t m_expanded : 1 = 0;\n -+ uint_least16_t m_until_save_pending : 1 = 0; // PR 116110, see\nabove\n ++ uint_least16_t m_until_save_pending : 1 = 0;\n duration<int_least16_t, ratio<60>> m_save{};\n sec32_t m_offset{};\n sys_seconds m_until{};\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n +\n + // Find the Rule whose save value is in force at the wall-time\nUNTIL\n + // of a Zone line, given that `wall_minus_stdoff` is the line's\nUNTIL\n -+ // expressed in the \"save=0\" frame (i.e. the parsed wall UNTIL\nwith\n -+ // the line's STDOFF subtracted) and `stdoff` is the line's\nstandard\n -+ // offset.\n -+ //\n -+ // The function walks all (rule, year) pairs in chronological\norder,\n -+ // maintaining a running save value. Wall-time rules have their\nTIME\n -+ // field interpreted relative to the running save (since \"wall\"\nmeans\n -+ // local civil time = stdoff + save), so a rule's effective UT\nfiring\n -+ // time depends on which prior rule was last in force. This\nmatches\n -+ // zic.c's outzone() logic and is required for zone lines whose\n -+ // rule set has rules whose at_time depends on cascading saves.\n ++ // with STDOFF subtracted and `stdoff` is the line's standard\noffset.\n + //\n -+ // The comparison `fire < boundary` shrinks `boundary` as the\nrunning\n -+ // save cascades up: a rule that fires AT the boundary (with the\n -+ // cascaded save applied) is treated as belonging to the next zone\n -+ // line, and its save is excluded from the running total.\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 -+ // Canonical case: Africa/Algiers 1977-10-21. The \"Algeria 1977\n -+ // Oct 21\" rule (save=0) fires at the same instant as the wall\nUNTIL\n -+ // of line 6 (\"0 d WE%sT 1977 O 21\"). The pre-rule save (1h, from\n -+ // the May 6 rule) is what determines the boundary's UT placement,\n -+ // not the Oct 21 rule's save=0.\n -+ //\n -+ // The calendar window is extended by one year on each side, to\n -+ // catch rules whose wall-time at_time falls in early January or\n -+ // late December but whose UT firing crosses a year boundary due\n -+ // to a large stdoff or save.\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_pre_until_rule(const _RuleRange& rules,\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n if (letters.empty())\n {\n - sys_seconds t = info.begin - seconds(1);\n -+ // We want the rule whose effect is in force at info.begin --\n -+ // including a rule that fires at exactly info.begin (its effect\n -+ // has just begun and is active for the first sys_info we are\n -+ // about to generate). The search below uses a strict\n -+ // `rule_start < t` comparison, so pass info.begin + 1s to make\n -+ // the half-open lookup (..., info.begin] inclusive of the\n -+ // boundary instant. This is what makes named-rule zone lines\n -+ // like Africa/Algiers (PR 116110) seed with the correct save:\n -+ // the Oct-21 rule fires at Oct 20 23:00 UTC in the new line's\n -+ // frame, which is exactly the new line's begin.\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 @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n ranges::sort(node->db.links, {}, &time_zone_link::name);\n ranges::stable_sort(node->rules, {}, &Rule::name);\n\n -+ // PR 116110 fixup pass. For every Zone line whose UNTIL was a\nwall-\n -+ // time expression on a named-rule line, the parser deferred the\nSAVE\n -+ // adjustment because the active rule wasn't yet identifiable.\nNow\n -+ // that all Rule records are loaded and indexed, walk every\npending\n -+ // ZoneInfo, find the rule whose effect was in force just before\nthe\n -+ // wall UNTIL, and subtract that rule's save from m_until.\n -+ //\n -+ // \"Just before the wall UNTIL\" matches zic.c's interpretation:\nthe\n -+ // wall time of UNTIL is read in the frame in effect immediately\n -+ // prior to the boundary, so the SAVE used is the value that the\n -+ // most recent rule strictly before the UNTIL set. A rule firing\nat\n -+ // exactly the UNTIL is not yet in force at the moment the wall\n -+ // time is being interpreted -- its effect belongs to the next\nzone\n -+ // line, not this one. find_pre_until_rule's iterative-boundary\n -+ // walker implements this semantics.\n ++ // For every Zone line whose UNTIL was a wall-time expression on a\n ++ // named-rule line, the parser deferred the SAVE adjustment\nbecause\n ++ // the active rule was not yet identifiable. Now that all Rule\n ++ // records are loaded and indexed, find the rule active just\nbefore\n ++ // the wall UNTIL and subtract its save from m_until.\n + for (const auto& tz : node->db.zones)\n + {\n + auto& infos = tz._M_impl->infos;\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n inf.m_until -= inf.m_save;\n - // else Named Rule, SAVE is unknown. FIXME: PR 116110\n + else\n -+ // Named Rule: SAVE depends on which rule of the set\n -+ // was active at this instant, which can only be\n -+ // determined once all Rule records are loaded. Mark\n -+ // the ZoneInfo so that the fixup pass in reload_tzdb\n -+ // applies the deferred adjustment. PR 116110.\n ++ // Named Rule: defer SAVE adjustment until reload_tzdb\n ++ // has loaded all Rule records.\n + inf.set_until_save_pending();\n }\n }\n @@ libstdc++-v3/testsuite/std/time/time_zone/116110.cc: test_apia()\n\n - // FIXME: this should be + 10h but we do not account for DST yet,\nso + 11h.\n - sys_seconds ut(t.time_since_epoch() + 11h );\n -+ // The wall UNTIL \"2011 Dec 29 24\" is interpreted in the prior\noffset\n -+ // (-11h + save 1h = -10h), so the boundary is at local_days +24h\n+10h\n -+ // (the FIXME for the +11h compensation has been resolved by the\n -+ // fix for the named-rule UNTIL case in PR 116110).\n ++ // The wall UNTIL is interpreted in the prior offset (-11h + save 1h\n ++ // = -10h), so the boundary is at local_days + 24h + 10h.\n + sys_seconds ut(t.time_since_epoch() + 10h );\n sys_info info;\n info = tz->get_info(ut - 1s);\n @@ libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc (new)\n +// { dg-require-effective-target cxx11_abi }\n +// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n +\n -+// Regression test for PR 116110, named-rule case.\n ++// Africa/Algiers 1977-10-21: a Zone line whose RULES references a\n ++// named Rule and whose UNTIL is a wall-time expression. The wall\n ++// UNTIL is interpreted using the SAVE value in force just before the\n ++// boundary (the May-6 rule's save=1, not the Oct-21 rule's save=0\n ++// even though the Oct-21 rule fires at the same wall instant).\n +//\n -+// A Zone line whose RULES references a named Rule and whose UNTIL is\na\n -+// wall-time expression cannot have its UNTIL converted to a true UTC\n -+// instant at parse time, because the SAVE value at the UNTIL depends\non\n -+// which rule of the named set was last in force just before that wall\n -+// time -- and that's not known until all Rule records have been\nloaded\n -+// and indexed. The \"Partial fix for interpretation of non-UTC UNTIL\n -+// times\" commit handled the simpler cases (UNTIL with `s` indicator,\n -+// `u` indicator, or wall + non-named RULES) but explicitly left a\n -+// FIXME for the named-rule case. This test exercises that case via\n -+// the canonical Africa/Algiers boundary at 1977-10-21.\n -+//\n -+// In this synthetic data:\n +// Rule d 1977 May 6 0:00 wall save=1\n +// Rule d 1977 Oct 21 0:00 wall save=0\n +// Z A 0 d WE%sT 1977 O 21\n +// 1 d CE%sT\n -+//\n -+// The first Zone line has STDOFF=0 and uses rule set d. The May rule\n -+// sets save=1 (WEST, total +1). zic.c interprets the wall UNTIL\n -+// \"1977 O 21\" using the SAVE value in force just before the boundary\n -+// (i.e. May's save=1, since the Oct-21 rule in this line's frame\nfires\n -+// at exactly the boundary, after the wall time has been read). So:\n -+// wall(0:00) - stdoff(0) - save_just_before(1) = Oct 20 23:00 UTC\n -+// is the correct UTC instant of the line's end.\n -+//\n -+// The second Zone line has STDOFF=1 and uses the same rule set d. In\n -+// its own frame, the Oct-21 rule fires at exactly its starting\ninstant\n -+// (Oct 20 23:00 UTC = wall(0:00) - stdoff(1)), setting save=0. So at\n -+// Oct 20 23:00 UTC the new line begins with stdoff=1, save=0, abbrev\n -+// \"CET\", total offset +1. Both sides of the boundary have total +1;\n -+// only the (stdoff, save) split changes. This is the merge that\n -+// zdump shows for real Africa/Algiers in October 1977.\n +\n +#include <chrono>\n +#include <fstream>\n @@ libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc (new)\n + VERIFY( pre.save == 1h );\n + VERIFY( pre.abbrev == \"WEST\" );\n +\n -+ // The \"active rule just before the wall UNTIL\" is May-6 (save=1),\n -+ // so the wall UNTIL \"1977 O 21\" gets adjusted by stdoff(0)+save(1).\n -+ // Without the fix, master leaves the line's m_until 1 hour too late\n -+ // and the query just before Oct 21 00:00 UTC is in the wrong frame.\n -+ // With the fix, queries strictly before the boundary stay in the\n -+ // first line (WEST) and queries at/after the boundary are in the\n -+ // second line (CET).\n ++ // The boundary is Oct 20 23:00 UTC (= wall 00:00 - stdoff(0) -\nsave(1)).\n ++ // At and after the boundary we are in the second line (CET).\n + auto at = tz->get_info(sys_days{1977y/October/20} + 23h);\n + VERIFY( at.offset == 1h ); // stdoff 1 + save 0 (CET, second\nline)\n + VERIFY( at.save == 0min );\n @@ libstdc++-v3/testsuite/std/time/time_zone/pr116110_named.cc (new)\n + VERIFY( after.save == 0min );\n + VERIFY( after.abbrev == \"CET\" );\n +\n -+ // And a regression check that the boundary really moved: a query at\n -+ // 1977-10-20 23:30 UTC must be in the SECOND line. Without the\n -+ // fix, master's m_until for the first line is 1977-10-21 00:00 UTC,\n -+ // and this query lands in the WET stretch produced by the first\n -+ // line's expansion of the Oct-21 rule.\n ++ // A query inside the [Oct 20 23:00, Oct 21 00:00] UTC window must\nbe\n ++ // in the second line, not in a leftover stretch from the first\nline.\n + auto window = tz->get_info(sys_days{1977y/October/20} + 23h +\n30min);\n + VERIFY( window.offset == 1h );\n + VERIFY( window.abbrev == \"CET\" );\n4: d94019806 ! 4: 9a01b2555 libstdc++: Cascade wall-time saves in lazy\nexpansion seeding [PR 124853]\n @@\n ## Metadata ##\n -Author: Alvaro Begue <alvaro.begue@gmail.com>\n +Author: Álvaro Begué <alvaro.begue@gmail.com>\n\n ## Commit message ##\n - libstdc++: Cascade wall-time saves in lazy expansion seeding [PR\n124853]\n + libstdc++: Cascade wall-time saves in lazy expansion seeding\n[PR124853]\n\n When _M_get_sys_info seeds a Zone line by looking up the active\nrule\n just before info.begin, the previous code interpreted each rule in\n @@ Commit message\n libstdc++-v3/ChangeLog:\n\n PR libstdc++/124853\n - * src/c++20/tzdb.cc (time_zone::_Impl::_M_get_sys_info):\n + * src/c++20/tzdb.cc (time_zone::_M_get_sys_info):\n Replace the per-rule isolated active-rule search with a\n chronological cascade walker that maintains a running save\n and interprets Wall-time rules' at_time relative to it.\n @@ Commit message\n \"earliest STD rule\" fallback to its own branch.\n * testsuite/std/time/time_zone/wall_cascade.cc: New test.\n\n + Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n +\n ## libstdc++-v3/src/c++20/tzdb.cc ##\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n #endif\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n\n - // Find the Rule whose save value is in force at the wall-time\nUNTIL\n - // of a Zone line, given that `wall_minus_stdoff` is the line's\nUNTIL\n -- // expressed in the \"save=0\" frame (i.e. the parsed wall UNTIL\nwith\n -- // the line's STDOFF subtracted) and `stdoff` is the line's\nstandard\n -- // offset.\n -+ // Find the Rule in `rules` whose effect was last in force at time\n -+ // `t`, given that `stdoff` is the standard offset of the\nenclosing\n -+ // zone line. Returns nullptr if no rule fired strictly before t.\n +- // with STDOFF subtracted and `stdoff` is the line's standard\noffset.\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\nline.\n ++ // Returns nullptr if no rule fired strictly before t.\n //\n - // The function walks all (rule, year) pairs in chronological\norder,\n - // maintaining a running save value. Wall-time rules have their\nTIME\n -@@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n - // zic.c's outzone() logic and is required for zone lines whose\n - // rule set has rules whose at_time depends on cascading saves.\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 -+ // Canonical case: Europe/Paris around 1945, where the France\nrules\n -+ // 1945 Apr 2 02:00 wall save=2 M\n -+ // 1945 Sep 16 03:00 wall save=0 -\n -+ // chain together: in the (stdoff=1, save=2) frame the September\n -+ // rule fires at Sep 16 00:00 UT, not Sep 16 02:00 UT.\n -+ //\n -+ // The calendar window is extended by one year on each side, to\n -+ // catch rules whose wall-time at_time falls in early January or\n -+ // late December but whose UT firing crosses a year boundary due\n -+ // to a large stdoff or save (Pacific/Auckland's \"1946 Ja 1\" rule\n -+ // in stdoff=12h fires at 1945-12-31 11:30 UT).\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,\nseconds stdoff)\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n +\n + // Find the Rule whose save value is in force at the wall-time\nUNTIL\n + // of a Zone line, given that `wall_minus_stdoff` is the line's\nUNTIL\n -+ // expressed in the \"save=0\" frame (i.e. the parsed wall UNTIL\nwith\n -+ // the line's STDOFF subtracted) and `stdoff` is the line's\nstandard\n -+ // offset.\n ++ // with STDOFF subtracted and `stdoff` is the line's standard\noffset.\n + //\n -+ // Like find_active_rule, walks (rule, year) pairs chronologically\n -+ // with a running save value, but interprets `t` differently.\n -+ //\n - // The comparison `fire < boundary` shrinks `boundary` as the\nrunning\n - // save cascades up: a rule that fires AT the boundary (with the\n - // cascaded save applied) is treated as belonging to the next zone\n -@@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n - // of line 6 (\"0 d WE%sT 1977 O 21\"). The pre-rule save (1h, from\n - // the May 6 rule) is what determines the boundary's UT placement,\n - // not the Oct 21 rule's save=0.\n -- //\n -- // The calendar window is extended by one year on each side, to\n -- // catch rules whose wall-time at_time falls in early January or\n -- // late December but whose UT firing crosses a year boundary due\n -- // to a large stdoff or save.\n ++ // Like find_active_rule but `wall_minus_stdoff - running_save`\n ++ // shrinks as save accumulates, so a rule firing AT the boundary\nis\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 @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n - // the Oct-21 rule fires at Oct 20 23:00 UTC in the new line's\n - // frame, which is exactly the new line's begin.\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 @@ libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc (new)\n +// { dg-require-effective-target cxx11_abi }\n +// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n +\n -+// Regression test: when lazy expansion seeds a Zone line whose rule\nset\n -+// has wall-time rules whose effective firing time depends on a prior\n -+// rule's save (cascading wall-time interpretation), the seeding code\n -+// must walk the rules chronologically with a running save value, not\n -+// look at each rule in isolation against a fixed `save=0` frame.\n -+//\n -+// Mirrors the Europe/Paris 1945 case. France's rules\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 -+// chain together: in the (stdoff=1, save=2) frame the September rule\n -+// fires at Sep 16 00:00 UT, not Sep 16 02:00 UT.\n -+//\n -+// Construct a synthetic two-line zone whose second line begins\nbetween\n -+// those two interpretations of the September rule, so the seeding has\n -+// to choose: with the cascade, the September rule has already fired\nat\n -+// info.begin and the new line seeds with save=0; without the cascade,\n -+// the September rule appears not to have fired yet and the new line\n -+// would (incorrectly) seed with save=2 from the April rule.\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 @@ libstdc++-v3/testsuite/std/time/time_zone/wall_cascade.cc (new)\n + // Line 1 ends at \"1945 Sep 16 1u\" (Universal time, no save\nshenanigans),\n + // so info.begin for line 2 is exactly 1945-09-16 01:00 UT.\n + //\n -+ // Cascade seeding: in line 2's frame (stdoff=1):\n -+ // * Apr 2 fires at Apr 2 01:00 UT (running save 0 → 2)\n -+ // * Sep 16 fires at Sep 16 00:00 UT (running save 2 → 0)\n -+ // Both fire before info.begin (Sep 16 01:00 UT), so the active rule\n -+ // at info.begin is the Sep 16 one, save=0 → CET, total offset 1h.\n -+ //\n -+ // Without cascade: Sep 16 is computed as 03:00 - stdoff(1) = 02:00\nUT,\n -+ // which is *after* info.begin, so the seeding falls back to the\nApril\n -+ // rule's save=2 → CEMT, total offset 3h. That is the bug.\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\ninfo.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 -\n5: ab7938201 ! 5: 1c717d45a libstdc++: Implement zic writezone merge\noptimization [PR 124854]\n @@\n ## Metadata ##\n -Author: Alvaro Begue <alvaro.begue@gmail.com>\n +Author: Álvaro Begué <alvaro.begue@gmail.com>\n\n ## Commit message ##\n - libstdc++: Implement zic writezone merge optimization [PR 124854]\n + libstdc++: Implement zic writezone merge optimization [PR124854]\n\n Two distinct correctness fixes that together let lazy expansion\nmatch\n zic.c's writezone output for zones with rule firings near zone-line\n @@ Commit message\n libstdc++-v3/ChangeLog:\n\n PR libstdc++/124854\n - * src/c++20/tzdb.cc (time_zone::_Impl::_M_get_sys_info):\n + * src/c++20/tzdb.cc (time_zone::_M_get_sys_info):\n Always run find_active_rule to seed info.offset and\ninfo.save,\n regardless of whether letters was already populated from\n i[-1].next_letters(). Add a writezone merge optimization\n @@ Commit message\n re-entry.\n * testsuite/std/time/time_zone/zone_merge.cc: New test.\n\n + Signed-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n +\n ## libstdc++-v3/src/c++20/tzdb.cc ##\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n if (i != infos.begin() && i[-1].expanded())\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n\n - if (letters.empty())\n - {\n -- // We want the rule whose effect is in force at info.begin --\n -- // including a rule that fires at exactly info.begin (its effect\n -- // has just begun and is active for the first sys_info we are\n -- // about to generate). The search below uses a strict\n -- // `rule_start < t` comparison, so pass info.begin + 1s to make\n -- // the half-open lookup (..., info.begin] inclusive of the\n -- // boundary instant. This is what makes named-rule zone lines\n -- // like Africa/Algiers (PR 116110) seed with the correct save:\n -- // the Oct-21 rule fires at Oct 20 23:00 UTC in the new line's\n -- // frame, which is exactly the new line's begin.\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 -\n - // Try to find a Rule active before this time, to get initial\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n - {\n - info.offset = ri.offset() + active_rule->save;\n - info.save = chrono::duration_cast<minutes>(active_rule->save);\n -+ // Seed info.offset and info.save from the rule whose effect is in\n -+ // force at info.begin. This must run even when `letters` was\n -+ // already populated from i[-1].next_letters() (which happens\n -+ // during a re-entry of partial lazy expansion: the previous batch\n -+ // left an expanded ZoneInfo whose next_letters() field is the\n -+ // letters for the first sys_info of this batch), because\n -+ // info.offset/save are still at their stdoff/0 init values and\n -+ // would otherwise carry through into the first emitted sys_info\n -+ // with the wrong total offset.\n ++ // Seed info.offset and info.save from the rule active at\n ++ // info.begin. Always run this (even when `letters` was populated\n ++ // from i[-1].next_letters() during partial-expansion re-entry),\n ++ // because info.offset/save are still at their stdoff/0 init\nvalues\n ++ // and would otherwise produce the wrong total offset.\n + //\n -+ // The search uses a strict `rule_start < t` comparison, so pass\n -+ // info.begin + 1s to make the half-open lookup (..., info.begin]\n -+ // inclusive of the boundary instant. This is what makes named-\n -+ // rule zone lines like Africa/Algiers (PR 116110) seed with the\n -+ // correct save: the Oct-21 rule fires at Oct 20 23:00 UTC in the\n -+ // new line's frame, which is exactly the new line's begin.\n ++ // info.begin + 1s makes the strict `rule_start < t` search\n ++ // inclusive of a rule that fires at exactly info.begin.\n + {\n + sys_seconds t = info.begin + seconds(1);\n + const Rule* active_rule = find_active_rule(rules, t,\nri.offset());\n @@ libstdc++-v3/src/c++20/tzdb.cc: namespace std::chrono\n + }\n + }\n +\n -+ // zic.c writezone merge optimization. When the previous zone\n -+ // line's end total offset differs from this line's seeded total\n -+ // and the local time would jump backward at the boundary, look\n -+ // for a rule in this line's set that fires within the resulting\n -+ // gap window and would compensate the jump. zic folds such a\n -+ // rule into the boundary transition; we mirror that here by\n -+ // pulling the rule's save back to info.begin.\n -+ //\n -+ // Only runs at the first sys_info of a zone line (not on re-entry\n -+ // mid-line during partial lazy expansion), which is detected by\n -+ // an empty next_letters() on the prior expanded ZoneInfo: a mid-\n -+ // line re-entry's prior ZoneInfo always has a non-empty\n -+ // next_letters() (the letters of the rule that fires at the new\n -+ // batch's start), whereas a zone-line transition's prior ZoneInfo\n -+ // ends with empty next_letters() because the line's last\n -+ // iteration emits with letters cleared.\n ++ // zic.c writezone merge optimization. When the local time jumps\n ++ // backward at a zone-line boundary and a rule in the new line's\nset\n ++ // fires within that gap window, fold the rule's save into the\n ++ // boundary so the new line begins with the post-rule save.\n + //\n -+ // Canonical examples:\n -+ // * America/Argentina/Buenos_Aires 1999-10-03: lines\n -+ // -3 A -03/-02 1999 O 3\n -+ // -4 A -04/-03 2000 Mar 3\n -+ // have new_total = -4, prev_total = -3 (jump = -1h). The\n -+ // 1999 Oct Argentina rule fires at Oct 3 04:00 UTC in the\n -+ // new -4 frame, which is exactly info.begin + 1h, inside\n -+ // the 1h window.\n -+ // * Europe/Berlin 1945-05-24: lines\n -+ // 1 c CE%sT 1945 May 24 2\n -+ // 1 So CE%sT 1946\n -+ // have new_total = 1, prev_total = 2 (jump = -1h). The So\n -+ // 1945-May-24 rule (save = 2h, \"CEMT\") fires at 01:00 UTC\n -+ // in the new frame, inside the 1h window.\n ++ // Only runs at the first sys_info of a zone line (not on partial-\n ++ // expansion re-entry mid-line): a mid-line re-entry's prior\n ++ // ZoneInfo has a non-empty next_letters(), while a zone-line\n ++ // transition's prior ZoneInfo ends with empty next_letters().\n + if (i != infos.begin() && i[-1].expanded()\n + && i[-1].next_letters().empty())\n + {\n @@ libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc (new)\n +// { dg-require-effective-target cxx11_abi }\n +// { dg-xfail-run-if \"no weak override on AIX\" { powerpc-ibm-aix* } }\n +\n -+// Regression test: zic.c's writezone merges a zone-line transition\nwith\n -+// a rule firing that would otherwise create a brief observably-wrong\n -+// stretch of local time. When two adjacent Zone lines have different\n -+// total offsets and the new line's rule set has a rule firing within\n -+// |jump| of the boundary (where jump = new_total - old_total < 0,\ni.e.\n -+// local time goes backward at the boundary), zic folds that rule into\n -+// the boundary itself: the single transition emitted has the rule's\n -+// save value already applied, so the new line begins with the\npost-rule\n -+// save rather than briefly using the pre-rule save and then\ntransitioning\n -+// again moments later.\n -+//\n -+// Two canonical real-world cases:\n -+// * America/Argentina/Buenos_Aires 1999-10-03 (lines change stdoff\n -+// -3 → -4 with an Argentina DST rule firing on the same day).\n -+// * Europe/Berlin 1945-05-24 (lines split a rule set, with the So\n -+// 1945-May-24 rule firing inside the boundary's window).\n ++// When two adjacent Zone lines differ in total offset and the new\nline's\n ++// rule set has a rule firing within |jump| of the boundary (where\njump\n ++// is a backward local-time jump), zic.c's writezone folds that rule\n ++// into the boundary, so the new line begins with the post-rule save.\n +//\n -+// Mirror the Buenos Aires shape with a synthetic zone.\n ++// Mirrors America/Argentina/Buenos_Aires around 1999-10-03.\n +\n +#include <chrono>\n +#include <fstream>\n @@ libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc (new)\n +{\n + using namespace std::chrono;\n +\n -+ // Argentina-style: stdoff jumps from -3 to -4 at the same instant\n -+ // a save=1 (\"S\") rule fires. In the new (-4) frame, the rule's\n -+ // wall at_time of 00:00 is at UT 04:00, which is 1 hour after the\n -+ // boundary at UT 03:00. Without the merge optimization the new\n -+ // line would seed with save=0 (offset -4, abbrev -04) for that 1\n -+ // hour and then transition to save=1 (offset -3, abbrev -03);\n -+ // with the merge, the boundary itself is at offset=-3, save=1.\n ++ // stdoff jumps from -3 to -4 at the same instant a save=1 rule\nfires.\n ++ // In the new (-4) frame the rule fires 1 hour after the boundary at\n ++ // UT 03:00, so the merge folds the rule into the boundary and the\n ++ // new line begins at offset=-3, save=1 (abbrev \"-03\").\n + std::ofstream(\"tzdata.zi\") << R\"(# version test_zone_merge\n +R T 1999 o - O 3 0 1 -\n +R T 2000 o - Mar 3 0 0 -\n @@ libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc (new)\n + VERIFY( before.save == 0min );\n + VERIFY( before.abbrev == \"-03\" );\n +\n -+ // At the boundary the merge optimization kicks in: the second zone\n -+ // line's first sys_info should already have save=1 from the Oct 3\n -+ // rule, total offset -3h, abbrev \"-03\". Without the fix, chrono\n -+ // would emit a 1-hour stretch of save=0 (\"-04\") here.\n ++ // The new line's first sys_info already has save=1 from the merge,\n ++ // total offset -3h, abbrev \"-03\".\n + auto at_boundary = tz->get_info(boundary);\n + VERIFY( at_boundary.offset == -3h );\n + VERIFY( at_boundary.save == 60min );" }