Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/1.2/patches/2228548/?format=api
{ "id": 2228548, "url": "http://patchwork.ozlabs.org/api/1.2/patches/2228548/?format=api", "web_url": "http://patchwork.ozlabs.org/project/gcc/patch/CAF8dVMVcmh5ci027piC81ue4wyUWYgmHYkykgmyVBX0wPoqfUA@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": "<CAF8dVMVcmh5ci027piC81ue4wyUWYgmHYkykgmyVBX0wPoqfUA@mail.gmail.com>", "list_archive_url": null, "date": "2026-04-26T23:57:42", "name": "[v2,5/5] libstdc++: Implement zic writezone merge optimization [PR124854]", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "380da46548493d5b96997102d5aa654498e92e08", "submitter": { "id": 93119, "url": "http://patchwork.ozlabs.org/api/1.2/people/93119/?format=api", "name": "Álvaro Begué", "email": "alvaro.begue@gmail.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/gcc/patch/CAF8dVMVcmh5ci027piC81ue4wyUWYgmHYkykgmyVBX0wPoqfUA@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/patches/2228548/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2228548/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=aBSEXoyi;\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=aBSEXoyi", "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.44" ], "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 4g3kFF4RHrz1yJX\n\tfor <incoming@patchwork.ozlabs.org>; Mon, 27 Apr 2026 09:59:12 +1000 (AEST)", "from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 4F96D4BB24C5\n\tfor <incoming@patchwork.ozlabs.org>; Sun, 26 Apr 2026 23:59:05 +0000 (GMT)", "from mail-vs1-f44.google.com (mail-vs1-f44.google.com\n [209.85.217.44])\n by sourceware.org (Postfix) with ESMTPS id 299064BB24D8\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 23:58:20 +0000 (GMT)", "by mail-vs1-f44.google.com with SMTP id\n ada2fe7eead31-6055a0414d7so2422848137.2\n for <gcc-patches@gcc.gnu.org>; Sun, 26 Apr 2026 16:58:20 -0700 (PDT)" ], "DKIM-Filter": [ "OpenDKIM Filter v2.11.0 sourceware.org 4F96D4BB24C5", "OpenDKIM Filter v2.11.0 sourceware.org 299064BB24D8" ], "DMARC-Filter": "OpenDMARC Filter v1.4.2 sourceware.org 299064BB24D8", "ARC-Filter": "OpenARC Filter v1.0.0 sourceware.org 299064BB24D8", "ARC-Seal": [ "i=2; a=rsa-sha256; d=sourceware.org; s=key; t=1777247900; cv=pass;\n b=IkTQyNMeEBkF7zCED9Yk053UeoUCrdtnlkX7c4xZf7Czb4xvNVpKIKEo7XYe8lRCkIhyu/5K9Ow9W/EipuA6BESQBvUkdWAvIzPY3BNaYc9BaRg8Z7t6YOPaykttRwhBlDvpicfCtl7V12ET3rNerBXzB5/wsl5LnEQIoHTi8Qk=", "i=1; a=rsa-sha256; t=1777247899; cv=none;\n d=google.com; s=arc-20240605;\n b=amEL8nmheJyzwMzDAM1EAfCf4UyMHPj8Gj0/S7fBEyYnTE5wpkWrUhZN0GNNTK2O5F\n 3h0tDNzO4enFL4pFKq5qa0y/IjM+DJTMed91VMnY/EfwKFrl2Sw5g0dUpU1KmNiaKUS+\n Yyx4lpLVUnwLMdd9nytddJgT5nOeOy90WN3DnCMGVD72w4f1rJvt6e2EJtjG9bFCPzpN\n r875EEWhvTBbode97T5jfzc9i1M/qgeARrCMjpgS/6UXFpPWkts8eVQ6tJq9USwsvy89\n j1EA7em0dV0EOphyeCHSd6AdqWnHB+sDc42ngmPgHovxJrEwqZzJKzC/sqbqa8cHVhwR\n NrlA==" ], "ARC-Message-Signature": [ "i=2; a=rsa-sha256; d=sourceware.org; s=key;\n t=1777247900; c=relaxed/simple;\n bh=KrXOuDTW1TBo8sw4YA7u14zdlGhnemkN5B+Vr6Pw9M4=;\n h=DKIM-Signature:MIME-Version:From:Date:Message-ID:Subject:To;\n b=HL1h07VvtaJ/XMpZZpIJlNuFxV4Jl2HQng7fj3IfLfryXImAFrr5fT+6Mc4k2QctlPToTpI4w0HGcHp7qQ3doJUS2RKriVb1Yu3f+Zq4A2JpcohJyoHRlCo99heOv+pYPqipbkgFhN18+uZOZezhFPW6bk0Z8LhXR7a/wSOGvc4=", "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=f7EXRs8WLhBa6l1+IGpx/7Q9a+BOP4y0/ruhKCuboZo=;\n fh=sWbBxiX87E8F1zPxXVV3X4tQqxV9sZLoQvmjvveliwE=;\n b=ehiId0rvObt87jei98g+60GM3L1CbBd39cunxdy0ls+Gz0colmkoKYjHVDOHIGaSn/\n 6xv8IG4QWxilmRZ1RLIzsTL/wzF+xxERbGvyalZOT1WMRKBAOsDn2mp2hQCpkwJsCnpT\n mSioQR7inZLAVTW6tZwA876DZjvkw1MNeVZIZHYhPDwVmWm8TKCeIEpjMXqZLQPIOtOk\n zDLKu1YgPZLk6NQJjD8mxWTPLSZtvXVjXt5adgDaNSiHQKdEC/WveLi3aIV/UcvQ+s9c\n 1hi6mlInvPCroh1TPf2RYwNDivHETcnWmJTMhZPS9aT8pFfrNAmPSOSqE0B6KtKbPoyK\n p65g==; 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=1777247899; x=1777852699; 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=f7EXRs8WLhBa6l1+IGpx/7Q9a+BOP4y0/ruhKCuboZo=;\n b=aBSEXoyiNUaPZKRgxXb4IkicVgBzMkw2RcMh+i66kx0eW2fUe93rYj2AA09LtaWgj6\n YYAEaY7N3qQnvR9Dlp2cc3h0XGT/6BULgxYiwnY1gHd/Ta4s2txuCZZJd00NuaQjyDXK\n 9MRDrlKh2smbIn5IO6ksO/vzC5XxRKavH2/e6BrrhRH1OceHIr3cgOIw6Yg38Tz33RNd\n 7imMFs9XDDIIexppji3t5uH/KYjpPKk/O0QG9D17Xksi7hCmCEJ+BInVCuWdBdMBKjdS\n vzpPq2aC5kEPHq3eFpQjWuMUV3rH5r2bopiKxtOHsd2kCOEJKGezpRPKXCIyPp8r0Ygf\n IOGQ==", "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1777247899; x=1777852699;\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=f7EXRs8WLhBa6l1+IGpx/7Q9a+BOP4y0/ruhKCuboZo=;\n b=sfbqOsZoAY8RYk/1UjD9ZizLPKdup+kVSKzejzKVNmngch7qp7xTxqGmYzcsNoFdgJ\n HQGmDmwmRagjfFoPzebRMl2j12kJ/72VvxpBfqZSFaWKrkqbxwhfG1VnJvovtk2HFIJP\n ktuRy7w8HzXoIKSXpOmADI3pG+Mlh2WWDKRMLh108KChjDpNdUMFMxLouXnqg4YkUptI\n 4xILjJqptl2D1eGmTp8fn9K/TqVz2EFsGkebD4hdT550ELXuGqACxEEtbqKsyDSCG0p3\n X/XyUWU+l22Pdsrrbz6YHxlckPnl64LGpRxq6fvBTTJ+DG+UJQkXKBUwX6QpWyG5YNB5\n aHVg==", "X-Gm-Message-State": "AOJu0Yyll/e7gp89nZMQ/AVLDcff31++j1a6Tkplcsx3bZz5Cpbnevr3\n 3aSbGHhU5Ug3qZojgosTy/Zmql7SvnNjtD729/Xn5BzL1ey58KYgydXsigZOWtwIxbEt12DNI3J\n qm3JFFsEQSfzErxzEhHV0bUyMPmLR7o5z+lUftu8=", "X-Gm-Gg": "AeBDies869hdFJcy9I05UqhnW+qkkBHUsQPHa5/OfkfzAxVFSVErz70Rr3N33dNxRUT\n +Cii3tabhjepDtVeIFObQh3bfcpx8MhU+QDS6smXwC6BrRQelDiAyM3aOg3cHd0wAvlkTFeZKfx\n ZlygOpiENElanNrCzqxwP55LzQRDvwCnUl+jfjzeItsb4WnbU6Uqy94GngAgfE0ZQjkraol6FxU\n G19uMOj68tpg1dEpqww/Q3E9YWHtQqbcl0B9Q4SzzP9vu58yymYi37khEXyJjx96YhOvnPZohf0\n lbLnJ8Px7J52bPbgDlcCN1eVufZM407Y/gJBs3eZd2ACLPlP51WooIqk+Sd/swmXJMH2Pvdu8P0\n ijy75zK8L9iUBNZs4MjRSxrJh2mpFVfBkT4truG2HkzSQBr5HuWt3FZSc4Tq2d/gEmZKjxg==", "X-Received": "by 2002:a05:6102:3a11:b0:608:94e3:bd89 with SMTP id\n ada2fe7eead31-616f5eb9cd4mr18036281137.1.1777247899317; Sun, 26 Apr 2026\n 16:58:19 -0700 (PDT)", "MIME-Version": "1.0", "From": "=?utf-8?b?w4FsdmFybyBCZWd1w6k=?= <alvaro.begue@gmail.com>", "Date": "Sun, 26 Apr 2026 19:57:42 -0400", "X-Gm-Features": "AVHnY4L4uxorRMpYOqetnyqgThrVT6qRbNBSZ0rhmBxgJrZYTrmtmxhsoPZkV_s", "Message-ID": "\n <CAF8dVMVcmh5ci027piC81ue4wyUWYgmHYkykgmyVBX0wPoqfUA@mail.gmail.com>", "Subject": "[PATCH v2 5/5] libstdc++: Implement zic writezone merge optimization\n [PR124854]", "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=\"000000000000b77a3f065065c5a3\"", "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": "Two distinct correctness fixes that together let lazy expansion match\nzic.c's writezone output for zones with rule firings near zone-line\nboundaries.\n\n1. Always seed info.offset and info.save from the active rule.\n\n Previously, _M_get_sys_info skipped the find_active_rule lookup\n when `letters` had already been populated from i[-1].next_letters().\n That happens during a re-entry of partial lazy expansion: a prior\n call cut off after num_after sys_infos and left an expanded\n ZoneInfo whose next_letters() field is the letters of the rule\n that should fire at the next batch's start.\n\n The skip is wrong because info.offset and info.save are still at\n their (ri.offset(), 0) initialization values when entering the\n loop. Without re-running the seeding, the first sys_info of the\n new batch is emitted with stdoff alone for offset and save=0,\n even though the line had non-zero save in force at info.begin.\n\n Canonical breakage: Europe/Berlin around 1947-06-29. With\n num_after=4, batch 1 stops mid-line; batch 2 re-enters with\n non-empty letters and emits a 2-hour CEST sys_info that has\n offset=3600 (CET's offset) and save=0, instead of offset=7200\n save=60 — observably wrong total offset for two hours.\n\n Fix: pull the seeding (find_active_rule + info.offset/save\n assignment) out of the `if (letters.empty())` branch and run it\n unconditionally. Only the letters fallback (first_std lookup)\n stays gated on letters being empty.\n\n2. Add zic.c writezone merge optimization for backward jumps at\n zone-line boundaries.\n\n When two adjacent Zone lines have different total offsets and the\n new line's rule set has a rule firing within |jump| of the\n boundary (where jump = new_total - old_total < 0, i.e. local time\n goes backward at the boundary), zic folds that rule into the\n boundary itself: the single transition emitted has the rule's\n save value already applied, so the new line begins with the\n post-rule save rather than briefly using the pre-rule save and\n then transitioning again moments later.\n\n Canonical examples handled by the new merge block:\n * America/Argentina/Buenos_Aires 1999-10-03: lines change\n stdoff -3 → -4 with an Argentina DST rule firing on the same\n day. Without the merge, chrono emits a 1-hour stretch of\n offset=-4 save=0 and then transitions to offset=-3 save=1;\n with the merge, the boundary itself is at offset=-3 save=1.\n * Europe/Berlin 1945-05-24: lines split a rule set, with the\n So 1945-May-24 rule (save=2, \"CEMT\") firing at 01:00 UTC in\n the new frame, inside the 1h backward window.\n\n The merge block runs only at the first sys_info of a zone line,\n not on partial-expansion re-entry. We detect this by checking\n that i[-1].next_letters() is empty: a mid-line re-entry's prior\n ZoneInfo always has non-empty next_letters() (the rule firing\n at the new batch's start), whereas a zone-line transition's\n prior ZoneInfo ends with empty next_letters() because its\n line's last forward-walk iteration emits with letters cleared.\n\nlibstdc++-v3/ChangeLog:\n\nPR libstdc++/124854\n* src/c++20/tzdb.cc (time_zone::_M_get_sys_info):\nAlways run find_active_rule to seed info.offset and info.save,\nregardless of whether letters was already populated from\ni[-1].next_letters(). Add a writezone merge optimization\nblock at zone-line boundaries with backward jumps, gated on\nan empty next_letters() to avoid running on partial-expansion\nre-entry.\n* testsuite/std/time/time_zone/zone_merge.cc: New test.\n\nSigned-off-by: Álvaro Begué <alvaro.begue@gmail.com>\n---\n libstdc++-v3/src/c++20/tzdb.cc | 116 ++++++++++++------\n .../std/time/time_zone/zone_merge.cc | 84 +++++++++++++\n 2 files changed, 161 insertions(+), 39 deletions(-)\n create mode 100644 libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc", "diff": "diff --git a/libstdc++-v3/src/c++20/tzdb.cc b/libstdc++-v3/src/c++20/tzdb.cc\nindex 4de77e6b1..193579a98 100644\n--- a/libstdc++-v3/src/c++20/tzdb.cc\n+++ b/libstdc++-v3/src/c++20/tzdb.cc\n@@ -1017,46 +1017,87 @@ namespace std::chrono\n if (i != infos.begin() && i[-1].expanded())\n letters = i[-1].next_letters();\n\n- if (letters.empty())\n- {\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- // 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+ // 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 values\n+ // and would otherwise produce the wrong total offset.\n+ //\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, ri.offset());\n+ if (active_rule)\n+ {\n+ info.offset = ri.offset() + active_rule->save;\n+ info.save = chrono::duration_cast<minutes>(active_rule->save);\n+ if (letters.empty())\n letters = active_rule->letters;\n- }\n- else\n+ }\n+ else if (letters.empty())\n+ {\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+ first_std = &rule;\n+ else if (rule.from == first_std->from)\n+ {\n+ if (rule.start_time(rule.from, {})\n+ < first_std->start_time(first_std->from, {}))\n+ first_std = &rule;\n+ }\n+ }\n+ if (first_std)\n+ letters = first_std->letters;\n+ }\n+ }\n+\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 set\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+ // 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+ sys_info prev;\n+ i[-1].to(prev);\n+ const seconds prev_total = prev.offset;\n+ const seconds new_total = info.offset;\n+ const seconds jump = new_total - prev_total;\n+ if (jump < 0s)\n {\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+ const seconds window = -jump;\n+ // Look for a rule firing in (info.begin, info.begin+window].\n+ const Rule* merge_rule\n+ = find_active_rule(rules,\n+ info.begin + window + seconds(1),\n+ ri.offset());\n+ if (merge_rule\n+ && merge_rule->start_time(year_month_day{\n+ chrono::floor<days>(\n+ info.begin + window)\n+ }.year(),\n+ ri.offset()) > info.begin)\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- first_std = &rule;\n- else if (rule.from == first_std->from)\n- {\n- if (rule.start_time(rule.from, {})\n- < first_std->start_time(first_std->from, {}))\n- first_std = &rule;\n- }\n+ info.offset = ri.offset() + merge_rule->save;\n+ info.save\n+ = chrono::duration_cast<minutes>(merge_rule->save);\n+ letters = merge_rule->letters;\n }\n- if (first_std)\n- letters = first_std->letters;\n }\n }\n\n@@ -1105,9 +1146,6 @@ namespace std::chrono\n\n if (t < rule_start && rule_start < info.end)\n {\n- if (rule_start - t < days(1)) // XXX shouldn't be needed!\n- continue;\n-\n // Found a closer transition than the previous info.end.\n info.end = rule_start;\n next_rule = &rule;\ndiff --git a/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc\nb/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc\nnew file mode 100644\nindex 000000000..75942f976\n--- /dev/null\n+++ b/libstdc++-v3/testsuite/std/time/time_zone/zone_merge.cc\n@@ -0,0 +1,84 @@\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+// When two adjacent Zone lines differ in total offset and the new line's\n+// rule set has a rule firing within |jump| of the boundary (where jump\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+// Mirrors America/Argentina/Buenos_Aires around 1999-10-03.\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+ // stdoff jumps from -3 to -4 at the same instant a save=1 rule fires.\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+Z Test/BA -3 - %z 1999 O 3\n+ -4 T %z 2000 Mar 3\n+ -3 - %z\n+)\";\n+\n+ const auto& db = reload_tzdb();\n+ VERIFY( override_used );\n+ VERIFY( db.version == \"test_zone_merge\" );\n+\n+ auto* tz = locate_zone(\"Test/BA\");\n+\n+ // The boundary is the wall UNTIL \"1999 O 3\" (default time 00:00)\n+ // interpreted in the prior (-3) frame, i.e. UT 03:00 1999-10-03.\n+ sys_seconds boundary{sys_days(1999y/October/3) + 3h};\n+\n+ auto before = tz->get_info(boundary - 1s);\n+ VERIFY( before.offset == -3h );\n+ VERIFY( before.save == 0min );\n+ VERIFY( before.abbrev == \"-03\" );\n+\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 );\n+ VERIFY( at_boundary.abbrev == \"-03\" );\n+\n+ auto plus_30min = tz->get_info(boundary + 30min);\n+ VERIFY( plus_30min.offset == -3h );\n+ VERIFY( plus_30min.save == 60min );\n+ VERIFY( plus_30min.abbrev == \"-03\" );\n+\n+ // Sanity: well after the boundary, still in the merged sys_info\n+ // until the Mar 3 2000 transition.\n+ auto winter = tz->get_info(sys_days(2000y/January/15));\n+ VERIFY( winter.offset == -3h );\n+ VERIFY( winter.save == 60min );\n+ VERIFY( winter.abbrev == \"-03\" );\n+\n+ // After Mar 3 2000: line 2 ends, line 3 begins. No DST rule fires\n+ // at this boundary, so total offset reverts to -3h with save=0.\n+ auto spring = tz->get_info(sys_days(2000y/April/15));\n+ VERIFY( spring.offset == -3h );\n+ VERIFY( spring.save == 0min );\n+ VERIFY( spring.abbrev == \"-03\" );\n+}\n", "prefixes": [ "v2", "5/5" ] }